summaryrefslogtreecommitdiffstats
path: root/sc/source/core
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--sc/source/core/data/attarray.cxx2690
-rw-r--r--sc/source/core/data/attrib.cxx889
-rw-r--r--sc/source/core/data/autonamecache.cxx90
-rw-r--r--sc/source/core/data/bcaslot.cxx1300
-rw-r--r--sc/source/core/data/bigrange.cxx25
-rw-r--r--sc/source/core/data/celltextattr.cxx21
-rw-r--r--sc/source/core/data/cellvalue.cxx691
-rw-r--r--sc/source/core/data/cellvalues.cxx357
-rw-r--r--sc/source/core/data/clipcontext.cxx440
-rw-r--r--sc/source/core/data/clipparam.cxx189
-rw-r--r--sc/source/core/data/colcontainer.cxx55
-rw-r--r--sc/source/core/data/colorscale.cxx1445
-rw-r--r--sc/source/core/data/column.cxx3391
-rw-r--r--sc/source/core/data/column2.cxx3793
-rw-r--r--sc/source/core/data/column3.cxx3726
-rw-r--r--sc/source/core/data/column4.cxx2225
-rw-r--r--sc/source/core/data/columniterator.cxx209
-rw-r--r--sc/source/core/data/columnset.cxx67
-rw-r--r--sc/source/core/data/columnspanset.cxx369
-rw-r--r--sc/source/core/data/compressedarray.cxx418
-rw-r--r--sc/source/core/data/conditio.cxx2335
-rw-r--r--sc/source/core/data/dbdocutl.cxx201
-rw-r--r--sc/source/core/data/dociter.cxx1779
-rw-r--r--sc/source/core/data/docparam.cxx25
-rw-r--r--sc/source/core/data/docpool.cxx619
-rw-r--r--sc/source/core/data/documen2.cxx1471
-rw-r--r--sc/source/core/data/documen3.cxx2155
-rw-r--r--sc/source/core/data/documen4.cxx1367
-rw-r--r--sc/source/core/data/documen5.cxx657
-rw-r--r--sc/source/core/data/documen6.cxx210
-rw-r--r--sc/source/core/data/documen7.cxx621
-rw-r--r--sc/source/core/data/documen8.cxx1329
-rw-r--r--sc/source/core/data/documen9.cxx681
-rw-r--r--sc/source/core/data/document.cxx7089
-rw-r--r--sc/source/core/data/document10.cxx1106
-rw-r--r--sc/source/core/data/documentimport.cxx859
-rw-r--r--sc/source/core/data/documentstreamaccess.cxx147
-rw-r--r--sc/source/core/data/dpcache.cxx1522
-rw-r--r--sc/source/core/data/dpdimsave.cxx791
-rw-r--r--sc/source/core/data/dpfilteredcache.cxx433
-rw-r--r--sc/source/core/data/dpglobal.cxx20
-rw-r--r--sc/source/core/data/dpgroup.cxx1030
-rw-r--r--sc/source/core/data/dpitemdata.cxx371
-rw-r--r--sc/source/core/data/dpnumgroupinfo.cxx35
-rw-r--r--sc/source/core/data/dpobject.cxx3952
-rw-r--r--sc/source/core/data/dpoutput.cxx1781
-rw-r--r--sc/source/core/data/dpoutputgeometry.cxx255
-rw-r--r--sc/source/core/data/dpresfilter.cxx267
-rw-r--r--sc/source/core/data/dpsave.cxx1383
-rw-r--r--sc/source/core/data/dpsdbtab.cxx169
-rw-r--r--sc/source/core/data/dpshttab.cxx323
-rw-r--r--sc/source/core/data/dptabdat.cxx292
-rw-r--r--sc/source/core/data/dptabres.cxx4117
-rw-r--r--sc/source/core/data/dptabsrc.cxx2613
-rw-r--r--sc/source/core/data/dputil.cxx420
-rw-r--r--sc/source/core/data/drawpage.cxx51
-rw-r--r--sc/source/core/data/drwlayer.cxx2676
-rw-r--r--sc/source/core/data/edittextiterator.cxx97
-rw-r--r--sc/source/core/data/fillinfo.cxx1064
-rw-r--r--sc/source/core/data/formulacell.cxx5571
-rw-r--r--sc/source/core/data/formulaiter.cxx77
-rw-r--r--sc/source/core/data/funcdesc.cxx1267
-rw-r--r--sc/source/core/data/global.cxx1160
-rw-r--r--sc/source/core/data/global2.cxx598
-rw-r--r--sc/source/core/data/globalx.cxx142
-rw-r--r--sc/source/core/data/grouptokenconverter.cxx316
-rw-r--r--sc/source/core/data/listenercontext.cxx95
-rw-r--r--sc/source/core/data/markarr.cxx462
-rw-r--r--sc/source/core/data/markdata.cxx950
-rw-r--r--sc/source/core/data/markmulti.cxx485
-rw-r--r--sc/source/core/data/mtvcellfunc.cxx31
-rw-r--r--sc/source/core/data/mtvelements.cxx198
-rw-r--r--sc/source/core/data/olinetab.cxx873
-rw-r--r--sc/source/core/data/pagepar.cxx56
-rw-r--r--sc/source/core/data/patattr.cxx1422
-rw-r--r--sc/source/core/data/pivot2.cxx161
-rw-r--r--sc/source/core/data/poolhelp.cxx118
-rw-r--r--sc/source/core/data/postit.cxx1306
-rw-r--r--sc/source/core/data/queryevaluator.cxx961
-rw-r--r--sc/source/core/data/queryiter.cxx1476
-rw-r--r--sc/source/core/data/refupdatecontext.cxx139
-rw-r--r--sc/source/core/data/rowheightcontext.cxx38
-rw-r--r--sc/source/core/data/segmenttree.cxx711
-rw-r--r--sc/source/core/data/sheetevents.cxx123
-rw-r--r--sc/source/core/data/simpleformulacalc.cxx155
-rw-r--r--sc/source/core/data/sortparam.cxx306
-rw-r--r--sc/source/core/data/stlpool.cxx447
-rw-r--r--sc/source/core/data/stlsheet.cxx303
-rw-r--r--sc/source/core/data/subtotalparam.cxx199
-rw-r--r--sc/source/core/data/tabbgcolor.cxx36
-rw-r--r--sc/source/core/data/table1.cxx2729
-rw-r--r--sc/source/core/data/table2.cxx4372
-rw-r--r--sc/source/core/data/table3.cxx3099
-rw-r--r--sc/source/core/data/table4.cxx2990
-rw-r--r--sc/source/core/data/table5.cxx1295
-rw-r--r--sc/source/core/data/table6.cxx1155
-rw-r--r--sc/source/core/data/table7.cxx652
-rw-r--r--sc/source/core/data/tabprotection.cxx724
-rw-r--r--sc/source/core/data/types.cxx32
-rw-r--r--sc/source/core/data/userdat.cxx51
-rw-r--r--sc/source/core/data/validat.cxx1087
-rw-r--r--sc/source/core/inc/addinhelpid.hxx45
-rw-r--r--sc/source/core/inc/addinlis.hxx84
-rw-r--r--sc/source/core/inc/adiasync.hxx77
-rw-r--r--sc/source/core/inc/bcaslot.hxx396
-rw-r--r--sc/source/core/inc/cellkeytranslator.hxx83
-rw-r--r--sc/source/core/inc/ddelink.hxx84
-rw-r--r--sc/source/core/inc/doubleref.hxx178
-rw-r--r--sc/source/core/inc/formulagroupcl.hxx29
-rw-r--r--sc/source/core/inc/grouptokenconverter.hxx38
-rw-r--r--sc/source/core/inc/interpre.hxx1165
-rw-r--r--sc/source/core/inc/jumpmatrix.hxx122
-rw-r--r--sc/source/core/inc/parclass.hxx144
-rw-r--r--sc/source/core/inc/poolhelp.hxx65
-rw-r--r--sc/source/core/inc/refupdat.hxx71
-rw-r--r--sc/source/core/inc/scrdata.hxx37
-rw-r--r--sc/source/core/inc/sharedstringpoolpurge.hxx48
-rw-r--r--sc/source/core/inc/webservicelink.hxx49
-rw-r--r--sc/source/core/opencl/cl-test.odsbin0 -> 18569 bytes
-rw-r--r--sc/source/core/opencl/formulagroupcl.cxx4438
-rw-r--r--sc/source/core/opencl/op_addin.cxx236
-rw-r--r--sc/source/core/opencl/op_addin.hxx33
-rw-r--r--sc/source/core/opencl/op_array.cxx191
-rw-r--r--sc/source/core/opencl/op_array.hxx40
-rw-r--r--sc/source/core/opencl/op_database.cxx1642
-rw-r--r--sc/source/core/opencl/op_database.hxx106
-rw-r--r--sc/source/core/opencl/op_financial.cxx4791
-rw-r--r--sc/source/core/opencl/op_financial.hxx579
-rw-r--r--sc/source/core/opencl/op_logical.cxx360
-rw-r--r--sc/source/core/opencl/op_logical.hxx58
-rw-r--r--sc/source/core/opencl/op_math.cxx3207
-rw-r--r--sc/source/core/opencl/op_math.hxx499
-rw-r--r--sc/source/core/opencl/op_spreadsheet.cxx286
-rw-r--r--sc/source/core/opencl/op_spreadsheet.hxx27
-rw-r--r--sc/source/core/opencl/op_statistical.cxx9828
-rw-r--r--sc/source/core/opencl/op_statistical.hxx550
-rw-r--r--sc/source/core/opencl/opbase.cxx390
-rw-r--r--sc/source/core/opencl/opbase.hxx246
-rw-r--r--sc/source/core/opencl/opinlinefun_finacial.cxx1882
-rw-r--r--sc/source/core/opencl/opinlinefun_math.hxx90
-rw-r--r--sc/source/core/opencl/opinlinefun_statistical.cxx1366
-rw-r--r--sc/source/core/tool/addincfg.cxx53
-rw-r--r--sc/source/core/tool/addincol.cxx1694
-rw-r--r--sc/source/core/tool/addinhelpid.cxx209
-rw-r--r--sc/source/core/tool/addinlis.cxx133
-rw-r--r--sc/source/core/tool/address.cxx2532
-rw-r--r--sc/source/core/tool/adiasync.cxx136
-rw-r--r--sc/source/core/tool/appoptio.cxx654
-rw-r--r--sc/source/core/tool/arraysum.hxx36
-rw-r--r--sc/source/core/tool/arraysumSSE2.cxx122
-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.cxx411
-rw-r--r--sc/source/core/tool/cellform.cxx214
-rw-r--r--sc/source/core/tool/cellkeytranslator.cxx232
-rw-r--r--sc/source/core/tool/cellkeywords.inl199
-rw-r--r--sc/source/core/tool/chartarr.cxx377
-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.cxx523
-rw-r--r--sc/source/core/tool/chgtrack.cxx4671
-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.cxx6568
-rw-r--r--sc/source/core/tool/consoli.cxx544
-rw-r--r--sc/source/core/tool/dbdata.cxx1637
-rw-r--r--sc/source/core/tool/ddelink.cxx266
-rw-r--r--sc/source/core/tool/defaultsoptions.cxx151
-rw-r--r--sc/source/core/tool/detdata.cxx88
-rw-r--r--sc/source/core/tool/detfunc.cxx1755
-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.cxx935
-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.cxx357
-rw-r--r--sc/source/core/tool/formulaopt.cxx652
-rw-r--r--sc/source/core/tool/formulaparserpool.cxx142
-rw-r--r--sc/source/core/tool/formularesult.cxx640
-rw-r--r--sc/source/core/tool/grouparealistener.cxx352
-rw-r--r--sc/source/core/tool/hints.cxx113
-rw-r--r--sc/source/core/tool/inputopt.cxx162
-rw-r--r--sc/source/core/tool/interpr1.cxx10198
-rw-r--r--sc/source/core/tool/interpr2.cxx3679
-rw-r--r--sc/source/core/tool/interpr3.cxx5579
-rw-r--r--sc/source/core/tool/interpr4.cxx4795
-rw-r--r--sc/source/core/tool/interpr5.cxx3345
-rw-r--r--sc/source/core/tool/interpr6.cxx1010
-rw-r--r--sc/source/core/tool/interpr7.cxx556
-rw-r--r--sc/source/core/tool/interpr8.cxx2008
-rw-r--r--sc/source/core/tool/interpretercontext.cxx215
-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.cxx128
-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.cxx65
-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.cxx30
-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.cxx179
-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.cxx200
-rw-r--r--sc/source/core/tool/rangelst.cxx1532
-rw-r--r--sc/source/core/tool/rangenam.cxx899
-rw-r--r--sc/source/core/tool/rangeseq.cxx446
-rw-r--r--sc/source/core/tool/rangeutl.cxx1068
-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.cxx3420
-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.cxx466
-rw-r--r--sc/source/core/tool/stylehelper.cxx162
-rw-r--r--sc/source/core/tool/subtotal.cxx204
-rw-r--r--sc/source/core/tool/token.cxx5389
-rw-r--r--sc/source/core/tool/tokenstringcontext.cxx136
-rw-r--r--sc/source/core/tool/typedstrdata.cxx103
-rw-r--r--sc/source/core/tool/unitconv.cxx118
-rw-r--r--sc/source/core/tool/userlist.cxx360
-rw-r--r--sc/source/core/tool/viewopti.cxx611
-rw-r--r--sc/source/core/tool/webservicelink.cxx98
-rw-r--r--sc/source/core/tool/zforauto.cxx88
237 files changed, 224104 insertions, 0 deletions
diff --git a/sc/source/core/data/attarray.cxx b/sc/source/core/data/attarray.cxx
new file mode 100644
index 000000000..d5eaa1906
--- /dev/null
+++ b/sc/source/core/data/attarray.cxx
@@ -0,0 +1,2690 @@
+/* -*- 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 <attarray.hxx>
+#include <scitems.hxx>
+#include <editeng/borderline.hxx>
+#include <editeng/boxitem.hxx>
+#include <editeng/lineitem.hxx>
+#include <editeng/shaditem.hxx>
+#include <editeng/editobj.hxx>
+#include <editeng/justifyitem.hxx>
+#include <osl/diagnose.h>
+#include <svl/poolcach.hxx>
+#include <sfx2/objsh.hxx>
+
+#include <global.hxx>
+#include <document.hxx>
+#include <docpool.hxx>
+#include <patattr.hxx>
+#include <stlsheet.hxx>
+#include <stlpool.hxx>
+#include <markarr.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <segmenttree.hxx>
+#include <editdataarray.hxx>
+#include <cellvalue.hxx>
+#include <editutil.hxx>
+#include <mtvelements.hxx>
+#include <memory>
+
+using ::editeng::SvxBorderLine;
+
+ScAttrArray::ScAttrArray( SCCOL nNewCol, SCTAB nNewTab, ScDocument& rDoc, ScAttrArray* pDefaultColAttrArray ) :
+ nCol( nNewCol ),
+ nTab( nNewTab ),
+ rDocument( rDoc )
+{
+ if ( nCol == -1 || !pDefaultColAttrArray || pDefaultColAttrArray->mvData.empty() )
+ return;
+
+ ScAddress aAdrStart( nCol, 0, nTab );
+ ScAddress aAdrEnd( nCol, 0, nTab );
+ mvData.resize( pDefaultColAttrArray->mvData.size() );
+ for ( size_t nIdx = 0; nIdx < pDefaultColAttrArray->mvData.size(); ++nIdx )
+ {
+ mvData[nIdx].nEndRow = pDefaultColAttrArray->mvData[nIdx].nEndRow;
+ ScPatternAttr aNewPattern( *(pDefaultColAttrArray->mvData[nIdx].pPattern) );
+ mvData[nIdx].pPattern = &rDocument.GetPool()->Put( aNewPattern );
+ bool bNumFormatChanged = false;
+ if ( ScGlobal::CheckWidthInvalidate( bNumFormatChanged,
+ mvData[nIdx].pPattern->GetItemSet(), rDocument.GetDefPattern()->GetItemSet() ) )
+ {
+ aAdrStart.SetRow( nIdx ? mvData[nIdx-1].nEndRow+1 : 0 );
+ aAdrEnd.SetRow( mvData[nIdx].nEndRow );
+ rDocument.InvalidateTextWidth( &aAdrStart, &aAdrEnd, bNumFormatChanged );
+ }
+ }
+}
+
+ScAttrArray::~ScAttrArray()
+{
+#if DEBUG_SC_TESTATTRARRAY
+ TestData();
+#endif
+
+ ScDocumentPool* pDocPool = rDocument.GetPool();
+ for (auto const & rEntry : mvData)
+ pDocPool->Remove(*rEntry.pPattern);
+}
+
+#if DEBUG_SC_TESTATTRARRAY
+void ScAttrArray::TestData() const
+{
+
+ sal_uInt16 nErr = 0;
+ SCSIZE nPos;
+ for (nPos=0; nPos<nCount; nPos++)
+ {
+ if (nPos > 0)
+ if (mvData[nPos].pPattern == mvData[nPos-1].pPattern || mvData[nPos].nRow <= mvData[nPos-1].nRow)
+ ++nErr;
+ if (mvData[nPos].pPattern->Which() != ATTR_PATTERN)
+ ++nErr;
+ }
+ if ( nPos && mvData[nPos-1].nRow != rDocument.MaxRow() )
+ ++nErr;
+
+ SAL_WARN_IF( nErr, "sc", nErr << " errors in attribute array, column " << nCol );
+}
+#endif
+
+void ScAttrArray::SetDefaultIfNotInit( SCSIZE nNeeded )
+{
+ if ( !mvData.empty() )
+ return;
+
+ SCSIZE nNewLimit = std::max<SCSIZE>( SC_ATTRARRAY_DELTA, nNeeded );
+ mvData.reserve( nNewLimit );
+ mvData.emplace_back();
+ mvData[0].nEndRow = rDocument.MaxRow();
+ mvData[0].pPattern = rDocument.GetDefPattern(); // no put
+}
+
+void ScAttrArray::Reset( const ScPatternAttr* pPattern )
+{
+ ScDocumentPool* pDocPool = rDocument.GetPool();
+ ScAddress aAdrStart( nCol, 0, nTab );
+ ScAddress aAdrEnd ( nCol, 0, nTab );
+
+ for (SCSIZE i=0; i<mvData.size(); i++)
+ {
+ // ensure that attributing changes text width of cell
+ const ScPatternAttr* pOldPattern = mvData[i].pPattern;
+ if ( nCol != -1 )
+ {
+ bool bNumFormatChanged;
+ if ( ScGlobal::CheckWidthInvalidate( bNumFormatChanged,
+ pPattern->GetItemSet(), pOldPattern->GetItemSet() ) )
+ {
+ aAdrStart.SetRow( i ? mvData[i-1].nEndRow+1 : 0 );
+ aAdrEnd .SetRow( mvData[i].nEndRow );
+ rDocument.InvalidateTextWidth( &aAdrStart, &aAdrEnd, bNumFormatChanged );
+ }
+ }
+ pDocPool->Remove(*pOldPattern);
+ }
+ mvData.resize(0);
+
+ rDocument.SetStreamValid(nTab, false);
+
+ mvData.resize(1);
+ const ScPatternAttr* pNewPattern = &pDocPool->Put(*pPattern);
+ mvData[0].nEndRow = rDocument.MaxRow();
+ mvData[0].pPattern = pNewPattern;
+}
+
+bool ScAttrArray::Concat(SCSIZE nPos)
+{
+ bool bRet = false;
+ if (nPos < mvData.size())
+ {
+ if (nPos > 0)
+ {
+ if (mvData[nPos - 1].pPattern == mvData[nPos].pPattern)
+ {
+ mvData[nPos - 1].nEndRow = mvData[nPos].nEndRow;
+ rDocument.GetPool()->Remove(*mvData[nPos].pPattern);
+ mvData.erase(mvData.begin() + nPos);
+ nPos--;
+ bRet = true;
+ }
+ }
+ if (nPos + 1 < mvData.size())
+ {
+ if (mvData[nPos + 1].pPattern == mvData[nPos].pPattern)
+ {
+ mvData[nPos].nEndRow = mvData[nPos + 1].nEndRow;
+ rDocument.GetPool()->Remove(*mvData[nPos].pPattern);
+ mvData.erase(mvData.begin() + nPos + 1);
+ bRet = true;
+ }
+ }
+ }
+ return bRet;
+}
+
+/*
+ * nCount is the number of runs of different attribute combinations;
+ * no attribute in a column => nCount==1, one attribute somewhere => nCount == 3
+ * (ie. one run with no attribute + one attribute + another run with no attribute)
+ * so a range of identical attributes is only one entry in ScAttrArray.
+ *
+ * Iterative implementation of Binary Search
+ * The same implementation was used inside ScMarkArray::Search().
+ */
+
+bool ScAttrArray::Search( SCROW nRow, SCSIZE& nIndex ) const
+{
+/* auto it = std::lower_bound(mvData.begin(), mvData.end(), nRow,
+ [] (const ScAttrEntry &r1, SCROW nRow)
+ { return r1.nEndRow < nRow; } );
+ if (it != mvData.end())
+ nIndex = it - mvData.begin();
+ return it != mvData.end(); */
+
+ if (mvData.size() == 1)
+ {
+ nIndex = 0;
+ return true;
+ }
+
+ tools::Long nHi = static_cast<tools::Long>(mvData.size()) - 1;
+ tools::Long i = 0;
+ tools::Long nLo = 0;
+
+ while ( nLo <= nHi )
+ {
+ i = (nLo + nHi) / 2;
+
+ if (mvData[i].nEndRow < nRow)
+ {
+ // If [nRow] greater, ignore left half
+ nLo = i + 1;
+ }
+ else if ((i > 0) && (mvData[i - 1].nEndRow >= nRow))
+ {
+ // If [nRow] is smaller, ignore right half
+ nHi = i - 1;
+ }
+ else
+ {
+ // found
+ nIndex=static_cast<SCSIZE>(i);
+ return true;
+ }
+ }
+
+ nIndex=0;
+ return false;
+}
+
+const ScPatternAttr* ScAttrArray::GetPattern( SCROW nRow ) const
+{
+ if ( mvData.empty() )
+ {
+ if ( !rDocument.ValidRow(nRow) )
+ return nullptr;
+ return rDocument.GetDefPattern();
+ }
+ SCSIZE i;
+ if (Search( nRow, i ))
+ return mvData[i].pPattern;
+ else
+ return nullptr;
+}
+
+const ScPatternAttr* ScAttrArray::GetPatternRange( SCROW& rStartRow,
+ SCROW& rEndRow, SCROW nRow ) const
+{
+ if ( mvData.empty() )
+ {
+ if ( !rDocument.ValidRow( nRow ) )
+ return nullptr;
+ rStartRow = 0;
+ rEndRow = rDocument.MaxRow();
+ return rDocument.GetDefPattern();
+ }
+ SCSIZE nIndex;
+ if ( Search( nRow, nIndex ) )
+ {
+ if ( nIndex > 0 )
+ rStartRow = mvData[nIndex-1].nEndRow + 1;
+ else
+ rStartRow = 0;
+ rEndRow = mvData[nIndex].nEndRow;
+ return mvData[nIndex].pPattern;
+ }
+ return nullptr;
+}
+
+void ScAttrArray::AddCondFormat( SCROW nStartRow, SCROW nEndRow, sal_uInt32 nIndex )
+{
+ if(!rDocument.ValidRow(nStartRow) || !rDocument.ValidRow(nEndRow))
+ return;
+
+ if(nEndRow < nStartRow)
+ return;
+
+ SCROW nTempStartRow = nStartRow;
+ SCROW nTempEndRow = nEndRow;
+
+ do
+ {
+ const ScPatternAttr* pPattern = GetPattern(nTempStartRow);
+
+ std::unique_ptr<ScPatternAttr> pNewPattern;
+ if(pPattern)
+ {
+ pNewPattern.reset( new ScPatternAttr(*pPattern) );
+ SCROW nPatternStartRow;
+ SCROW nPatternEndRow;
+ GetPatternRange( nPatternStartRow, nPatternEndRow, nTempStartRow );
+
+ nTempEndRow = std::min<SCROW>( nPatternEndRow, nEndRow );
+ if (const ScCondFormatItem* pItem = pPattern->GetItemSet().GetItemIfSet( ATTR_CONDITIONAL ))
+ {
+ ScCondFormatIndexes const & rCondFormatData = pItem->GetCondFormatData();
+ if (rCondFormatData.find(nIndex) == rCondFormatData.end())
+ {
+ ScCondFormatIndexes aNewCondFormatData;
+ aNewCondFormatData.reserve(rCondFormatData.size()+1);
+ aNewCondFormatData = rCondFormatData;
+ aNewCondFormatData.insert(nIndex);
+ ScCondFormatItem aItem( std::move(aNewCondFormatData) );
+ pNewPattern->GetItemSet().Put( aItem );
+ }
+ }
+ else
+ {
+ ScCondFormatItem aItem(nIndex);
+ pNewPattern->GetItemSet().Put( aItem );
+ }
+ }
+ else
+ {
+ pNewPattern.reset( new ScPatternAttr( rDocument.GetPool() ) );
+ ScCondFormatItem aItem(nIndex);
+ pNewPattern->GetItemSet().Put( aItem );
+ nTempEndRow = nEndRow;
+ }
+
+ SetPatternArea( nTempStartRow, nTempEndRow, std::move(pNewPattern), true );
+ nTempStartRow = nTempEndRow + 1;
+ }
+ while(nTempEndRow < nEndRow);
+
+}
+
+void ScAttrArray::RemoveCondFormat( SCROW nStartRow, SCROW nEndRow, sal_uInt32 nIndex )
+{
+ if(!rDocument.ValidRow(nStartRow) || !rDocument.ValidRow(nEndRow))
+ return;
+
+ if(nEndRow < nStartRow)
+ return;
+
+ SCROW nTempStartRow = nStartRow;
+ SCROW nTempEndRow = nEndRow;
+
+ do
+ {
+ const ScPatternAttr* pPattern = GetPattern(nTempStartRow);
+
+ if(pPattern)
+ {
+ SCROW nPatternStartRow;
+ SCROW nPatternEndRow;
+ GetPatternRange( nPatternStartRow, nPatternEndRow, nTempStartRow );
+
+ nTempEndRow = std::min<SCROW>( nPatternEndRow, nEndRow );
+ if (const ScCondFormatItem* pItem = pPattern->GetItemSet().GetItemIfSet( ATTR_CONDITIONAL ))
+ {
+ auto pPatternAttr = std::make_unique<ScPatternAttr>( *pPattern );
+ if (nIndex == 0)
+ {
+ ScCondFormatItem aItem;
+ pPatternAttr->GetItemSet().Put( aItem );
+ SetPatternArea( nTempStartRow, nTempEndRow, std::move(pPatternAttr), true );
+ }
+ else
+ {
+ ScCondFormatIndexes const & rCondFormatData = pItem->GetCondFormatData();
+ auto itr = rCondFormatData.find(nIndex);
+ if(itr != rCondFormatData.end())
+ {
+ ScCondFormatIndexes aNewCondFormatData(rCondFormatData);
+ aNewCondFormatData.erase_at(std::distance(rCondFormatData.begin(), itr));
+ ScCondFormatItem aItem( std::move(aNewCondFormatData) );
+ pPatternAttr->GetItemSet().Put( aItem );
+ SetPatternArea( nTempStartRow, nTempEndRow, std::move(pPatternAttr), true );
+ }
+ }
+ }
+ }
+ else
+ {
+ return;
+ }
+
+ nTempStartRow = nTempEndRow + 1;
+ }
+ while(nTempEndRow < nEndRow);
+
+}
+
+void ScAttrArray::RemoveCellCharAttribs( SCROW nStartRow, SCROW nEndRow,
+ const ScPatternAttr* pPattern, ScEditDataArray* pDataArray )
+{
+ assert( nCol != -1 );
+ // cache mdds position, this doesn't modify the mdds container, just EditTextObject's
+ sc::ColumnBlockPosition blockPos;
+ rDocument.InitColumnBlockPosition( blockPos, nTab, nCol );
+ for (SCROW nRow = nStartRow; nRow <= nEndRow; ++nRow)
+ {
+ ScAddress aPos(nCol, nRow, nTab);
+ ScRefCellValue aCell(rDocument, aPos, blockPos);
+ if (aCell.meType != CELLTYPE_EDIT || !aCell.mpEditText)
+ continue;
+
+ std::unique_ptr<EditTextObject> pOldData;
+ if (pDataArray)
+ pOldData = aCell.mpEditText->Clone();
+
+ // Direct modification of cell content - something to watch out for if
+ // we decide to share edit text instances in the future.
+ ScEditUtil::RemoveCharAttribs(const_cast<EditTextObject&>(*aCell.mpEditText), *pPattern);
+
+ if (pDataArray)
+ {
+ std::unique_ptr<EditTextObject> pNewData = aCell.mpEditText->Clone();
+ pDataArray->AddItem(nTab, nCol, nRow, std::move(pOldData), std::move(pNewData));
+ }
+ }
+}
+
+bool ScAttrArray::Reserve( SCSIZE nReserve )
+{
+ if ( mvData.empty() && nReserve )
+ {
+ try {
+ mvData.reserve(nReserve);
+ mvData.emplace_back();
+ mvData[0].nEndRow = rDocument.MaxRow();
+ mvData[0].pPattern = rDocument.GetDefPattern(); // no put
+ return true;
+ } catch (std::bad_alloc const &) {
+ return false;
+ }
+ }
+ else if ( mvData.capacity() < nReserve )
+ {
+ try {
+ mvData.reserve(nReserve);
+ return true;
+ } catch (std::bad_alloc const &) {
+ return false;
+ }
+ }
+ else
+ return false;
+}
+
+const ScPatternAttr* ScAttrArray::SetPatternAreaImpl(SCROW nStartRow, SCROW nEndRow, const ScPatternAttr* pPattern,
+ bool bPutToPool, ScEditDataArray* pDataArray, bool bPassingOwnership )
+{
+ if (rDocument.ValidRow(nStartRow) && rDocument.ValidRow(nEndRow))
+ {
+ if (bPutToPool)
+ {
+ if (bPassingOwnership)
+ pPattern = &rDocument.GetPool()->Put(std::unique_ptr<ScPatternAttr>(const_cast<ScPatternAttr*>(pPattern)));
+ else
+ pPattern = &rDocument.GetPool()->Put(*pPattern);
+ }
+ if ((nStartRow == 0) && (nEndRow == rDocument.MaxRow()))
+ Reset(pPattern);
+ else
+ {
+ SCSIZE nNeeded = mvData.size() + 2;
+ SetDefaultIfNotInit( nNeeded );
+
+ ScAddress aAdrStart( nCol, 0, nTab );
+ ScAddress aAdrEnd ( nCol, 0, nTab );
+
+ SCSIZE ni = 0; // number of entries in beginning
+ SCSIZE nx = 0; // track position
+ SCROW ns = 0; // start row of track position
+ if ( nStartRow > 0 )
+ {
+ // skip beginning
+ SCSIZE nIndex;
+ Search( nStartRow, nIndex );
+ ni = nIndex;
+
+ if ( ni > 0 )
+ {
+ nx = ni;
+ ns = mvData[ni-1].nEndRow+1;
+ }
+ }
+
+ // ensure that attributing changes text width of cell
+ // otherwise, conditional formats need to be reset or deleted
+ bool bIsLoading = !rDocument.GetDocumentShell() || rDocument.GetDocumentShell()->IsLoading();
+ while ( ns <= nEndRow )
+ {
+ if ( nCol != -1 && !bIsLoading )
+ {
+ const SfxItemSet& rNewSet = pPattern->GetItemSet();
+ const SfxItemSet& rOldSet = mvData[nx].pPattern->GetItemSet();
+ bool bNumFormatChanged;
+ if ( ScGlobal::CheckWidthInvalidate( bNumFormatChanged,
+ rNewSet, rOldSet ) )
+ {
+ aAdrStart.SetRow( std::max(nStartRow,ns) );
+ aAdrEnd .SetRow( std::min(nEndRow,mvData[nx].nEndRow) );
+ rDocument.InvalidateTextWidth( &aAdrStart, &aAdrEnd, bNumFormatChanged );
+ }
+ }
+ ns = mvData[nx].nEndRow + 1;
+ nx++;
+ }
+
+ // continue modifying data array
+
+ SCSIZE nInsert; // insert position (MAXROWCOUNT := no insert)
+ bool bCombined = false;
+ bool bSplit = false;
+ if ( nStartRow > 0 )
+ {
+ nInsert = rDocument.MaxRow() + 1;
+ if ( mvData[ni].pPattern != pPattern )
+ {
+ if ( ni == 0 || (mvData[ni-1].nEndRow < nStartRow - 1) )
+ { // may be a split or a simple insert or just a shrink,
+ // row adjustment is done further down
+ if ( mvData[ni].nEndRow > nEndRow )
+ bSplit = true;
+ ni++;
+ nInsert = ni;
+ }
+ else if (mvData[ni - 1].nEndRow == nStartRow - 1)
+ nInsert = ni;
+ }
+ if ( ni > 0 && mvData[ni-1].pPattern == pPattern )
+ { // combine
+ mvData[ni-1].nEndRow = nEndRow;
+ nInsert = rDocument.MaxRow() + 1;
+ bCombined = true;
+ }
+ }
+ else
+ nInsert = 0;
+
+ SCSIZE nj = ni; // stop position of range to replace
+ while ( nj < mvData.size() && mvData[nj].nEndRow <= nEndRow )
+ nj++;
+ if ( !bSplit )
+ {
+ if ( nj < mvData.size() && mvData[nj].pPattern == pPattern )
+ { // combine
+ if ( ni > 0 )
+ {
+ if ( mvData[ni-1].pPattern == pPattern )
+ { // adjacent entries
+ mvData[ni-1].nEndRow = mvData[nj].nEndRow;
+ nj++;
+ }
+ else if ( ni == nInsert )
+ mvData[ni-1].nEndRow = nStartRow - 1; // shrink
+ }
+ nInsert = rDocument.MaxRow() + 1;
+ bCombined = true;
+ }
+ else if ( ni > 0 && ni == nInsert )
+ mvData[ni-1].nEndRow = nStartRow - 1; // shrink
+ }
+ ScDocumentPool* pDocPool = rDocument.GetPool();
+ if ( bSplit )
+ { // duplicate split entry in pool
+ pDocPool->Put( *mvData[ni-1].pPattern );
+ }
+ if ( ni < nj )
+ { // remove middle entries
+ for ( SCSIZE nk=ni; nk<nj; nk++)
+ { // remove entries from pool
+ pDocPool->Remove( *mvData[nk].pPattern );
+ }
+ if ( !bCombined )
+ { // replace one entry
+ mvData[ni].nEndRow = nEndRow;
+ mvData[ni].pPattern = pPattern;
+ ni++;
+ nInsert = rDocument.MaxRow() + 1;
+ }
+ if ( ni < nj )
+ { // remove entries
+ mvData.erase( mvData.begin() + ni, mvData.begin() + nj);
+ }
+ }
+
+ if ( nInsert < sal::static_int_cast<SCSIZE>(rDocument.MaxRow() + 1) )
+ { // insert or append new entry
+ if ( nInsert <= mvData.size() )
+ {
+ if ( !bSplit )
+ mvData.emplace(mvData.begin() + nInsert);
+ else
+ {
+ mvData.insert(mvData.begin() + nInsert, 2, ScAttrEntry());
+ mvData[nInsert+1] = mvData[nInsert-1];
+ }
+ }
+ if ( nInsert )
+ mvData[nInsert-1].nEndRow = nStartRow - 1;
+ mvData[nInsert].nEndRow = nEndRow;
+ mvData[nInsert].pPattern = pPattern;
+
+ // Remove character attributes from these cells if the pattern
+ // is applied during normal session.
+ if (pDataArray && nCol != -1)
+ RemoveCellCharAttribs(nStartRow, nEndRow, pPattern, pDataArray);
+ }
+
+ rDocument.SetStreamValid(nTab, false);
+ }
+ }
+
+#if DEBUG_SC_TESTATTRARRAY
+ TestData();
+#endif
+ return pPattern;
+}
+
+void ScAttrArray::ApplyStyleArea( SCROW nStartRow, SCROW nEndRow, const ScStyleSheet& rStyle )
+{
+ if (!(rDocument.ValidRow(nStartRow) && rDocument.ValidRow(nEndRow)))
+ return;
+
+ SetDefaultIfNotInit();
+ SCSIZE nPos;
+ SCROW nStart=0;
+ if (!Search( nStartRow, nPos ))
+ {
+ OSL_FAIL("Search Failure");
+ return;
+ }
+
+ ScAddress aAdrStart( nCol, 0, nTab );
+ ScAddress aAdrEnd ( nCol, 0, nTab );
+
+ do
+ {
+ const ScPatternAttr* pOldPattern = mvData[nPos].pPattern;
+ std::unique_ptr<ScPatternAttr> pNewPattern(new ScPatternAttr(*pOldPattern));
+ pNewPattern->SetStyleSheet(const_cast<ScStyleSheet*>(&rStyle));
+ SCROW nY1 = nStart;
+ SCROW nY2 = mvData[nPos].nEndRow;
+ nStart = mvData[nPos].nEndRow + 1;
+
+ if ( *pNewPattern == *pOldPattern )
+ {
+ // keep the original pattern (might be default)
+ // pNewPattern is deleted below
+ nPos++;
+ }
+ else if ( nY1 < nStartRow || nY2 > nEndRow )
+ {
+ if (nY1 < nStartRow) nY1=nStartRow;
+ if (nY2 > nEndRow) nY2=nEndRow;
+ SetPatternArea( nY1, nY2, std::move(pNewPattern), true );
+ Search( nStart, nPos );
+ }
+ else
+ {
+ if ( nCol != -1 )
+ {
+ // ensure attributing changes text width of cell; otherwise
+ // there aren't (yet) template format changes
+ const SfxItemSet& rNewSet = pNewPattern->GetItemSet();
+ const SfxItemSet& rOldSet = pOldPattern->GetItemSet();
+
+ bool bNumFormatChanged;
+ if ( ScGlobal::CheckWidthInvalidate( bNumFormatChanged,
+ rNewSet, rOldSet ) )
+ {
+ aAdrStart.SetRow( nPos ? mvData[nPos-1].nEndRow+1 : 0 );
+ aAdrEnd .SetRow( mvData[nPos].nEndRow );
+ rDocument.InvalidateTextWidth( &aAdrStart, &aAdrEnd, bNumFormatChanged );
+ }
+ }
+
+ rDocument.GetPool()->Remove(*mvData[nPos].pPattern);
+ mvData[nPos].pPattern = &rDocument.GetPool()->Put(*pNewPattern);
+ if (Concat(nPos))
+ Search(nStart, nPos);
+ else
+ nPos++;
+ }
+ }
+ while ((nStart <= nEndRow) && (nPos < mvData.size()));
+
+ rDocument.SetStreamValid(nTab, false);
+
+#if DEBUG_SC_TESTATTRARRAY
+ TestData();
+#endif
+}
+
+ // const cast, otherwise it will be too inefficient/complicated
+static void SetLineColor(SvxBorderLine const * dest, Color c)
+{
+ if (dest)
+ {
+ const_cast<SvxBorderLine*>(dest)->SetColor(c);
+ }
+}
+
+static void SetLine(const SvxBorderLine* dest, const SvxBorderLine* src)
+{
+ if (dest)
+ {
+ SvxBorderLine* pCast = const_cast<SvxBorderLine*>(dest);
+ pCast->SetBorderLineStyle( src->GetBorderLineStyle() );
+ pCast->SetWidth( src->GetWidth() );
+ }
+}
+
+void ScAttrArray::ApplyLineStyleArea( SCROW nStartRow, SCROW nEndRow,
+ const SvxBorderLine* pLine, bool bColorOnly )
+{
+ if ( bColorOnly && !pLine )
+ return;
+
+ if (!(rDocument.ValidRow(nStartRow) && rDocument.ValidRow(nEndRow)))
+ return;
+
+ SCSIZE nPos;
+ SCROW nStart=0;
+ SetDefaultIfNotInit();
+ if (!Search( nStartRow, nPos ))
+ {
+ OSL_FAIL("Search failure");
+ return;
+ }
+
+ do
+ {
+ const ScPatternAttr* pOldPattern = mvData[nPos].pPattern;
+ const SfxItemSet& rOldSet = pOldPattern->GetItemSet();
+ const SvxBoxItem* pBoxItem = rOldSet.GetItemIfSet( ATTR_BORDER );
+ const SvxLineItem* pTLBRItem = rOldSet.GetItemIfSet( ATTR_BORDER_TLBR );
+ const SvxLineItem* pBLTRItem = rOldSet.GetItemIfSet( ATTR_BORDER_BLTR );
+
+ if ( pBoxItem || pTLBRItem || pBLTRItem )
+ {
+ std::unique_ptr<ScPatternAttr> pNewPattern(new ScPatternAttr(*pOldPattern));
+ SfxItemSet& rNewSet = pNewPattern->GetItemSet();
+ SCROW nY1 = nStart;
+ SCROW nY2 = mvData[nPos].nEndRow;
+
+ std::unique_ptr<SvxBoxItem> pNewBoxItem( pBoxItem ? pBoxItem->Clone() : nullptr);
+ std::unique_ptr<SvxLineItem> pNewTLBRItem( pTLBRItem ? pTLBRItem->Clone() : nullptr);
+ std::unique_ptr<SvxLineItem> pNewBLTRItem(pBLTRItem ? pBLTRItem->Clone() : nullptr);
+
+ // fetch line and update attributes with parameters
+
+ if ( !pLine )
+ {
+ if( pNewBoxItem )
+ {
+ if ( pNewBoxItem->GetTop() ) pNewBoxItem->SetLine( nullptr, SvxBoxItemLine::TOP );
+ if ( pNewBoxItem->GetBottom() ) pNewBoxItem->SetLine( nullptr, SvxBoxItemLine::BOTTOM );
+ if ( pNewBoxItem->GetLeft() ) pNewBoxItem->SetLine( nullptr, SvxBoxItemLine::LEFT );
+ if ( pNewBoxItem->GetRight() ) pNewBoxItem->SetLine( nullptr, SvxBoxItemLine::RIGHT );
+ }
+ if( pNewTLBRItem && pNewTLBRItem->GetLine() )
+ pNewTLBRItem->SetLine( nullptr );
+ if( pNewBLTRItem && pNewBLTRItem->GetLine() )
+ pNewBLTRItem->SetLine( nullptr );
+ }
+ else
+ {
+ if ( bColorOnly )
+ {
+ Color aColor( pLine->GetColor() );
+ if( pNewBoxItem )
+ {
+ SetLineColor( pNewBoxItem->GetTop(), aColor );
+ SetLineColor( pNewBoxItem->GetBottom(), aColor );
+ SetLineColor( pNewBoxItem->GetLeft(), aColor );
+ SetLineColor( pNewBoxItem->GetRight(), aColor );
+ }
+ if( pNewTLBRItem )
+ SetLineColor( pNewTLBRItem->GetLine(), aColor );
+ if( pNewBLTRItem )
+ SetLineColor( pNewBLTRItem->GetLine(), aColor );
+ }
+ else
+ {
+ if( pNewBoxItem )
+ {
+ SetLine( pNewBoxItem->GetTop(), pLine );
+ SetLine( pNewBoxItem->GetBottom(), pLine );
+ SetLine( pNewBoxItem->GetLeft(), pLine );
+ SetLine( pNewBoxItem->GetRight(), pLine );
+ }
+ if( pNewTLBRItem )
+ SetLine( pNewTLBRItem->GetLine(), pLine );
+ if( pNewBLTRItem )
+ SetLine( pNewBLTRItem->GetLine(), pLine );
+ }
+ }
+ if( pNewBoxItem ) rNewSet.Put( std::move(pNewBoxItem) );
+ if( pNewTLBRItem ) rNewSet.Put( std::move(pNewTLBRItem) );
+ if( pNewBLTRItem ) rNewSet.Put( std::move(pNewBLTRItem) );
+
+ nStart = mvData[nPos].nEndRow + 1;
+
+ if ( nY1 < nStartRow || nY2 > nEndRow )
+ {
+ if (nY1 < nStartRow) nY1=nStartRow;
+ if (nY2 > nEndRow) nY2=nEndRow;
+ SetPatternArea( nY1, nY2, std::move(pNewPattern), true );
+ Search( nStart, nPos );
+ }
+ else
+ {
+ // remove from pool ?
+ rDocument.GetPool()->Remove(*mvData[nPos].pPattern);
+ mvData[nPos].pPattern =
+ &rDocument.GetPool()->Put(std::move(pNewPattern));
+
+ if (Concat(nPos))
+ Search(nStart, nPos);
+ else
+ nPos++;
+ }
+ }
+ else
+ {
+ nStart = mvData[nPos].nEndRow + 1;
+ nPos++;
+ }
+ }
+ while ((nStart <= nEndRow) && (nPos < mvData.size()));
+}
+
+void ScAttrArray::ApplyCacheArea( SCROW nStartRow, SCROW nEndRow, SfxItemPoolCache* pCache, ScEditDataArray* pDataArray, bool* const pIsChanged )
+{
+#if DEBUG_SC_TESTATTRARRAY
+ TestData();
+#endif
+
+ if (!(rDocument.ValidRow(nStartRow) && rDocument.ValidRow(nEndRow)))
+ return;
+
+ SCSIZE nPos;
+ SCROW nStart=0;
+ SetDefaultIfNotInit();
+ if (!Search( nStartRow, nPos ))
+ {
+ OSL_FAIL("Search Failure");
+ return;
+ }
+
+ ScAddress aAdrStart( nCol, 0, nTab );
+ ScAddress aAdrEnd ( nCol, 0, nTab );
+
+ do
+ {
+ const ScPatternAttr* pOldPattern = mvData[nPos].pPattern;
+ const ScPatternAttr* pNewPattern = static_cast<const ScPatternAttr*>( &pCache->ApplyTo( *pOldPattern ) );
+ if (pNewPattern != pOldPattern)
+ {
+ SCROW nY1 = nStart;
+ SCROW nY2 = mvData[nPos].nEndRow;
+ nStart = mvData[nPos].nEndRow + 1;
+
+ if(pIsChanged)
+ *pIsChanged = true;
+
+ if ( nY1 < nStartRow || nY2 > nEndRow )
+ {
+ if (nY1 < nStartRow) nY1=nStartRow;
+ if (nY2 > nEndRow) nY2=nEndRow;
+ SetPatternArea( nY1, nY2, pNewPattern, false, pDataArray );
+ Search( nStart, nPos );
+ }
+ else
+ {
+ if ( nCol != -1 )
+ {
+ // ensure attributing changes text-width of cell
+
+ const SfxItemSet& rNewSet = pNewPattern->GetItemSet();
+ const SfxItemSet& rOldSet = pOldPattern->GetItemSet();
+
+ bool bNumFormatChanged;
+ if ( ScGlobal::CheckWidthInvalidate( bNumFormatChanged,
+ rNewSet, rOldSet ) )
+ {
+ aAdrStart.SetRow( nPos ? mvData[nPos-1].nEndRow+1 : 0 );
+ aAdrEnd .SetRow( mvData[nPos].nEndRow );
+ rDocument.InvalidateTextWidth( &aAdrStart, &aAdrEnd, bNumFormatChanged );
+ }
+ }
+
+ rDocument.GetPool()->Remove(*mvData[nPos].pPattern);
+ mvData[nPos].pPattern = pNewPattern;
+ if (Concat(nPos))
+ Search(nStart, nPos);
+ else
+ ++nPos;
+ }
+ }
+ else
+ {
+ nStart = mvData[nPos].nEndRow + 1;
+ ++nPos;
+ }
+ }
+ while (nStart <= nEndRow);
+
+ rDocument.SetStreamValid(nTab, false);
+
+#if DEBUG_SC_TESTATTRARRAY
+ TestData();
+#endif
+}
+
+void ScAttrArray::SetAttrEntries(std::vector<ScAttrEntry> && vNewData)
+{
+ ScDocumentPool* pDocPool = rDocument.GetPool();
+ for (auto const & rEntry : mvData)
+ pDocPool->Remove(*rEntry.pPattern);
+
+ mvData = std::move(vNewData);
+
+#ifdef DBG_UTIL
+ SCROW lastEndRow = -1;
+ for(const auto& entry : mvData)
+ { // Verify that the data is not corrupted.
+ assert(entry.nEndRow > lastEndRow);
+ lastEndRow = entry.nEndRow;
+ }
+#endif
+}
+
+static void lcl_MergeDeep( SfxItemSet& rMergeSet, const SfxItemSet& rSource )
+{
+ const SfxPoolItem* pNewItem;
+ const SfxPoolItem* pOldItem;
+ for (sal_uInt16 nId=ATTR_PATTERN_START; nId<=ATTR_PATTERN_END; nId++)
+ {
+ // pMergeSet has no parent
+ SfxItemState eOldState = rMergeSet.GetItemState( nId, false, &pOldItem );
+
+ if ( eOldState == SfxItemState::DEFAULT )
+ {
+ SfxItemState eNewState = rSource.GetItemState( nId, true, &pNewItem );
+ if ( eNewState == SfxItemState::SET )
+ {
+ if ( *pNewItem != rMergeSet.GetPool()->GetDefaultItem(nId) )
+ rMergeSet.InvalidateItem( nId );
+ }
+ }
+ else if ( eOldState == SfxItemState::SET ) // Item set
+ {
+ SfxItemState eNewState = rSource.GetItemState( nId, true, &pNewItem );
+ if ( eNewState == SfxItemState::SET )
+ {
+ if ( pNewItem != pOldItem ) // Both pulled
+ rMergeSet.InvalidateItem( nId );
+ }
+ else // Default
+ {
+ if ( *pOldItem != rSource.GetPool()->GetDefaultItem(nId) )
+ rMergeSet.InvalidateItem( nId );
+ }
+ }
+ // Dontcare remains Dontcare
+ }
+}
+
+void ScAttrArray::MergePatternArea( SCROW nStartRow, SCROW nEndRow,
+ ScMergePatternState& rState, bool bDeep ) const
+{
+ if (!(rDocument.ValidRow(nStartRow) && rDocument.ValidRow(nEndRow)))
+ return;
+
+ SCSIZE nPos = 0;
+ SCROW nStart=0;
+ if ( !mvData.empty() && !Search( nStartRow, nPos ) )
+ {
+ OSL_FAIL("Search failure");
+ return;
+ }
+
+ do
+ {
+ // similar patterns must not be repeated
+ const ScPatternAttr* pPattern = nullptr;
+ if ( !mvData.empty() )
+ pPattern = mvData[nPos].pPattern;
+ else
+ pPattern = rDocument.GetDefPattern();
+ if ( pPattern != rState.pOld1 && pPattern != rState.pOld2 )
+ {
+ const SfxItemSet& rThisSet = pPattern->GetItemSet();
+ if (rState.pItemSet)
+ {
+ rState.mbValidPatternId = false;
+ if (bDeep)
+ lcl_MergeDeep( *rState.pItemSet, rThisSet );
+ else
+ rState.pItemSet->MergeValues( rThisSet );
+ }
+ else
+ {
+ // first pattern - copied from parent
+ rState.pItemSet.emplace( *rThisSet.GetPool(), rThisSet.GetRanges() );
+ rState.pItemSet->Set( rThisSet, bDeep );
+ rState.mnPatternId = pPattern->GetKey();
+ }
+
+ rState.pOld2 = rState.pOld1;
+ rState.pOld1 = pPattern;
+ }
+
+ if ( !mvData.empty() )
+ nStart = mvData[nPos].nEndRow + 1;
+ else
+ nStart = rDocument.MaxRow() + 1;
+ ++nPos;
+ }
+ while (nStart <= nEndRow);
+}
+
+// assemble border
+
+static bool lcl_TestAttr( const SvxBorderLine* pOldLine, const SvxBorderLine* pNewLine,
+ sal_uInt8& rModified, const SvxBorderLine*& rpNew )
+{
+ if (rModified == SC_LINE_DONTCARE)
+ return false; // don't go again
+
+ if (rModified == SC_LINE_EMPTY)
+ {
+ rModified = SC_LINE_SET;
+ rpNew = pNewLine;
+ return true; // initial value
+ }
+
+ if (pOldLine == pNewLine)
+ {
+ rpNew = pOldLine;
+ return false;
+ }
+
+ if (pOldLine && pNewLine)
+ if (*pOldLine == *pNewLine)
+ {
+ rpNew = pOldLine;
+ return false;
+ }
+
+ rModified = SC_LINE_DONTCARE;
+ rpNew = nullptr;
+ return true; // another line -> don't care
+}
+
+static void lcl_MergeToFrame( SvxBoxItem* pLineOuter, SvxBoxInfoItem* pLineInner,
+ ScLineFlags& rFlags, const ScPatternAttr* pPattern,
+ bool bLeft, SCCOL nDistRight, bool bTop, SCROW nDistBottom )
+{
+ // right/bottom border set when connected together
+ const ScMergeAttr& rMerge = pPattern->GetItem(ATTR_MERGE);
+ if ( rMerge.GetColMerge() == nDistRight + 1 )
+ nDistRight = 0;
+ if ( rMerge.GetRowMerge() == nDistBottom + 1 )
+ nDistBottom = 0;
+
+ const SvxBoxItem* pCellFrame = &pPattern->GetItemSet().Get( ATTR_BORDER );
+ const SvxBorderLine* pLeftAttr = pCellFrame->GetLeft();
+ const SvxBorderLine* pRightAttr = pCellFrame->GetRight();
+ const SvxBorderLine* pTopAttr = pCellFrame->GetTop();
+ const SvxBorderLine* pBottomAttr = pCellFrame->GetBottom();
+ const SvxBorderLine* pNew;
+
+ if (bTop)
+ {
+ if (lcl_TestAttr( pLineOuter->GetTop(), pTopAttr, rFlags.nTop, pNew ))
+ pLineOuter->SetLine( pNew, SvxBoxItemLine::TOP );
+ }
+ else
+ {
+ if (lcl_TestAttr( pLineInner->GetHori(), pTopAttr, rFlags.nHori, pNew ))
+ pLineInner->SetLine( pNew, SvxBoxInfoItemLine::HORI );
+ }
+
+ if (nDistBottom == 0)
+ {
+ if (lcl_TestAttr( pLineOuter->GetBottom(), pBottomAttr, rFlags.nBottom, pNew ))
+ pLineOuter->SetLine( pNew, SvxBoxItemLine::BOTTOM );
+ }
+ else
+ {
+ if (lcl_TestAttr( pLineInner->GetHori(), pBottomAttr, rFlags.nHori, pNew ))
+ pLineInner->SetLine( pNew, SvxBoxInfoItemLine::HORI );
+ }
+
+ if (bLeft)
+ {
+ if (lcl_TestAttr( pLineOuter->GetLeft(), pLeftAttr, rFlags.nLeft, pNew ))
+ pLineOuter->SetLine( pNew, SvxBoxItemLine::LEFT );
+ }
+ else
+ {
+ if (lcl_TestAttr( pLineInner->GetVert(), pLeftAttr, rFlags.nVert, pNew ))
+ pLineInner->SetLine( pNew, SvxBoxInfoItemLine::VERT );
+ }
+
+ if (nDistRight == 0)
+ {
+ if (lcl_TestAttr( pLineOuter->GetRight(), pRightAttr, rFlags.nRight, pNew ))
+ pLineOuter->SetLine( pNew, SvxBoxItemLine::RIGHT );
+ }
+ else
+ {
+ if (lcl_TestAttr( pLineInner->GetVert(), pRightAttr, rFlags.nVert, pNew ))
+ pLineInner->SetLine( pNew, SvxBoxInfoItemLine::VERT );
+ }
+}
+
+void ScAttrArray::MergeBlockFrame( SvxBoxItem* pLineOuter, SvxBoxInfoItem* pLineInner,
+ ScLineFlags& rFlags,
+ SCROW nStartRow, SCROW nEndRow, bool bLeft, SCCOL nDistRight ) const
+{
+ const ScPatternAttr* pPattern;
+
+ if (nStartRow == nEndRow)
+ {
+ pPattern = GetPattern( nStartRow );
+ lcl_MergeToFrame( pLineOuter, pLineInner, rFlags, pPattern, bLeft, nDistRight, true, 0 );
+ }
+ else if ( !mvData.empty() ) // non-default pattern
+ {
+ pPattern = GetPattern( nStartRow );
+ lcl_MergeToFrame( pLineOuter, pLineInner, rFlags, pPattern, bLeft, nDistRight, true,
+ nEndRow-nStartRow );
+
+ SCSIZE nStartIndex;
+ SCSIZE nEndIndex;
+ Search( nStartRow+1, nStartIndex );
+ Search( nEndRow-1, nEndIndex );
+ for (SCSIZE i=nStartIndex; i<=nEndIndex; i++)
+ {
+ pPattern = mvData[i].pPattern;
+ lcl_MergeToFrame( pLineOuter, pLineInner, rFlags, pPattern, bLeft, nDistRight, false,
+ nEndRow - std::min( mvData[i].nEndRow, static_cast<SCROW>(nEndRow-1) ) );
+ // nDistBottom here always > 0
+ }
+
+ pPattern = GetPattern( nEndRow );
+ lcl_MergeToFrame( pLineOuter, pLineInner, rFlags, pPattern, bLeft, nDistRight, false, 0 );
+ }
+ else
+ {
+ lcl_MergeToFrame( pLineOuter, pLineInner, rFlags, rDocument.GetDefPattern(), bLeft, nDistRight, true, 0 );
+ }
+}
+
+// apply border
+
+// ApplyFrame - on an entry into the array
+
+bool ScAttrArray::ApplyFrame( const SvxBoxItem& rBoxItem,
+ const SvxBoxInfoItem* pBoxInfoItem,
+ SCROW nStartRow, SCROW nEndRow,
+ bool bLeft, SCCOL nDistRight, bool bTop, SCROW nDistBottom )
+{
+ OSL_ENSURE( pBoxInfoItem, "Missing line attributes!" );
+
+ const ScPatternAttr* pPattern = GetPattern( nStartRow );
+ const SvxBoxItem* pOldFrame = &pPattern->GetItemSet().Get( ATTR_BORDER );
+
+ // right/bottom border set when connected together
+ const ScMergeAttr& rMerge = pPattern->GetItem(ATTR_MERGE);
+ if ( rMerge.GetColMerge() == nDistRight + 1 )
+ nDistRight = 0;
+ if ( rMerge.GetRowMerge() == nDistBottom + 1 )
+ nDistBottom = 0;
+
+ SvxBoxItem aNewFrame( *pOldFrame );
+ bool bRTL=rDocument.IsLayoutRTL(nTab);
+ // fdo#37464 check if the sheet are RTL then replace right <=> left
+ if (bRTL)
+ {
+ if( bLeft && nDistRight==0)
+ {
+ if ( pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::LEFT) )
+ aNewFrame.SetLine( rBoxItem.GetLeft(), SvxBoxItemLine::RIGHT );
+ if ( pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::RIGHT) )
+ aNewFrame.SetLine( rBoxItem.GetRight(), SvxBoxItemLine::LEFT );
+ }
+ else
+ {
+ if ( (nDistRight==0) ? pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::LEFT) : pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::VERT) )
+ aNewFrame.SetLine( (nDistRight==0) ? rBoxItem.GetLeft() : pBoxInfoItem->GetVert(),
+ SvxBoxItemLine::RIGHT );
+ if ( bLeft ? pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::RIGHT) : pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::VERT) )
+ aNewFrame.SetLine( bLeft ? rBoxItem.GetRight() : pBoxInfoItem->GetVert(),
+ SvxBoxItemLine::LEFT );
+ }
+ }
+ else
+ {
+ if ( bLeft ? pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::LEFT) : pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::VERT) )
+ aNewFrame.SetLine( bLeft ? rBoxItem.GetLeft() : pBoxInfoItem->GetVert(),
+ SvxBoxItemLine::LEFT );
+ if ( (nDistRight==0) ? pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::RIGHT) : pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::VERT) )
+ aNewFrame.SetLine( (nDistRight==0) ? rBoxItem.GetRight() : pBoxInfoItem->GetVert(),
+ SvxBoxItemLine::RIGHT );
+ }
+ if ( bTop ? pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::TOP) : pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::HORI) )
+ aNewFrame.SetLine( bTop ? rBoxItem.GetTop() : pBoxInfoItem->GetHori(),
+ SvxBoxItemLine::TOP );
+ if ( (nDistBottom==0) ? pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::BOTTOM) : pBoxInfoItem->IsValid(SvxBoxInfoItemValidFlags::HORI) )
+ aNewFrame.SetLine( (nDistBottom==0) ? rBoxItem.GetBottom() : pBoxInfoItem->GetHori(),
+ SvxBoxItemLine::BOTTOM );
+
+ if (aNewFrame == *pOldFrame)
+ {
+ // nothing to do
+ return false;
+ }
+ else
+ {
+ SfxItemPoolCache aCache( rDocument.GetPool(), &aNewFrame );
+ ApplyCacheArea( nStartRow, nEndRow, &aCache );
+
+ return true;
+ }
+}
+
+void ScAttrArray::ApplyBlockFrame(const SvxBoxItem& rLineOuter, const SvxBoxInfoItem* pLineInner,
+ SCROW nStartRow, SCROW nEndRow, bool bLeft, SCCOL nDistRight)
+{
+ SetDefaultIfNotInit();
+ if (nStartRow == nEndRow)
+ ApplyFrame(rLineOuter, pLineInner, nStartRow, nEndRow, bLeft, nDistRight, true, 0);
+ else
+ {
+ ApplyFrame(rLineOuter, pLineInner, nStartRow, nStartRow, bLeft, nDistRight,
+ true, nEndRow-nStartRow);
+
+ if ( nEndRow > nStartRow+1 ) // inner part available?
+ {
+ SCSIZE nStartIndex;
+ SCSIZE nEndIndex;
+ Search( nStartRow+1, nStartIndex );
+ Search( nEndRow-1, nEndIndex );
+ SCROW nTmpStart = nStartRow+1;
+ SCROW nTmpEnd;
+ for (SCSIZE i=nStartIndex; i<=nEndIndex;)
+ {
+ nTmpEnd = std::min( static_cast<SCROW>(nEndRow-1), mvData[i].nEndRow );
+ bool bChanged = ApplyFrame(rLineOuter, pLineInner, nTmpStart, nTmpEnd,
+ bLeft, nDistRight, false, nEndRow - nTmpEnd);
+ nTmpStart = nTmpEnd+1;
+ if (bChanged)
+ {
+ Search(nTmpStart, i);
+ Search(nEndRow-1, nEndIndex);
+ }
+ else
+ i++;
+ }
+ }
+
+ ApplyFrame(rLineOuter, pLineInner, nEndRow, nEndRow, bLeft, nDistRight, false, 0);
+ }
+}
+
+bool ScAttrArray::HasAttrib_Impl(const ScPatternAttr* pPattern, HasAttrFlags nMask, SCROW nRow1, SCROW nRow2, SCSIZE i) const
+{
+ bool bFound = false;
+ if ( nMask & HasAttrFlags::Merged )
+ {
+ const ScMergeAttr* pMerge = &pPattern->GetItem( ATTR_MERGE );
+ if ( pMerge->GetColMerge() > 1 || pMerge->GetRowMerge() > 1 )
+ bFound = true;
+ }
+ if ( nMask & ( HasAttrFlags::Overlapped | HasAttrFlags::NotOverlapped | HasAttrFlags::AutoFilter ) )
+ {
+ const ScMergeFlagAttr* pMergeFlag = &pPattern->GetItem( ATTR_MERGE_FLAG );
+ if ( (nMask & HasAttrFlags::Overlapped) && pMergeFlag->IsOverlapped() )
+ bFound = true;
+ if ( (nMask & HasAttrFlags::NotOverlapped) && !pMergeFlag->IsOverlapped() )
+ bFound = true;
+ if ( (nMask & HasAttrFlags::AutoFilter) && pMergeFlag->HasAutoFilter() )
+ bFound = true;
+ }
+ if ( nMask & HasAttrFlags::Lines )
+ {
+ const SvxBoxItem* pBox = &pPattern->GetItem( ATTR_BORDER );
+ if ( pBox->GetLeft() || pBox->GetRight() || pBox->GetTop() || pBox->GetBottom() )
+ bFound = true;
+ }
+ if ( nMask & HasAttrFlags::Shadow )
+ {
+ const SvxShadowItem* pShadow = &pPattern->GetItem( ATTR_SHADOW );
+ if ( pShadow->GetLocation() != SvxShadowLocation::NONE )
+ bFound = true;
+ }
+ if ( nMask & HasAttrFlags::Conditional )
+ {
+ if ( !pPattern->GetItem( ATTR_CONDITIONAL ).GetCondFormatData().empty())
+ bFound = true;
+ }
+ if ( nMask & HasAttrFlags::Protected )
+ {
+ const ScProtectionAttr* pProtect = &pPattern->GetItem( ATTR_PROTECTION );
+ bool bFoundTemp = false;
+ if ( pProtect->GetProtection() || pProtect->GetHideCell() )
+ bFoundTemp = true;
+
+ bool bContainsCondFormat = !mvData.empty() &&
+ !pPattern->GetItem( ATTR_CONDITIONAL ).GetCondFormatData().empty();
+ if ( bContainsCondFormat && nCol != -1 ) // rDocument.GetCondResult() is valid only for real columns.
+ {
+ SCROW nRowStartCond = std::max<SCROW>( nRow1, i ? mvData[i-1].nEndRow + 1: 0 );
+ SCROW nRowEndCond = std::min<SCROW>( nRow2, mvData[i].nEndRow );
+ bool bFoundCond = false;
+ for(SCROW nRowCond = nRowStartCond; nRowCond <= nRowEndCond && !bFoundCond; ++nRowCond)
+ {
+ const SfxItemSet* pSet = rDocument.GetCondResult( nCol, nRowCond, nTab );
+
+ const ScProtectionAttr* pCondProtect;
+ if( pSet && (pCondProtect = pSet->GetItemIfSet( ATTR_PROTECTION )) )
+ {
+ if( pCondProtect->GetProtection() || pCondProtect->GetHideCell() )
+ bFoundCond = true;
+ else
+ break;
+ }
+ else
+ {
+ // well it is not true that we found one
+ // but existing one + cell where conditional
+ // formatting does not remove it
+ // => we should use the existing protection setting
+ bFoundCond = bFoundTemp;
+ }
+ }
+ bFoundTemp = bFoundCond;
+ }
+
+ if(bFoundTemp)
+ bFound = true;
+ }
+ if ( nMask & HasAttrFlags::Rotate )
+ {
+ const ScRotateValueItem* pRotate = &pPattern->GetItem( ATTR_ROTATE_VALUE );
+ // 90 or 270 degrees is former SvxOrientationItem - only look for other values
+ // (see ScPatternAttr::GetCellOrientation)
+ Degree100 nAngle = pRotate->GetValue();
+ if ( nAngle && nAngle != 9000_deg100 && nAngle != 27000_deg100 )
+ bFound = true;
+ }
+ if ( nMask & HasAttrFlags::NeedHeight )
+ {
+ if (pPattern->GetCellOrientation() != SvxCellOrientation::Standard)
+ bFound = true;
+ else if (pPattern->GetItem( ATTR_LINEBREAK ).GetValue())
+ bFound = true;
+ else if (pPattern->GetItem( ATTR_HOR_JUSTIFY ).GetValue() == SvxCellHorJustify::Block)
+ bFound = true;
+
+ else if (!pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData().empty())
+ bFound = true;
+ else if (pPattern->GetItem( ATTR_ROTATE_VALUE ).GetValue())
+ bFound = true;
+ }
+ if ( nMask & ( HasAttrFlags::ShadowRight | HasAttrFlags::ShadowDown ) )
+ {
+ const SvxShadowItem* pShadow = &pPattern->GetItem( ATTR_SHADOW );
+ SvxShadowLocation eLoc = pShadow->GetLocation();
+ if ( nMask & HasAttrFlags::ShadowRight )
+ if ( eLoc == SvxShadowLocation::TopRight || eLoc == SvxShadowLocation::BottomRight )
+ bFound = true;
+ if ( nMask & HasAttrFlags::ShadowDown )
+ if ( eLoc == SvxShadowLocation::BottomLeft || eLoc == SvxShadowLocation::BottomRight )
+ bFound = true;
+ }
+ if ( nMask & HasAttrFlags::RightOrCenter )
+ {
+ // called only if the sheet is LTR, so physical=logical alignment can be assumed
+ SvxCellHorJustify eHorJust = pPattern->GetItem( ATTR_HOR_JUSTIFY ).GetValue();
+ if ( eHorJust == SvxCellHorJustify::Right || eHorJust == SvxCellHorJustify::Center )
+ bFound = true;
+ }
+
+ return bFound;
+}
+
+// Test if field contains specific attribute
+bool ScAttrArray::HasAttrib( SCROW nRow1, SCROW nRow2, HasAttrFlags nMask ) const
+{
+ if (mvData.empty())
+ {
+ return HasAttrib_Impl(rDocument.GetDefPattern(), nMask, 0, rDocument.MaxRow(), 0);
+ }
+
+ SCSIZE nStartIndex;
+ SCSIZE nEndIndex;
+ Search( nRow1, nStartIndex );
+ if (nRow1 != nRow2)
+ Search( nRow2, nEndIndex );
+ else
+ nEndIndex = nStartIndex;
+ bool bFound = false;
+
+ for (SCSIZE i=nStartIndex; i<=nEndIndex && !bFound; i++)
+ {
+ const ScPatternAttr* pPattern = mvData[i].pPattern;
+ bFound = HasAttrib_Impl(pPattern, nMask, nRow1, nRow2, i);
+ }
+
+ return bFound;
+}
+
+bool ScAttrArray::HasAttrib( SCROW nRow, HasAttrFlags nMask, SCROW* nStartRow, SCROW* nEndRow ) const
+{
+ if (mvData.empty())
+ {
+ if( nStartRow )
+ *nStartRow = 0;
+ if( nEndRow )
+ *nEndRow = rDocument.MaxRow();
+ return HasAttrib_Impl(rDocument.GetDefPattern(), nMask, 0, rDocument.MaxRow(), 0);
+ }
+
+ SCSIZE nIndex;
+ Search( nRow, nIndex );
+ if( nStartRow )
+ *nStartRow = nIndex > 0 ? mvData[nIndex-1].nEndRow+1 : 0;
+ if( nEndRow )
+ *nEndRow = mvData[nIndex].nEndRow;
+ const ScPatternAttr* pPattern = mvData[nIndex].pPattern;
+ return HasAttrib_Impl(pPattern, nMask, nRow, nRow, nIndex);
+}
+
+bool ScAttrArray::IsMerged( SCROW nRow ) const
+{
+ if ( !mvData.empty() )
+ {
+ SCSIZE nIndex;
+ Search(nRow, nIndex);
+ const ScMergeAttr& rItem = mvData[nIndex].pPattern->GetItem(ATTR_MERGE);
+
+ return rItem.IsMerged();
+ }
+
+ return rDocument.GetDefPattern()->GetItem(ATTR_MERGE).IsMerged();
+}
+
+/**
+ * Area around any given summaries expand and adapt any MergeFlag (bRefresh)
+ */
+bool ScAttrArray::ExtendMerge( SCCOL nThisCol, SCROW nStartRow, SCROW nEndRow,
+ SCCOL& rPaintCol, SCROW& rPaintRow,
+ bool bRefresh )
+{
+ assert( nCol != -1 );
+ SetDefaultIfNotInit();
+ const ScPatternAttr* pPattern;
+ const ScMergeAttr* pItem;
+ SCSIZE nStartIndex;
+ SCSIZE nEndIndex;
+ Search( nStartRow, nStartIndex );
+ Search( nEndRow, nEndIndex );
+ bool bFound = false;
+
+ for (SCSIZE i=nStartIndex; i<=nEndIndex; i++)
+ {
+ pPattern = mvData[i].pPattern;
+ pItem = &pPattern->GetItem( ATTR_MERGE );
+ SCCOL nCountX = pItem->GetColMerge();
+ SCROW nCountY = pItem->GetRowMerge();
+ if (nCountX>1 || nCountY>1)
+ {
+ SCROW nThisRow = (i>0) ? mvData[i-1].nEndRow+1 : 0;
+ SCCOL nMergeEndCol = nThisCol + nCountX - 1;
+ SCROW nMergeEndRow = nThisRow + nCountY - 1;
+ if (nMergeEndCol > rPaintCol && nMergeEndCol <= rDocument.MaxCol())
+ rPaintCol = nMergeEndCol;
+ if (nMergeEndRow > rPaintRow && nMergeEndRow <= rDocument.MaxRow())
+ rPaintRow = nMergeEndRow;
+ bFound = true;
+
+ if (bRefresh)
+ {
+ if ( nMergeEndCol > nThisCol )
+ rDocument.ApplyFlagsTab( nThisCol+1, nThisRow, nMergeEndCol, mvData[i].nEndRow,
+ nTab, ScMF::Hor );
+ if ( nMergeEndRow > nThisRow )
+ rDocument.ApplyFlagsTab( nThisCol, nThisRow+1, nThisCol, nMergeEndRow,
+ nTab, ScMF::Ver );
+ if ( nMergeEndCol > nThisCol && nMergeEndRow > nThisRow )
+ rDocument.ApplyFlagsTab( nThisCol+1, nThisRow+1, nMergeEndCol, nMergeEndRow,
+ nTab, ScMF::Hor | ScMF::Ver );
+
+ Search( nThisRow, i ); // Data changed
+ Search( nStartRow, nStartIndex );
+ Search( nEndRow, nEndIndex );
+ }
+ }
+ }
+
+ return bFound;
+}
+
+void ScAttrArray::RemoveAreaMerge(SCROW nStartRow, SCROW nEndRow)
+{
+ assert( nCol != -1 );
+ SetDefaultIfNotInit();
+ const ScPatternAttr* pPattern;
+ const ScMergeAttr* pItem;
+ SCSIZE nIndex;
+
+ Search( nStartRow, nIndex );
+ SCROW nThisStart = (nIndex>0) ? mvData[nIndex-1].nEndRow+1 : 0;
+ if (nThisStart < nStartRow)
+ nThisStart = nStartRow;
+
+ while ( nThisStart <= nEndRow )
+ {
+ SCROW nThisEnd = mvData[nIndex].nEndRow;
+ if (nThisEnd > nEndRow)
+ nThisEnd = nEndRow;
+
+ pPattern = mvData[nIndex].pPattern;
+ pItem = &pPattern->GetItem( ATTR_MERGE );
+ SCCOL nCountX = pItem->GetColMerge();
+ SCROW nCountY = pItem->GetRowMerge();
+ if (nCountX>1 || nCountY>1)
+ {
+ const ScMergeAttr* pAttr = &rDocument.GetPool()->GetDefaultItem( ATTR_MERGE );
+ const ScMergeFlagAttr* pFlagAttr = &rDocument.GetPool()->GetDefaultItem( ATTR_MERGE_FLAG );
+
+ OSL_ENSURE( nCountY==1 || nThisStart==nThisEnd, "What's up?" );
+
+ SCCOL nThisCol = nCol;
+ SCCOL nMergeEndCol = nThisCol + nCountX - 1;
+ SCROW nMergeEndRow = nThisEnd + nCountY - 1;
+
+ // ApplyAttr for areas
+ for (SCROW nThisRow = nThisStart; nThisRow <= nThisEnd; nThisRow++)
+ rDocument.ApplyAttr( nThisCol, nThisRow, nTab, *pAttr );
+
+ ScPatternAttr aNewPattern( rDocument.GetPool() );
+ SfxItemSet* pSet = &aNewPattern.GetItemSet();
+ pSet->Put( *pFlagAttr );
+ rDocument.ApplyPatternAreaTab( nThisCol, nThisStart, nMergeEndCol, nMergeEndRow,
+ nTab, aNewPattern );
+
+ Search( nThisEnd, nIndex ); // data changed
+ }
+
+ ++nIndex;
+ if ( nIndex < mvData.size() )
+ nThisStart = mvData[nIndex-1].nEndRow+1;
+ else
+ nThisStart = rDocument.MaxRow()+1; // End
+ }
+}
+
+void ScAttrArray::SetPatternAreaSafe( SCROW nStartRow, SCROW nEndRow,
+ const ScPatternAttr* pWantedPattern, bool bDefault )
+{
+ SetDefaultIfNotInit();
+ const ScPatternAttr* pOldPattern;
+ const ScMergeFlagAttr* pItem;
+
+ SCSIZE nIndex;
+ SCROW nRow;
+ SCROW nThisRow;
+ bool bFirstUse = true;
+
+ Search( nStartRow, nIndex );
+ nThisRow = (nIndex>0) ? mvData[nIndex-1].nEndRow+1 : 0;
+ while ( nThisRow <= nEndRow )
+ {
+ pOldPattern = mvData[nIndex].pPattern;
+ if (pOldPattern != pWantedPattern) // FIXME: else-branch?
+ {
+ if (nThisRow < nStartRow) nThisRow = nStartRow;
+ nRow = mvData[nIndex].nEndRow;
+ SCROW nAttrRow = std::min( nRow, nEndRow );
+ pItem = &pOldPattern->GetItem( ATTR_MERGE_FLAG );
+
+ if (pItem->IsOverlapped() || pItem->HasAutoFilter())
+ {
+ // default-constructing a ScPatternAttr for DeleteArea doesn't work
+ // because it would have no cell style information.
+ // Instead, the document's GetDefPattern is copied. Since it is passed as
+ // pWantedPattern, no special treatment of default is needed here anymore.
+ std::unique_ptr<ScPatternAttr> pNewPattern(new ScPatternAttr( *pWantedPattern ));
+ pNewPattern->GetItemSet().Put( *pItem );
+ SetPatternArea( nThisRow, nAttrRow, std::move(pNewPattern), true );
+ }
+ else
+ {
+ if ( !bDefault )
+ {
+ if (bFirstUse)
+ bFirstUse = false;
+ else
+ // it's in the pool
+ rDocument.GetPool()->Put( *pWantedPattern );
+ }
+ SetPatternArea( nThisRow, nAttrRow, pWantedPattern );
+ }
+
+ Search( nThisRow, nIndex ); // data changed
+ }
+
+ ++nIndex;
+ nThisRow = mvData[nIndex-1].nEndRow+1;
+ }
+}
+
+bool ScAttrArray::ApplyFlags( SCROW nStartRow, SCROW nEndRow, ScMF nFlags )
+{
+ SetDefaultIfNotInit();
+ const ScPatternAttr* pOldPattern;
+
+ ScMF nOldValue;
+ SCSIZE nIndex;
+ SCROW nRow;
+ SCROW nThisRow;
+ bool bChanged = false;
+
+ Search( nStartRow, nIndex );
+ nThisRow = (nIndex>0) ? mvData[nIndex-1].nEndRow+1 : 0;
+ if (nThisRow < nStartRow) nThisRow = nStartRow;
+
+ while ( nThisRow <= nEndRow )
+ {
+ pOldPattern = mvData[nIndex].pPattern;
+ nOldValue = pOldPattern->GetItem( ATTR_MERGE_FLAG ).GetValue();
+ if ( (nOldValue | nFlags) != nOldValue )
+ {
+ nRow = mvData[nIndex].nEndRow;
+ SCROW nAttrRow = std::min( nRow, nEndRow );
+ auto pNewPattern = std::make_unique<ScPatternAttr>(*pOldPattern);
+ pNewPattern->GetItemSet().Put( ScMergeFlagAttr( nOldValue | nFlags ) );
+ SetPatternArea( nThisRow, nAttrRow, std::move(pNewPattern), true );
+ Search( nThisRow, nIndex ); // data changed
+ bChanged = true;
+ }
+
+ ++nIndex;
+ nThisRow = mvData[nIndex-1].nEndRow+1;
+ }
+
+ return bChanged;
+}
+
+bool ScAttrArray::RemoveFlags( SCROW nStartRow, SCROW nEndRow, ScMF nFlags )
+{
+ SetDefaultIfNotInit();
+ const ScPatternAttr* pOldPattern;
+
+ ScMF nOldValue;
+ SCSIZE nIndex;
+ SCROW nRow;
+ SCROW nThisRow;
+ bool bChanged = false;
+
+ Search( nStartRow, nIndex );
+ nThisRow = (nIndex>0) ? mvData[nIndex-1].nEndRow+1 : 0;
+ if (nThisRow < nStartRow) nThisRow = nStartRow;
+
+ while ( nThisRow <= nEndRow )
+ {
+ pOldPattern = mvData[nIndex].pPattern;
+ nOldValue = pOldPattern->GetItem( ATTR_MERGE_FLAG ).GetValue();
+ if ( (nOldValue & ~nFlags) != nOldValue )
+ {
+ nRow = mvData[nIndex].nEndRow;
+ SCROW nAttrRow = std::min( nRow, nEndRow );
+ auto pNewPattern = std::make_unique<ScPatternAttr>(*pOldPattern);
+ pNewPattern->GetItemSet().Put( ScMergeFlagAttr( nOldValue & ~nFlags ) );
+ SetPatternArea( nThisRow, nAttrRow, std::move(pNewPattern), true );
+ Search( nThisRow, nIndex ); // data changed
+ bChanged = true;
+ }
+
+ ++nIndex;
+ nThisRow = mvData[nIndex-1].nEndRow+1;
+ }
+
+ return bChanged;
+}
+
+void ScAttrArray::ClearItems( SCROW nStartRow, SCROW nEndRow, const sal_uInt16* pWhich )
+{
+ SetDefaultIfNotInit();
+ SCSIZE nIndex;
+ SCROW nRow;
+ SCROW nThisRow;
+
+ Search( nStartRow, nIndex );
+ nThisRow = (nIndex>0) ? mvData[nIndex-1].nEndRow+1 : 0;
+ if (nThisRow < nStartRow) nThisRow = nStartRow;
+
+ while ( nThisRow <= nEndRow )
+ {
+ const ScPatternAttr* pOldPattern = mvData[nIndex].pPattern;
+ if ( pOldPattern->HasItemsSet( pWhich ) )
+ {
+ auto pNewPattern = std::make_unique<ScPatternAttr>(*pOldPattern);
+ pNewPattern->ClearItems( pWhich );
+
+ nRow = mvData[nIndex].nEndRow;
+ SCROW nAttrRow = std::min( nRow, nEndRow );
+ SetPatternArea( nThisRow, nAttrRow, std::move(pNewPattern), true );
+ Search( nThisRow, nIndex ); // data changed
+ }
+
+ ++nIndex;
+ nThisRow = mvData[nIndex-1].nEndRow+1;
+ }
+}
+
+void ScAttrArray::ChangeIndent( SCROW nStartRow, SCROW nEndRow, bool bIncrement )
+{
+ SetDefaultIfNotInit();
+ SCSIZE nIndex;
+ Search( nStartRow, nIndex );
+ SCROW nThisStart = (nIndex>0) ? mvData[nIndex-1].nEndRow+1 : 0;
+ if (nThisStart < nStartRow) nThisStart = nStartRow;
+
+ while ( nThisStart <= nEndRow )
+ {
+ const ScPatternAttr* pOldPattern = mvData[nIndex].pPattern;
+ const SfxItemSet& rOldSet = pOldPattern->GetItemSet();
+ const SvxHorJustifyItem* pItem;
+
+ bool bNeedJust = !( pItem = rOldSet.GetItemIfSet( ATTR_HOR_JUSTIFY, false ) )
+ || (pItem->GetValue() != SvxCellHorJustify::Left &&
+ pItem->GetValue() != SvxCellHorJustify::Right );
+ sal_uInt16 nOldValue = rOldSet.Get( ATTR_INDENT ).GetValue();
+ sal_uInt16 nNewValue = nOldValue;
+ // To keep Increment indent from running outside the cell1659
+ tools::Long nColWidth = static_cast<tools::Long>(
+ rDocument.GetColWidth(nCol == -1 ? rDocument.MaxCol() : nCol,nTab));
+ if ( bIncrement )
+ {
+ if ( nNewValue < nColWidth-SC_INDENT_STEP )
+ {
+ nNewValue += SC_INDENT_STEP;
+ if ( nNewValue > nColWidth-SC_INDENT_STEP )
+ nNewValue = nColWidth-SC_INDENT_STEP;
+ }
+ }
+ else
+ {
+ if ( nNewValue > 0 )
+ {
+ if ( nNewValue > SC_INDENT_STEP )
+ nNewValue -= SC_INDENT_STEP;
+ else
+ nNewValue = 0;
+ }
+ }
+
+ if ( bNeedJust || nNewValue != nOldValue )
+ {
+ SCROW nThisEnd = mvData[nIndex].nEndRow;
+ SCROW nAttrRow = std::min( nThisEnd, nEndRow );
+ auto pNewPattern = std::make_unique<ScPatternAttr>(*pOldPattern);
+ pNewPattern->GetItemSet().Put( ScIndentItem( nNewValue ) );
+ if ( bNeedJust )
+ pNewPattern->GetItemSet().Put(
+ SvxHorJustifyItem( SvxCellHorJustify::Left, ATTR_HOR_JUSTIFY ) );
+ SetPatternArea( nThisStart, nAttrRow, std::move(pNewPattern), true );
+
+ nThisStart = nThisEnd + 1;
+ Search( nThisStart, nIndex ); // data changed
+ }
+ else
+ {
+ nThisStart = mvData[nIndex].nEndRow + 1;
+ ++nIndex;
+ }
+ }
+}
+
+SCROW ScAttrArray::GetNextUnprotected( SCROW nRow, bool bUp ) const
+{
+ tools::Long nRet = nRow;
+ if (rDocument.ValidRow(nRow))
+ {
+ if ( mvData.empty() )
+ {
+ if ( bUp )
+ return -1;
+ else
+ return rDocument.MaxRow()+1;
+ }
+
+ SCSIZE nIndex;
+ Search(nRow, nIndex);
+ while (mvData[nIndex].pPattern->
+ GetItem(ATTR_PROTECTION).GetProtection())
+ {
+ if (bUp)
+ {
+ if (nIndex==0)
+ return -1; // not found
+ --nIndex;
+ nRet = mvData[nIndex].nEndRow;
+ }
+ else
+ {
+ nRet = mvData[nIndex].nEndRow+1;
+ ++nIndex;
+ if (nIndex >= mvData.size())
+ return rDocument.MaxRow()+1; // not found
+ }
+ }
+ }
+ return nRet;
+}
+
+void ScAttrArray::FindStyleSheet( const SfxStyleSheetBase* pStyleSheet, ScFlatBoolRowSegments& rUsedRows, bool bReset )
+{
+ SetDefaultIfNotInit();
+ SCROW nStart = 0;
+ SCSIZE nPos = 0;
+ while (nPos < mvData.size())
+ {
+ SCROW nEnd = mvData[nPos].nEndRow;
+ if (mvData[nPos].pPattern->GetStyleSheet() == pStyleSheet)
+ {
+ rUsedRows.setTrue(nStart, nEnd);
+
+ if (bReset)
+ {
+ ScPatternAttr aNewPattern(*mvData[nPos].pPattern);
+ rDocument.GetPool()->Remove(*mvData[nPos].pPattern);
+ aNewPattern.SetStyleSheet( static_cast<ScStyleSheet*>(
+ rDocument.GetStyleSheetPool()->
+ Find( ScResId(STR_STYLENAME_STANDARD),
+ SfxStyleFamily::Para,
+ SfxStyleSearchBits::Auto | SfxStyleSearchBits::ScStandard ) ) );
+ mvData[nPos].pPattern = &rDocument.GetPool()->Put(aNewPattern);
+
+ if (Concat(nPos))
+ {
+ Search(nStart, nPos);
+ --nPos; // because ++ at end
+ }
+ }
+ }
+ nStart = nEnd + 1;
+ ++nPos;
+ }
+}
+
+bool ScAttrArray::IsStyleSheetUsed( const ScStyleSheet& rStyle ) const
+{
+ if ( mvData.empty() )
+ {
+ const ScStyleSheet* pStyle = rDocument.GetDefPattern()->GetStyleSheet();
+ if ( pStyle )
+ {
+ pStyle->SetUsage( ScStyleSheet::Usage::USED );
+ if ( pStyle == &rStyle )
+ return true;
+ }
+ return false;
+ }
+
+ bool bIsUsed = false;
+ SCSIZE nPos = 0;
+
+ while ( nPos < mvData.size() )
+ {
+ const ScStyleSheet* pStyle = mvData[nPos].pPattern->GetStyleSheet();
+ if ( pStyle )
+ {
+ pStyle->SetUsage( ScStyleSheet::Usage::USED );
+ if ( pStyle == &rStyle )
+ {
+ bIsUsed = true;
+ }
+ }
+ nPos++;
+ }
+
+ return bIsUsed;
+}
+
+bool ScAttrArray::IsEmpty() const
+{
+ if ( mvData.empty() )
+ return true;
+
+ if (mvData.size() == 1)
+ {
+ return mvData[0].pPattern == rDocument.GetDefPattern();
+ }
+ else
+ return false;
+}
+
+bool ScAttrArray::GetFirstVisibleAttr( SCROW& rFirstRow ) const
+{
+ if ( mvData.empty() )
+ return false;
+
+ bool bFound = false;
+ SCSIZE nStart = 0;
+
+ // Skip first entry if more than 1 row.
+ // Entries at the end are not skipped, GetFirstVisibleAttr may be larger than GetLastVisibleAttr.
+
+ SCSIZE nVisStart = 1;
+ while ( nVisStart < mvData.size() && mvData[nVisStart].pPattern->IsVisibleEqual(*mvData[nVisStart-1].pPattern) )
+ ++nVisStart;
+ if ( nVisStart >= mvData.size() || mvData[nVisStart-1].nEndRow > 0 ) // more than 1 row?
+ nStart = nVisStart;
+
+ while ( nStart < mvData.size() && !bFound )
+ {
+ if ( mvData[nStart].pPattern->IsVisible() )
+ {
+ rFirstRow = nStart ? ( mvData[nStart-1].nEndRow + 1 ) : 0;
+ bFound = true;
+ }
+ else
+ ++nStart;
+ }
+
+ return bFound;
+}
+
+// size (rows) of a range of attributes after cell content where the search is stopped
+// (more than a default page size, 2*42 because it's as good as any number)
+
+const SCROW SC_VISATTR_STOP = 84;
+
+bool ScAttrArray::GetLastVisibleAttr( SCROW& rLastRow, SCROW nLastData ) const
+{
+ if ( mvData.empty() )
+ {
+ rLastRow = nLastData;
+ return false;
+ }
+
+ // #i30830# changed behavior:
+ // ignore all attributes starting with the first run of SC_VISATTR_STOP equal rows
+ // below the last content cell
+
+ if ( nLastData == rDocument.MaxRow() )
+ {
+ rLastRow = rDocument.MaxRow(); // can't look for attributes below rDocument.MaxRow()
+ return true;
+ }
+
+ // Quick check: last data row in or immediately preceding a run that is the
+ // last attribution down to the end, e.g. default style or column style.
+ SCSIZE nPos = mvData.size() - 1;
+ SCROW nStartRow = (nPos ? mvData[nPos-1].nEndRow + 1 : 0);
+ if (nStartRow <= nLastData + 1)
+ {
+ // Ignore here a few rows if data happens to end within
+ // SC_VISATTR_STOP rows before rDocument.MaxRow().
+ rLastRow = nLastData;
+ return false;
+ }
+
+ // Find a run below last data row.
+ bool bFound = false;
+ Search( nLastData, nPos );
+ while ( nPos < mvData.size() )
+ {
+ // find range of visually equal formats
+ SCSIZE nEndPos = nPos;
+ while ( nEndPos < mvData.size()-1 &&
+ mvData[nEndPos].pPattern->IsVisibleEqual( *mvData[nEndPos+1].pPattern))
+ ++nEndPos;
+ SCROW nAttrStartRow = ( nPos > 0 ) ? ( mvData[nPos-1].nEndRow + 1 ) : 0;
+ if ( nAttrStartRow <= nLastData )
+ nAttrStartRow = nLastData + 1;
+ SCROW nAttrSize = mvData[nEndPos].nEndRow + 1 - nAttrStartRow;
+ if ( nAttrSize >= SC_VISATTR_STOP )
+ break; // while, ignore this range and below
+ else if ( mvData[nEndPos].pPattern->IsVisible() )
+ {
+ rLastRow = mvData[nEndPos].nEndRow;
+ bFound = true;
+ }
+ nPos = nEndPos + 1;
+ }
+
+ return bFound;
+}
+
+bool ScAttrArray::HasVisibleAttrIn( SCROW nStartRow, SCROW nEndRow ) const
+{
+ if ( mvData.empty() )
+ return rDocument.GetDefPattern()->IsVisible();
+
+ SCSIZE nIndex;
+ Search( nStartRow, nIndex );
+ SCROW nThisStart = nStartRow;
+ bool bFound = false;
+ while ( nIndex < mvData.size() && nThisStart <= nEndRow && !bFound )
+ {
+ if ( mvData[nIndex].pPattern->IsVisible() )
+ bFound = true;
+
+ nThisStart = mvData[nIndex].nEndRow + 1;
+ ++nIndex;
+ }
+
+ return bFound;
+}
+
+bool ScAttrArray::IsVisibleEqual( const ScAttrArray& rOther,
+ SCROW nStartRow, SCROW nEndRow ) const
+{
+ if ( mvData.empty() && rOther.mvData.empty() )
+ {
+ const ScPatternAttr* pDefPattern1 = rDocument.GetDefPattern();
+ const ScPatternAttr* pDefPattern2 = rOther.rDocument.GetDefPattern();
+ return ( pDefPattern1 == pDefPattern2 || pDefPattern1->IsVisibleEqual( *pDefPattern2 ) );
+ }
+
+ {
+ const ScAttrArray* pNonDefault = nullptr;
+ const ScPatternAttr* pDefPattern = nullptr;
+ bool bDefNonDefCase = false;
+ if ( mvData.empty() && !rOther.mvData.empty() )
+ {
+ pNonDefault = &rOther;
+ pDefPattern = rDocument.GetDefPattern();
+ bDefNonDefCase = true;
+ }
+ else if ( !mvData.empty() && rOther.mvData.empty() )
+ {
+ pNonDefault = this;
+ pDefPattern = rOther.rDocument.GetDefPattern();
+ bDefNonDefCase = true;
+ }
+
+ if ( bDefNonDefCase )
+ {
+ bool bEqual = true;
+ SCSIZE nPos = 0;
+ if ( nStartRow > 0 )
+ pNonDefault->Search( nStartRow, nPos );
+
+ while ( nPos < pNonDefault->Count() && bEqual )
+ {
+ const ScPatternAttr* pNonDefPattern = pNonDefault->mvData[nPos].pPattern;
+ bEqual = ( pNonDefPattern == pDefPattern ||
+ pNonDefPattern->IsVisibleEqual( *pDefPattern ) );
+
+ if ( pNonDefault->mvData[nPos].nEndRow >= nEndRow ) break;
+ ++nPos;
+ }
+ return bEqual;
+ }
+ }
+
+ bool bEqual = true;
+ SCSIZE nThisPos = 0;
+ SCSIZE nOtherPos = 0;
+ if ( nStartRow > 0 )
+ {
+ Search( nStartRow, nThisPos );
+ rOther.Search( nStartRow, nOtherPos );
+ }
+
+ while ( nThisPos<mvData.size() && nOtherPos<rOther.Count() && bEqual )
+ {
+ SCROW nThisRow = mvData[nThisPos].nEndRow;
+ SCROW nOtherRow = rOther.mvData[nOtherPos].nEndRow;
+ const ScPatternAttr* pThisPattern = mvData[nThisPos].pPattern;
+ const ScPatternAttr* pOtherPattern = rOther.mvData[nOtherPos].pPattern;
+ bEqual = ( pThisPattern == pOtherPattern ||
+ pThisPattern->IsVisibleEqual(*pOtherPattern) );
+
+ if ( nThisRow >= nOtherRow )
+ {
+ if ( nOtherRow >= nEndRow ) break;
+ ++nOtherPos;
+ }
+ if ( nThisRow <= nOtherRow )
+ {
+ if ( nThisRow >= nEndRow ) break;
+ ++nThisPos;
+ }
+ }
+
+ return bEqual;
+}
+
+bool ScAttrArray::IsAllEqual( const ScAttrArray& rOther, SCROW nStartRow, SCROW nEndRow ) const
+{
+ // summarised with IsVisibleEqual
+ if ( mvData.empty() && rOther.mvData.empty() )
+ {
+ const ScPatternAttr* pDefPattern1 = rDocument.GetDefPattern();
+ const ScPatternAttr* pDefPattern2 = rOther.rDocument.GetDefPattern();
+ return ( pDefPattern1 == pDefPattern2 );
+ }
+
+ {
+ const ScAttrArray* pNonDefault = nullptr;
+ const ScPatternAttr* pDefPattern = nullptr;
+ bool bDefNonDefCase = false;
+ if ( mvData.empty() && !rOther.mvData.empty() )
+ {
+ pNonDefault = &rOther;
+ pDefPattern = rDocument.GetDefPattern();
+ bDefNonDefCase = true;
+ }
+ else if ( !mvData.empty() && rOther.mvData.empty() )
+ {
+ pNonDefault = this;
+ pDefPattern = rOther.rDocument.GetDefPattern();
+ bDefNonDefCase = true;
+ }
+
+ if ( bDefNonDefCase )
+ {
+ bool bEqual = true;
+ SCSIZE nPos = 0;
+ if ( nStartRow > 0 )
+ pNonDefault->Search( nStartRow, nPos );
+
+ while ( nPos < pNonDefault->Count() && bEqual )
+ {
+ const ScPatternAttr* pNonDefPattern = pNonDefault->mvData[nPos].pPattern;
+ bEqual = ( pNonDefPattern == pDefPattern );
+
+ if ( pNonDefault->mvData[nPos].nEndRow >= nEndRow ) break;
+ ++nPos;
+ }
+ return bEqual;
+ }
+ }
+
+ bool bEqual = true;
+ SCSIZE nThisPos = 0;
+ SCSIZE nOtherPos = 0;
+ if ( nStartRow > 0 )
+ {
+ Search( nStartRow, nThisPos );
+ rOther.Search( nStartRow, nOtherPos );
+ }
+
+ while ( nThisPos<mvData.size() && nOtherPos<rOther.Count() && bEqual )
+ {
+ SCROW nThisRow = mvData[nThisPos].nEndRow;
+ SCROW nOtherRow = rOther.mvData[nOtherPos].nEndRow;
+ const ScPatternAttr* pThisPattern = mvData[nThisPos].pPattern;
+ const ScPatternAttr* pOtherPattern = rOther.mvData[nOtherPos].pPattern;
+ bEqual = ( pThisPattern == pOtherPattern );
+
+ if ( nThisRow >= nOtherRow )
+ {
+ if ( nOtherRow >= nEndRow ) break;
+ ++nOtherPos;
+ }
+ if ( nThisRow <= nOtherRow )
+ {
+ if ( nThisRow >= nEndRow ) break;
+ ++nThisPos;
+ }
+ }
+
+ return bEqual;
+}
+
+bool ScAttrArray::TestInsertCol( SCROW nStartRow, SCROW nEndRow) const
+{
+ // Horizontal aggregate are not allowed to be moved out; if whole summary,
+ // here is not recognized
+
+ bool bTest = true;
+ if (!IsEmpty())
+ {
+ SCSIZE nIndex = 0;
+ if ( nStartRow > 0 )
+ Search( nStartRow, nIndex );
+
+ for ( ; nIndex < mvData.size(); nIndex++ )
+ {
+ if ( mvData[nIndex].pPattern->
+ GetItem(ATTR_MERGE_FLAG).IsHorOverlapped() )
+ {
+ bTest = false; // may not be pushed out
+ break;
+ }
+ if ( mvData[nIndex].nEndRow >= nEndRow ) // end of range
+ break;
+ }
+ }
+ return bTest;
+}
+
+bool ScAttrArray::TestInsertRow( SCSIZE nSize ) const
+{
+ // if 1st row pushed out is vertically overlapped, summary would be broken
+
+ // rDocument.MaxRow() + 1 - nSize = 1st row pushed out
+
+ if ( mvData.empty() )
+ return !rDocument.GetDefPattern()->
+ GetItem(ATTR_MERGE_FLAG).IsVerOverlapped();
+
+ SCSIZE nFirstLost = mvData.size()-1;
+ while ( nFirstLost && mvData[nFirstLost-1].nEndRow >= sal::static_int_cast<SCROW>(rDocument.MaxRow() + 1 - nSize) )
+ --nFirstLost;
+
+ return !mvData[nFirstLost].pPattern->
+ GetItem(ATTR_MERGE_FLAG).IsVerOverlapped();
+}
+
+void ScAttrArray::InsertRow( SCROW nStartRow, SCSIZE nSize )
+{
+ SetDefaultIfNotInit();
+
+ SCROW nSearch = nStartRow > 0 ? nStartRow - 1 : 0; // expand predecessor
+ SCSIZE nIndex;
+ Search( nSearch, nIndex );
+
+ // set ScMergeAttr may not be extended (so behind delete again)
+
+ bool bDoMerge = mvData[nIndex].pPattern->GetItem(ATTR_MERGE).IsMerged();
+
+ assert( !bDoMerge || nCol != -1 );
+
+ SCSIZE nRemove = 0;
+ SCSIZE i;
+ for (i = nIndex; i < mvData.size()-1; i++)
+ {
+ SCROW nNew = mvData[i].nEndRow + nSize;
+ if ( nNew >= rDocument.MaxRow() ) // at end?
+ {
+ nNew = rDocument.MaxRow();
+ if (!nRemove)
+ nRemove = i+1; // remove the following?
+ }
+ mvData[i].nEndRow = nNew;
+ }
+
+ // Remove entries at end ?
+
+ if (nRemove && nRemove < mvData.size())
+ DeleteRange( nRemove, mvData.size()-1 );
+
+ if (bDoMerge) // extensively repair (again) ScMergeAttr
+ {
+ // ApplyAttr for areas
+
+ const SfxPoolItem& rDef = rDocument.GetPool()->GetDefaultItem( ATTR_MERGE );
+ for (SCSIZE nAdd=0; nAdd<nSize; nAdd++)
+ rDocument.ApplyAttr( nCol, nStartRow+nAdd, nTab, rDef );
+
+ // reply inserts in this area not summarized
+ }
+
+ // Don't duplicate the merge flags in the inserted row.
+ // #i108488# ScMF::Scenario has to be allowed.
+ RemoveFlags( nStartRow, nStartRow+nSize-1, ScMF::Hor | ScMF::Ver | ScMF::Auto | ScMF::Button );
+}
+
+void ScAttrArray::DeleteRow( SCROW nStartRow, SCSIZE nSize )
+{
+ SetDefaultIfNotInit();
+ bool bFirst=true;
+ SCSIZE nStartIndex = 0;
+ SCSIZE nEndIndex = 0;
+ SCSIZE i;
+
+ for ( i = 0; i < mvData.size()-1; i++)
+ if (mvData[i].nEndRow >= nStartRow && mvData[i].nEndRow <= sal::static_int_cast<SCROW>(nStartRow+nSize-1))
+ {
+ if (bFirst)
+ {
+ nStartIndex = i;
+ bFirst = false;
+ }
+ nEndIndex = i;
+ }
+ if (!bFirst)
+ {
+ SCROW nStart;
+ if (nStartIndex==0)
+ nStart = 0;
+ else
+ nStart = mvData[nStartIndex-1].nEndRow + 1;
+
+ if (nStart < nStartRow)
+ {
+ mvData[nStartIndex].nEndRow = nStartRow - 1;
+ ++nStartIndex;
+ }
+ if (nEndIndex >= nStartIndex)
+ {
+ DeleteRange( nStartIndex, nEndIndex );
+ if (nStartIndex > 0)
+ if ( mvData[nStartIndex-1].pPattern == mvData[nStartIndex].pPattern )
+ DeleteRange( nStartIndex-1, nStartIndex-1 );
+ }
+ }
+ for (i = 0; i < mvData.size()-1; i++)
+ if (mvData[i].nEndRow >= nStartRow)
+ mvData[i].nEndRow -= nSize;
+
+ // Below does not follow the pattern to detect pressure ranges;
+ // instead, only remove merge flags.
+ RemoveFlags( rDocument.MaxRow()-nSize+1, rDocument.MaxRow(), ScMF::Hor | ScMF::Ver | ScMF::Auto );
+}
+
+void ScAttrArray::DeleteRange( SCSIZE nStartIndex, SCSIZE nEndIndex )
+{
+ SetDefaultIfNotInit();
+ ScDocumentPool* pDocPool = rDocument.GetPool();
+ for (SCSIZE i = nStartIndex; i <= nEndIndex; i++)
+ pDocPool->Remove(*mvData[i].pPattern);
+
+ mvData.erase(mvData.begin() + nStartIndex, mvData.begin() + nEndIndex + 1);
+}
+
+void ScAttrArray::DeleteArea(SCROW nStartRow, SCROW nEndRow)
+{
+ SetDefaultIfNotInit();
+ if ( nCol != -1 )
+ RemoveAreaMerge( nStartRow, nEndRow ); // remove from combined flags
+
+ if ( !HasAttrib( nStartRow, nEndRow, HasAttrFlags::Overlapped | HasAttrFlags::AutoFilter) )
+ SetPatternArea( nStartRow, nEndRow, rDocument.GetDefPattern() );
+ else
+ SetPatternAreaSafe( nStartRow, nEndRow, rDocument.GetDefPattern(), true ); // leave merge flags
+}
+
+void ScAttrArray::DeleteHardAttr(SCROW nStartRow, SCROW nEndRow)
+{
+ SetDefaultIfNotInit();
+ const ScPatternAttr* pDefPattern = rDocument.GetDefPattern();
+
+ SCSIZE nIndex;
+ SCROW nRow;
+ SCROW nThisRow;
+
+ Search( nStartRow, nIndex );
+ nThisRow = (nIndex>0) ? mvData[nIndex-1].nEndRow+1 : 0;
+ if (nThisRow < nStartRow) nThisRow = nStartRow;
+
+ while ( nThisRow <= nEndRow )
+ {
+ const ScPatternAttr* pOldPattern = mvData[nIndex].pPattern;
+
+ if ( pOldPattern->GetItemSet().Count() ) // hard attributes ?
+ {
+ nRow = mvData[nIndex].nEndRow;
+ SCROW nAttrRow = std::min( nRow, nEndRow );
+
+ auto pNewPattern = std::make_unique<ScPatternAttr>(*pOldPattern);
+ SfxItemSet& rSet = pNewPattern->GetItemSet();
+ for (sal_uInt16 nId = ATTR_PATTERN_START; nId <= ATTR_PATTERN_END; nId++)
+ if (nId != ATTR_MERGE && nId != ATTR_MERGE_FLAG)
+ rSet.ClearItem(nId);
+
+ if ( *pNewPattern == *pDefPattern )
+ SetPatternArea( nThisRow, nAttrRow, pDefPattern );
+ else
+ SetPatternArea( nThisRow, nAttrRow, std::move(pNewPattern), true );
+
+ Search( nThisRow, nIndex ); // data changed
+ }
+
+ ++nIndex;
+ nThisRow = mvData[nIndex-1].nEndRow+1;
+ }
+}
+
+/**
+ * Move within a document
+ */
+void ScAttrArray::MoveTo(SCROW nStartRow, SCROW nEndRow, ScAttrArray& rAttrArray)
+{
+ SetDefaultIfNotInit();
+ SCROW nStart = nStartRow;
+ for (SCSIZE i = 0; i < mvData.size(); i++)
+ {
+ if ((mvData[i].nEndRow >= nStartRow) && (i == 0 || mvData[i-1].nEndRow < nEndRow))
+ {
+ // copy (bPutToPool=TRUE)
+ rAttrArray.SetPatternArea( nStart, std::min( mvData[i].nEndRow, nEndRow ),
+ mvData[i].pPattern, true );
+ }
+ nStart = std::max( nStart, mvData[i].nEndRow + 1 );
+ }
+ DeleteArea(nStartRow, nEndRow);
+}
+
+/**
+ * Copy between documents (Clipboard)
+ */
+void ScAttrArray::CopyArea(
+ SCROW nStartRow, SCROW nEndRow, tools::Long nDy, ScAttrArray& rAttrArray, ScMF nStripFlags) const
+{
+ nStartRow -= nDy; // Source
+ nEndRow -= nDy;
+
+ SCROW nDestStart = std::max(static_cast<tools::Long>(static_cast<tools::Long>(nStartRow) + nDy), tools::Long(0));
+ SCROW nDestEnd = std::min(static_cast<tools::Long>(static_cast<tools::Long>(nEndRow) + nDy), tools::Long(rDocument.MaxRow()));
+
+ ScDocumentPool* pSourceDocPool = rDocument.GetPool();
+ ScDocumentPool* pDestDocPool = rAttrArray.rDocument.GetPool();
+ bool bSamePool = (pSourceDocPool==pDestDocPool);
+
+ if ( mvData.empty() )
+ {
+ const ScPatternAttr* pNewPattern = &pDestDocPool->GetDefaultItem( ATTR_PATTERN );
+ rAttrArray.SetPatternArea(nDestStart, nDestEnd, pNewPattern);
+ return;
+ }
+
+ for (SCSIZE i = 0; (i < mvData.size()) && (nDestStart <= nDestEnd); i++)
+ {
+ if (mvData[i].nEndRow >= nStartRow)
+ {
+ const ScPatternAttr* pOldPattern = mvData[i].pPattern;
+ const ScPatternAttr* pNewPattern;
+
+ if (IsDefaultItem( pOldPattern ))
+ {
+ // default: nothing changed
+
+ pNewPattern = &pDestDocPool->GetDefaultItem( ATTR_PATTERN );
+ }
+ else if ( nStripFlags != ScMF::NONE )
+ {
+ ScPatternAttr aTmpPattern( *pOldPattern );
+ ScMF nNewFlags = ScMF::NONE;
+ if ( nStripFlags != ScMF::All )
+ nNewFlags = aTmpPattern.GetItem(ATTR_MERGE_FLAG).GetValue() & ~nStripFlags;
+
+ if ( nNewFlags != ScMF::NONE )
+ aTmpPattern.GetItemSet().Put( ScMergeFlagAttr( nNewFlags ) );
+ else
+ aTmpPattern.GetItemSet().ClearItem( ATTR_MERGE_FLAG );
+
+ if (bSamePool)
+ pNewPattern = &pDestDocPool->Put(aTmpPattern);
+ else
+ pNewPattern = aTmpPattern.PutInPool( &rAttrArray.rDocument, &rDocument );
+ }
+ else
+ {
+ if (bSamePool)
+ pNewPattern = &pDestDocPool->Put(*pOldPattern);
+ else
+ pNewPattern = pOldPattern->PutInPool( &rAttrArray.rDocument, &rDocument );
+ }
+
+ rAttrArray.SetPatternArea(nDestStart,
+ std::min(static_cast<SCROW>(mvData[i].nEndRow + nDy), nDestEnd), pNewPattern);
+ }
+
+ // when pasting from clipboard and skipping filtered rows, the adjusted
+ // end position can be negative
+ nDestStart = std::max(static_cast<tools::Long>(nDestStart), static_cast<tools::Long>(mvData[i].nEndRow + nDy + 1));
+ }
+}
+
+/**
+ * Leave flags
+ * summarized with CopyArea
+ */
+void ScAttrArray::CopyAreaSafe( SCROW nStartRow, SCROW nEndRow, tools::Long nDy, ScAttrArray& rAttrArray )
+{
+ nStartRow -= nDy; // Source
+ nEndRow -= nDy;
+
+ SCROW nDestStart = std::max(static_cast<tools::Long>(static_cast<tools::Long>(nStartRow) + nDy), tools::Long(0));
+ SCROW nDestEnd = std::min(static_cast<tools::Long>(static_cast<tools::Long>(nEndRow) + nDy), tools::Long(rDocument.MaxRow()));
+
+ if ( !rAttrArray.HasAttrib( nDestStart, nDestEnd, HasAttrFlags::Overlapped ) )
+ {
+ CopyArea( nStartRow+nDy, nEndRow+nDy, nDy, rAttrArray );
+ return;
+ }
+
+ ScDocumentPool* pSourceDocPool = rDocument.GetPool();
+ ScDocumentPool* pDestDocPool = rAttrArray.rDocument.GetPool();
+ bool bSamePool = (pSourceDocPool==pDestDocPool);
+
+ if ( mvData.empty() )
+ {
+ const ScPatternAttr* pNewPattern;
+ if (bSamePool)
+ pNewPattern = &pDestDocPool->Put(*rDocument.GetDefPattern());
+ else
+ pNewPattern = rDocument.GetDefPattern()->PutInPool( &rAttrArray.rDocument, &rDocument );
+
+ rAttrArray.SetPatternAreaSafe(nDestStart, nDestEnd, pNewPattern, false);
+ return;
+ }
+
+
+ for (SCSIZE i = 0; (i < mvData.size()) && (nDestStart <= nDestEnd); i++)
+ {
+ if (mvData[i].nEndRow >= nStartRow)
+ {
+ const ScPatternAttr* pOldPattern = mvData[i].pPattern;
+ const ScPatternAttr* pNewPattern;
+
+ if (bSamePool)
+ pNewPattern = &pDestDocPool->Put(*pOldPattern);
+ else
+ pNewPattern = pOldPattern->PutInPool( &rAttrArray.rDocument, &rDocument );
+
+ rAttrArray.SetPatternAreaSafe(nDestStart,
+ std::min(static_cast<SCROW>(mvData[i].nEndRow + nDy), nDestEnd), pNewPattern, false);
+ }
+
+ // when pasting from clipboard and skipping filtered rows, the adjusted
+ // end position can be negative
+ nDestStart = std::max(static_cast<tools::Long>(nDestStart), static_cast<tools::Long>(mvData[i].nEndRow + nDy + 1));
+ }
+}
+
+SCROW ScAttrArray::SearchStyle(
+ SCROW nRow, const ScStyleSheet* pSearchStyle, bool bUp,
+ const ScMarkArray* pMarkArray) const
+{
+ bool bFound = false;
+
+ if (pMarkArray)
+ {
+ nRow = pMarkArray->GetNextMarked( nRow, bUp );
+ if (!rDocument.ValidRow(nRow))
+ return nRow;
+ }
+
+ if ( mvData.empty() )
+ {
+ if (rDocument.GetDefPattern()->GetStyleSheet() == pSearchStyle)
+ return nRow;
+
+ nRow = bUp ? -1 : rDocument.MaxRow() + 1;
+ return nRow;
+ }
+
+ SCSIZE nIndex;
+ Search(nRow, nIndex);
+ const ScPatternAttr* pPattern = mvData[nIndex].pPattern;
+
+ while (nIndex < mvData.size() && !bFound)
+ {
+ if (pPattern->GetStyleSheet() == pSearchStyle)
+ {
+ if (pMarkArray)
+ {
+ nRow = pMarkArray->GetNextMarked( nRow, bUp );
+ SCROW nStart = nIndex ? mvData[nIndex-1].nEndRow+1 : 0;
+ if (nRow >= nStart && nRow <= mvData[nIndex].nEndRow)
+ bFound = true;
+ }
+ else
+ bFound = true;
+ }
+
+ if (!bFound)
+ {
+ if (bUp)
+ {
+ if (nIndex==0)
+ {
+ nIndex = mvData.size();
+ nRow = -1;
+ }
+ else
+ {
+ --nIndex;
+ nRow = mvData[nIndex].nEndRow;
+ pPattern = mvData[nIndex].pPattern;
+ }
+ }
+ else
+ {
+ nRow = mvData[nIndex].nEndRow+1;
+ ++nIndex;
+ if (nIndex<mvData.size())
+ pPattern = mvData[nIndex].pPattern;
+ }
+ }
+ }
+
+ OSL_ENSURE( bFound || !rDocument.ValidRow(nRow), "Internal failure in ScAttrArray::SearchStyle" );
+
+ return nRow;
+}
+
+bool ScAttrArray::SearchStyleRange(
+ SCROW& rRow, SCROW& rEndRow, const ScStyleSheet* pSearchStyle, bool bUp,
+ const ScMarkArray* pMarkArray) const
+{
+ SCROW nStartRow = SearchStyle( rRow, pSearchStyle, bUp, pMarkArray );
+ if (rDocument.ValidRow(nStartRow))
+ {
+ if ( mvData.empty() )
+ {
+ rRow = nStartRow;
+ if (bUp)
+ {
+ rEndRow = 0;
+ if (pMarkArray)
+ {
+ SCROW nMarkEnd = pMarkArray->GetMarkEnd( nStartRow, true );
+ if (nMarkEnd>rEndRow)
+ rEndRow = nMarkEnd;
+ }
+ }
+ else
+ {
+ rEndRow = rDocument.MaxRow();
+ if (pMarkArray)
+ {
+ SCROW nMarkEnd = pMarkArray->GetMarkEnd( nStartRow, false );
+ if (nMarkEnd<rEndRow)
+ rEndRow = nMarkEnd;
+ }
+ }
+
+ return true;
+ }
+
+ SCSIZE nIndex;
+ Search(nStartRow,nIndex);
+
+ rRow = nStartRow;
+ if (bUp)
+ {
+ if (nIndex>0)
+ rEndRow = mvData[nIndex-1].nEndRow + 1;
+ else
+ rEndRow = 0;
+ if (pMarkArray)
+ {
+ SCROW nMarkEnd = pMarkArray->GetMarkEnd( nStartRow, true );
+ if (nMarkEnd>rEndRow)
+ rEndRow = nMarkEnd;
+ }
+ }
+ else
+ {
+ rEndRow = mvData[nIndex].nEndRow;
+ if (pMarkArray)
+ {
+ SCROW nMarkEnd = pMarkArray->GetMarkEnd( nStartRow, false );
+ if (nMarkEnd<rEndRow)
+ rEndRow = nMarkEnd;
+ }
+ }
+
+ return true;
+ }
+ else
+ return false;
+}
+
+SCSIZE ScAttrArray::Count( SCROW nStartRow, SCROW nEndRow ) const
+{
+ if ( mvData.empty() )
+ return 1;
+
+ SCSIZE nIndex1, nIndex2;
+
+ if( !Search( nStartRow, nIndex1 ) )
+ return 0;
+
+ if( !Search( nEndRow, nIndex2 ) )
+ nIndex2 = mvData.size() - 1;
+
+ return nIndex2 - nIndex1 + 1;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/attrib.cxx b/sc/source/core/data/attrib.cxx
new file mode 100644
index 000000000..7173bcb61
--- /dev/null
+++ b/sc/source/core/data/attrib.cxx
@@ -0,0 +1,889 @@
+/* -*- 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/util/CellProtection.hpp>
+
+#include <scitems.hxx>
+
+#include <editeng/editeng.hxx>
+#include <editeng/editobj.hxx>
+#include <editeng/eerdll.hxx>
+#include <editeng/borderline.hxx>
+#include <editeng/itemtype.hxx>
+#include <svl/itempool.hxx>
+
+#include <libxml/xmlwriter.h>
+
+#include <attrib.hxx>
+#include <global.hxx>
+#include <editutil.hxx>
+#include <mid.h>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <textuno.hxx>
+
+using namespace com::sun::star;
+
+
+SfxPoolItem* ScProtectionAttr::CreateDefault() { return new ScProtectionAttr; }
+
+/**
+ * General Help Function
+ */
+bool ScHasPriority( const ::editeng::SvxBorderLine* pThis, const ::editeng::SvxBorderLine* pOther )
+{
+
+ if (!pThis)
+ return false;
+ if (!pOther)
+ return true;
+
+ sal_uInt16 nThisSize = pThis->GetScaledWidth();
+ sal_uInt16 nOtherSize = pOther->GetScaledWidth();
+
+ if (nThisSize > nOtherSize)
+ return true;
+ else if (nThisSize < nOtherSize)
+ return false;
+ else
+ {
+ if ( pOther->GetInWidth() && !pThis->GetInWidth() )
+ return true;
+ else if ( pThis->GetInWidth() && !pOther->GetInWidth() )
+ return false;
+ else
+ {
+ return true; // FIXME: What is this?
+ }
+ }
+}
+
+/** Item - Implementations */
+
+/**
+ * Merge
+ */
+ScMergeAttr::ScMergeAttr():
+ SfxPoolItem(ATTR_MERGE),
+ nColMerge(0),
+ nRowMerge(0)
+{}
+
+ScMergeAttr::ScMergeAttr( SCCOL nCol, SCROW nRow):
+ SfxPoolItem(ATTR_MERGE),
+ nColMerge(nCol),
+ nRowMerge(nRow)
+{}
+
+ScMergeAttr::ScMergeAttr(const ScMergeAttr& rItem):
+ SfxPoolItem(ATTR_MERGE)
+{
+ nColMerge = rItem.nColMerge;
+ nRowMerge = rItem.nRowMerge;
+}
+
+ScMergeAttr::~ScMergeAttr()
+{
+}
+
+bool ScMergeAttr::operator==( const SfxPoolItem& rItem ) const
+{
+ return SfxPoolItem::operator==(rItem)
+ && (nColMerge == static_cast<const ScMergeAttr&>(rItem).nColMerge)
+ && (nRowMerge == static_cast<const ScMergeAttr&>(rItem).nRowMerge);
+}
+
+ScMergeAttr* ScMergeAttr::Clone( SfxItemPool * ) const
+{
+ return new ScMergeAttr(*this);
+}
+
+void ScMergeAttr::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("ScMergeAttr"));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("col-merge"), BAD_CAST(OString::number(GetColMerge()).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("row-merge"), BAD_CAST(OString::number(GetRowMerge()).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("merged"), BAD_CAST(OString::boolean(IsMerged()).getStr()));
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+/**
+ * MergeFlag
+ */
+ScMergeFlagAttr::ScMergeFlagAttr():
+ SfxInt16Item(ATTR_MERGE_FLAG, 0)
+{
+}
+
+ScMergeFlagAttr::ScMergeFlagAttr(ScMF nFlags):
+ SfxInt16Item(ATTR_MERGE_FLAG, static_cast<sal_Int16>(nFlags))
+{
+}
+
+ScMergeFlagAttr::~ScMergeFlagAttr()
+{
+}
+
+ScMergeFlagAttr* ScMergeFlagAttr::Clone(SfxItemPool *) const
+{
+ return new ScMergeFlagAttr(*this);
+}
+
+bool ScMergeFlagAttr::HasPivotButton() const
+{
+ return bool(GetValue() & ScMF::Button);
+}
+
+bool ScMergeFlagAttr::HasPivotPopupButton() const
+{
+ return bool(GetValue() & ScMF::ButtonPopup);
+}
+
+void ScMergeFlagAttr::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("ScMergeFlagAttr"));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("overlapped"), BAD_CAST(OString::boolean(IsOverlapped()).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("hor_overlapped"), BAD_CAST(OString::boolean(IsHorOverlapped()).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("ver_overlapped"), BAD_CAST(OString::boolean(IsVerOverlapped()).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("autofilter"), BAD_CAST(OString::boolean(HasAutoFilter()).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("scenario"), BAD_CAST(OString::boolean(IsScenario()).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("pivot-button"), BAD_CAST(OString::boolean(HasPivotButton()).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("pivot-popup-button"), BAD_CAST(OString::boolean(HasPivotPopupButton()).getStr()));
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+/**
+ * Protection
+ */
+ScProtectionAttr::ScProtectionAttr():
+ SfxPoolItem(ATTR_PROTECTION),
+ bProtection(true),
+ bHideFormula(false),
+ bHideCell(false),
+ bHidePrint(false)
+{
+}
+
+ScProtectionAttr::ScProtectionAttr( bool bProtect, bool bHFormula,
+ bool bHCell, bool bHPrint):
+ SfxPoolItem(ATTR_PROTECTION),
+ bProtection(bProtect),
+ bHideFormula(bHFormula),
+ bHideCell(bHCell),
+ bHidePrint(bHPrint)
+{
+}
+
+ScProtectionAttr::ScProtectionAttr(const ScProtectionAttr& rItem):
+ SfxPoolItem(ATTR_PROTECTION)
+{
+ bProtection = rItem.bProtection;
+ bHideFormula = rItem.bHideFormula;
+ bHideCell = rItem.bHideCell;
+ bHidePrint = rItem.bHidePrint;
+}
+
+ScProtectionAttr::~ScProtectionAttr()
+{
+}
+
+bool ScProtectionAttr::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const
+{
+ nMemberId &= ~CONVERT_TWIPS;
+ switch ( nMemberId )
+ {
+ case 0 :
+ {
+ util::CellProtection aProtection;
+ aProtection.IsLocked = bProtection;
+ aProtection.IsFormulaHidden = bHideFormula;
+ aProtection.IsHidden = bHideCell;
+ aProtection.IsPrintHidden = bHidePrint;
+ rVal <<= aProtection;
+ break;
+ }
+ case MID_1 :
+ rVal <<= bProtection; break;
+ case MID_2 :
+ rVal <<= bHideFormula; break;
+ case MID_3 :
+ rVal <<= bHideCell; break;
+ case MID_4 :
+ rVal <<= bHidePrint; break;
+ default:
+ OSL_FAIL("Wrong MemberID!");
+ return false;
+ }
+
+ return true;
+}
+
+bool ScProtectionAttr::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId )
+{
+ bool bRet = false;
+ bool bVal = false;
+ nMemberId &= ~CONVERT_TWIPS;
+ switch ( nMemberId )
+ {
+ case 0 :
+ {
+ util::CellProtection aProtection;
+ if ( rVal >>= aProtection )
+ {
+ bProtection = aProtection.IsLocked;
+ bHideFormula = aProtection.IsFormulaHidden;
+ bHideCell = aProtection.IsHidden;
+ bHidePrint = aProtection.IsPrintHidden;
+ bRet = true;
+ }
+ else
+ {
+ OSL_FAIL("exception - wrong argument");
+ }
+ break;
+ }
+ case MID_1 :
+ bRet = (rVal >>= bVal); if (bRet) bProtection=bVal; break;
+ case MID_2 :
+ bRet = (rVal >>= bVal); if (bRet) bHideFormula=bVal; break;
+ case MID_3 :
+ bRet = (rVal >>= bVal); if (bRet) bHideCell=bVal; break;
+ case MID_4 :
+ bRet = (rVal >>= bVal); if (bRet) bHidePrint=bVal; break;
+ default:
+ OSL_FAIL("Wrong MemberID!");
+ }
+
+ return bRet;
+}
+
+OUString ScProtectionAttr::GetValueText() const
+{
+ const OUString aStrYes ( ScResId(STR_YES) );
+ const OUString aStrNo ( ScResId(STR_NO) );
+
+ const OUString aValue = "("
+ + (bProtection ? aStrYes : aStrNo)
+ + ","
+ + (bHideFormula ? aStrYes : aStrNo)
+ + ","
+ + (bHideCell ? aStrYes : aStrNo)
+ + ","
+ + (bHidePrint ? aStrYes : aStrNo)
+ + ")";
+
+ return aValue;
+}
+
+bool ScProtectionAttr::GetPresentation
+ (
+ SfxItemPresentation ePres,
+ MapUnit /* eCoreMetric */,
+ MapUnit /* ePresMetric */,
+ OUString& rText,
+ const IntlWrapper& /* rIntl */
+ ) const
+{
+ const OUString aStrYes ( ScResId(STR_YES) );
+ const OUString aStrNo ( ScResId(STR_NO) );
+
+ switch ( ePres )
+ {
+ case SfxItemPresentation::Nameless:
+ rText = GetValueText();
+ break;
+
+ case SfxItemPresentation::Complete:
+ rText = ScResId(STR_PROTECTION)
+ + ": "
+ + (bProtection ? aStrYes : aStrNo)
+ + ", "
+ + ScResId(STR_FORMULAS)
+ + ": "
+ + (!bHideFormula ? aStrYes : aStrNo)
+ + ", "
+ + ScResId(STR_HIDE)
+ + ": "
+ + (bHideCell ? aStrYes : aStrNo)
+ + ", "
+ + ScResId(STR_PRINT)
+ + ": "
+ + (!bHidePrint ? aStrYes : aStrNo);
+ break;
+
+ default: break;
+ }
+
+ return true;
+}
+
+bool ScProtectionAttr::operator==( const SfxPoolItem& rItem ) const
+{
+ return SfxPoolItem::operator==(rItem)
+ && (bProtection == static_cast<const ScProtectionAttr&>(rItem).bProtection)
+ && (bHideFormula == static_cast<const ScProtectionAttr&>(rItem).bHideFormula)
+ && (bHideCell == static_cast<const ScProtectionAttr&>(rItem).bHideCell)
+ && (bHidePrint == static_cast<const ScProtectionAttr&>(rItem).bHidePrint);
+}
+
+ScProtectionAttr* ScProtectionAttr::Clone( SfxItemPool * ) const
+{
+ return new ScProtectionAttr(*this);
+}
+
+void ScProtectionAttr::SetProtection( bool bProtect)
+{
+ bProtection = bProtect;
+}
+
+void ScProtectionAttr::SetHideFormula( bool bHFormula)
+{
+ bHideFormula = bHFormula;
+}
+
+void ScProtectionAttr::SetHideCell( bool bHCell)
+{
+ bHideCell = bHCell;
+}
+
+void ScProtectionAttr::SetHidePrint( bool bHPrint)
+{
+ bHidePrint = bHPrint;
+}
+
+void ScProtectionAttr::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("ScProtectionAttr"));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("protection"), BAD_CAST(OString::boolean(GetProtection()).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("hide-formula"), BAD_CAST(OString::boolean(GetHideFormula()).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("hide-cell"), BAD_CAST(OString::boolean(GetHideCell()).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("hide-print"), BAD_CAST(OString::boolean(GetHidePrint()).getStr()));
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+/**
+ * ScPageHFItem - Dates from the Head and Foot lines
+ */
+ScPageHFItem::ScPageHFItem( sal_uInt16 nWhichP )
+ : SfxPoolItem ( nWhichP )
+{
+}
+
+ScPageHFItem::ScPageHFItem( const ScPageHFItem& rItem )
+ : SfxPoolItem ( rItem )
+{
+ if ( rItem.pLeftArea )
+ pLeftArea = rItem.pLeftArea->Clone();
+ if ( rItem.pCenterArea )
+ pCenterArea = rItem.pCenterArea->Clone();
+ if ( rItem.pRightArea )
+ pRightArea = rItem.pRightArea->Clone();
+}
+
+ScPageHFItem::~ScPageHFItem()
+{
+}
+
+bool ScPageHFItem::QueryValue( uno::Any& rVal, sal_uInt8 /* nMemberId */ ) const
+{
+ rtl::Reference<ScHeaderFooterContentObj> xContent =
+ new ScHeaderFooterContentObj();
+ xContent->Init(pLeftArea.get(), pCenterArea.get(), pRightArea.get());
+
+ uno::Reference<sheet::XHeaderFooterContent> xCont(xContent);
+
+ rVal <<= xCont;
+ return true;
+}
+
+bool ScPageHFItem::PutValue( const uno::Any& rVal, sal_uInt8 /* nMemberId */ )
+{
+ bool bRet = false;
+ uno::Reference<sheet::XHeaderFooterContent> xContent;
+ if ( rVal >>= xContent )
+ {
+ if ( xContent.is() )
+ {
+ rtl::Reference<ScHeaderFooterContentObj> pImp =
+ ScHeaderFooterContentObj::getImplementation( xContent );
+ if (pImp.is())
+ {
+ const EditTextObject* pImpLeft = pImp->GetLeftEditObject();
+ pLeftArea.reset();
+ if (pImpLeft)
+ pLeftArea = pImpLeft->Clone();
+
+ const EditTextObject* pImpCenter = pImp->GetCenterEditObject();
+ pCenterArea.reset();
+ if (pImpCenter)
+ pCenterArea = pImpCenter->Clone();
+
+ const EditTextObject* pImpRight = pImp->GetRightEditObject();
+ pRightArea.reset();
+ if (pImpRight)
+ pRightArea = pImpRight->Clone();
+
+ if ( !pLeftArea || !pCenterArea || !pRightArea )
+ {
+ // no Text with Null are left
+ ScEditEngineDefaulter aEngine( EditEngine::CreatePool().get(), true );
+ if (!pLeftArea)
+ pLeftArea = aEngine.CreateTextObject();
+ if (!pCenterArea)
+ pCenterArea = aEngine.CreateTextObject();
+ if (!pRightArea)
+ pRightArea = aEngine.CreateTextObject();
+ }
+
+ bRet = true;
+ }
+ }
+ }
+
+ if (!bRet)
+ {
+ OSL_FAIL("exception - wrong argument");
+ }
+
+ return true;
+}
+
+bool ScPageHFItem::operator==( const SfxPoolItem& rItem ) const
+{
+ assert(SfxPoolItem::operator==(rItem));
+
+ const ScPageHFItem& r = static_cast<const ScPageHFItem&>(rItem);
+
+ return ScGlobal::EETextObjEqual(pLeftArea.get(), r.pLeftArea.get())
+ && ScGlobal::EETextObjEqual(pCenterArea.get(), r.pCenterArea.get())
+ && ScGlobal::EETextObjEqual(pRightArea.get(), r.pRightArea.get());
+}
+
+ScPageHFItem* ScPageHFItem::Clone( SfxItemPool* ) const
+{
+ return new ScPageHFItem( *this );
+}
+
+void ScPageHFItem::SetLeftArea( const EditTextObject& rNew )
+{
+ pLeftArea = rNew.Clone();
+}
+
+void ScPageHFItem::SetCenterArea( const EditTextObject& rNew )
+{
+ pCenterArea = rNew.Clone();
+}
+
+void ScPageHFItem::SetRightArea( const EditTextObject& rNew )
+{
+ pRightArea = rNew.Clone();
+}
+void ScPageHFItem::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("ScPageHFItem"));
+ GetLeftArea()->dumpAsXml(pWriter);
+ GetCenterArea()->dumpAsXml(pWriter);
+ GetRightArea()->dumpAsXml(pWriter);
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+/**
+ * ScViewObjectModeItem - Display Mode of View Objects
+ */
+ScViewObjectModeItem::ScViewObjectModeItem( sal_uInt16 nWhichP )
+ : SfxEnumItem( nWhichP, VOBJ_MODE_SHOW )
+{
+}
+
+ScViewObjectModeItem::ScViewObjectModeItem( sal_uInt16 nWhichP, ScVObjMode eMode )
+ : SfxEnumItem( nWhichP, eMode )
+{
+}
+
+ScViewObjectModeItem::~ScViewObjectModeItem()
+{
+}
+
+bool ScViewObjectModeItem::GetPresentation
+(
+ SfxItemPresentation ePres,
+ MapUnit /* eCoreUnit */,
+ MapUnit /* ePresUnit */,
+ OUString& rText,
+ const IntlWrapper& /* rIntl */
+) const
+{
+ OUString aDel(": ");
+ rText.clear();
+
+ switch ( ePres )
+ {
+ case SfxItemPresentation::Complete:
+ switch( Which() )
+ {
+ case SID_SCATTR_PAGE_CHARTS:
+ rText = ScResId(STR_VOBJ_CHART) + aDel;
+ break;
+
+ case SID_SCATTR_PAGE_OBJECTS:
+ rText = ScResId(STR_VOBJ_OBJECT) + aDel;
+ break;
+
+ case SID_SCATTR_PAGE_DRAWINGS:
+ rText = ScResId(STR_VOBJ_DRAWINGS) + aDel;
+ break;
+
+ default: break;
+ }
+ [[fallthrough]];
+ case SfxItemPresentation::Nameless:
+ if (GetValue() == VOBJ_MODE_SHOW)
+ rText += ScResId(STR_VOBJ_MODE_SHOW);
+ else
+ rText += ScResId(STR_VOBJ_MODE_HIDE);
+ return true;
+
+ default: break;
+ // added to avoid warnings
+ }
+
+ return false;
+}
+
+sal_uInt16 ScViewObjectModeItem::GetValueCount() const
+{
+ return 2;
+}
+
+ScViewObjectModeItem* ScViewObjectModeItem::Clone( SfxItemPool* ) const
+{
+ return new ScViewObjectModeItem( *this );
+}
+
+ScPageScaleToItem::ScPageScaleToItem() :
+ SfxPoolItem( ATTR_PAGE_SCALETO ),
+ mnWidth( 0 ),
+ mnHeight( 0 )
+{
+}
+
+ScPageScaleToItem::ScPageScaleToItem( sal_uInt16 nWidth, sal_uInt16 nHeight ) :
+ SfxPoolItem( ATTR_PAGE_SCALETO ),
+ mnWidth( nWidth ),
+ mnHeight( nHeight )
+{
+}
+
+ScPageScaleToItem::~ScPageScaleToItem()
+{
+}
+
+ScPageScaleToItem* ScPageScaleToItem::Clone( SfxItemPool* ) const
+{
+ return new ScPageScaleToItem( *this );
+}
+
+bool ScPageScaleToItem::operator==( const SfxPoolItem& rCmp ) const
+{
+ assert(SfxPoolItem::operator==(rCmp));
+ const ScPageScaleToItem& rPageCmp = static_cast< const ScPageScaleToItem& >( rCmp );
+ return (mnWidth == rPageCmp.mnWidth) && (mnHeight == rPageCmp.mnHeight);
+}
+
+namespace {
+void lclAppendScalePageCount( OUString& rText, sal_uInt16 nPages )
+{
+ rText += ": ";
+ if( nPages )
+ {
+ OUString aPages(ScResId(STR_SCATTR_PAGE_SCALE_PAGES, nPages));
+ rText += aPages.replaceFirst( "%1", OUString::number( nPages ) );
+ }
+ else
+ rText += ScResId( STR_SCATTR_PAGE_SCALE_AUTO );
+}
+} // namespace
+
+bool ScPageScaleToItem::GetPresentation(
+ SfxItemPresentation ePres, MapUnit, MapUnit, OUString& rText, const IntlWrapper& ) const
+{
+ rText.clear();
+ if( !IsValid())
+ return false;
+
+ OUString aName( ScResId( STR_SCATTR_PAGE_SCALETO ) );
+ OUString aValue( ScResId( STR_SCATTR_PAGE_SCALE_WIDTH ) );
+ lclAppendScalePageCount( aValue, mnWidth );
+ aValue += ", " + ScResId( STR_SCATTR_PAGE_SCALE_HEIGHT );
+ lclAppendScalePageCount( aValue, mnHeight );
+
+ switch( ePres )
+ {
+ case SfxItemPresentation::Nameless:
+ rText = aValue;
+ return true;
+
+ case SfxItemPresentation::Complete:
+ rText = aName + " (" + aValue + ")";
+ return true;
+
+ default:
+ OSL_FAIL( "ScPageScaleToItem::GetPresentation - unknown presentation mode" );
+ }
+ return false;
+}
+
+bool ScPageScaleToItem::QueryValue( uno::Any& rAny, sal_uInt8 nMemberId ) const
+{
+ bool bRet = true;
+ switch( nMemberId )
+ {
+ case SC_MID_PAGE_SCALETO_WIDTH: rAny <<= mnWidth; break;
+ case SC_MID_PAGE_SCALETO_HEIGHT: rAny <<= mnHeight; break;
+ default:
+ OSL_FAIL( "ScPageScaleToItem::QueryValue - unknown member ID" );
+ bRet = false;
+ }
+ return bRet;
+}
+
+bool ScPageScaleToItem::PutValue( const uno::Any& rAny, sal_uInt8 nMemberId )
+{
+ bool bRet = false;
+ switch( nMemberId )
+ {
+ case SC_MID_PAGE_SCALETO_WIDTH: bRet = rAny >>= mnWidth; break;
+ case SC_MID_PAGE_SCALETO_HEIGHT: bRet = rAny >>= mnHeight; break;
+ default:
+ OSL_FAIL( "ScPageScaleToItem::PutValue - unknown member ID" );
+ }
+ return bRet;
+}
+void ScPageScaleToItem::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("ScPageScaleToItem"));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("width"), BAD_CAST(OString::number(GetWidth()).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("height"), BAD_CAST(OString::number(GetHeight()).getStr()));
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+ScCondFormatItem::ScCondFormatItem():
+ SfxPoolItem( ATTR_CONDITIONAL )
+{
+}
+
+ScCondFormatItem::ScCondFormatItem( sal_uInt32 nIndex ):
+ SfxPoolItem( ATTR_CONDITIONAL )
+{
+ maIndex.insert(nIndex);
+}
+
+ScCondFormatItem::ScCondFormatItem( const ScCondFormatIndexes& rIndex ):
+ SfxPoolItem( ATTR_CONDITIONAL ),
+ maIndex( rIndex )
+{
+}
+
+ScCondFormatItem::ScCondFormatItem( ScCondFormatIndexes&& aIndex ) noexcept:
+ SfxPoolItem( ATTR_CONDITIONAL ),
+ maIndex( std::move(aIndex) )
+{
+}
+
+ScCondFormatItem::~ScCondFormatItem()
+{
+}
+
+bool ScCondFormatItem::operator==( const SfxPoolItem& rCmp ) const
+{
+ if (!SfxPoolItem::operator==(rCmp))
+ return false;
+ auto const & other = static_cast<const ScCondFormatItem&>(rCmp);
+ if (maIndex.empty() && other.maIndex.empty())
+ return true;
+ // memcmp is faster than operator== on std::vector
+ return maIndex.size() == other.maIndex.size()
+ && memcmp(&maIndex.front(), &other.maIndex.front(), maIndex.size() * sizeof(sal_uInt32)) == 0;
+}
+
+bool ScCondFormatItem::operator<( const SfxPoolItem& rCmp ) const
+{
+ auto const & other = static_cast<const ScCondFormatItem&>(rCmp);
+ if ( maIndex.size() < other.maIndex.size() )
+ return true;
+ if ( maIndex.size() > other.maIndex.size() )
+ return false;
+ if (maIndex.empty() && other.maIndex.empty())
+ return false;
+ // memcmp is faster than operator< on std::vector
+ // Note that on little-endian this results in a confusing ordering (256 < 1),
+ // which technically doesn't matter as the ordering may be arbitrary.
+ return memcmp(&maIndex.front(), &other.maIndex.front(), maIndex.size() * sizeof(sal_uInt32)) < 0;
+}
+
+ScCondFormatItem* ScCondFormatItem::Clone(SfxItemPool*) const
+{
+ return new ScCondFormatItem(maIndex);
+}
+
+void ScCondFormatItem::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("ScCondFormatItem"));
+ for (const auto& nItem : maIndex)
+ {
+ std::string aStrVal = std::to_string(nItem);
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST(aStrVal.c_str()));
+ (void)xmlTextWriterEndElement(pWriter);
+ }
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+ScRotateValueItem::ScRotateValueItem(Degree100 nAngle)
+ : SdrAngleItem(ATTR_ROTATE_VALUE, nAngle)
+{
+}
+
+ScRotateValueItem* ScRotateValueItem::Clone(SfxItemPool*) const
+{
+ return new ScRotateValueItem(GetValue());
+}
+
+bool ScRotateValueItem::GetPresentation(SfxItemPresentation ePresentation,
+ MapUnit eCoreMetric, MapUnit ePresMetric,
+ OUString& rText,
+ const IntlWrapper& rWrapper) const
+{
+ bool bRet = SdrAngleItem::GetPresentation(SfxItemPresentation::Nameless, eCoreMetric, ePresMetric, rText, rWrapper);
+ if (bRet && ePresentation == SfxItemPresentation::Complete)
+ rText = ScResId(STR_TEXTORIENTANGLE) + " " + rText;
+ return bRet;
+}
+
+ScShrinkToFitCell::ScShrinkToFitCell(bool bShrink)
+ : SfxBoolItem(ATTR_SHRINKTOFIT, bShrink)
+{
+}
+
+ScShrinkToFitCell* ScShrinkToFitCell::Clone(SfxItemPool*) const
+{
+ return new ScShrinkToFitCell(GetValue());
+}
+
+bool ScShrinkToFitCell::GetPresentation(SfxItemPresentation,
+ MapUnit, MapUnit,
+ OUString& rText,
+ const IntlWrapper&) const
+{
+ TranslateId pId = GetValue() ? STR_SHRINKTOFITCELL_ON : STR_SHRINKTOFITCELL_OFF;
+ rText = ScResId(pId);
+ return true;
+}
+
+ScVerticalStackCell::ScVerticalStackCell(bool bStack)
+ : SfxBoolItem(ATTR_STACKED, bStack)
+{
+}
+
+ScVerticalStackCell* ScVerticalStackCell::Clone(SfxItemPool*) const
+{
+ return new ScVerticalStackCell(GetValue());
+}
+
+bool ScVerticalStackCell::GetPresentation(SfxItemPresentation,
+ MapUnit, MapUnit,
+ OUString& rText,
+ const IntlWrapper&) const
+{
+ TranslateId pId = GetValue() ? STR_VERTICALSTACKCELL_ON : STR_VERTICALSTACKCELL_OFF;
+ rText = ScResId(pId);
+ return true;
+}
+
+ScLineBreakCell::ScLineBreakCell(bool bStack)
+ : SfxBoolItem(ATTR_LINEBREAK, bStack)
+{
+}
+
+ScLineBreakCell* ScLineBreakCell::Clone(SfxItemPool*) const
+{
+ return new ScLineBreakCell(GetValue());
+}
+
+bool ScLineBreakCell::GetPresentation(SfxItemPresentation,
+ MapUnit, MapUnit,
+ OUString& rText,
+ const IntlWrapper&) const
+{
+ TranslateId pId = GetValue() ? STR_LINEBREAKCELL_ON : STR_LINEBREAKCELL_OFF;
+ rText = ScResId(pId);
+ return true;
+}
+
+ScHyphenateCell::ScHyphenateCell(bool bHyphenate)
+ : SfxBoolItem(ATTR_HYPHENATE, bHyphenate)
+{
+}
+
+ScHyphenateCell* ScHyphenateCell::Clone(SfxItemPool*) const
+{
+ return new ScHyphenateCell(GetValue());
+}
+
+bool ScHyphenateCell::GetPresentation(SfxItemPresentation,
+ MapUnit, MapUnit,
+ OUString& rText,
+ const IntlWrapper&) const
+{
+ TranslateId pId = GetValue() ? STR_HYPHENATECELL_ON : STR_HYPHENATECELL_OFF;
+ rText = ScResId(pId);
+ return true;
+}
+
+ScIndentItem::ScIndentItem(sal_uInt16 nIndent)
+ : SfxUInt16Item(ATTR_INDENT, nIndent)
+{
+}
+
+ScIndentItem* ScIndentItem::Clone(SfxItemPool*) const
+{
+ return new ScIndentItem(GetValue());
+}
+
+bool ScIndentItem::GetPresentation(SfxItemPresentation ePres,
+ MapUnit eCoreUnit, MapUnit,
+ OUString& rText,
+ const IntlWrapper& rIntl) const
+{
+ auto nValue = GetValue();
+
+ switch (ePres)
+ {
+ case SfxItemPresentation::Complete:
+ rText = ScResId(STR_INDENTCELL);
+ [[fallthrough]];
+ case SfxItemPresentation::Nameless:
+ rText += GetMetricText( nValue, eCoreUnit, MapUnit::MapPoint, &rIntl ) +
+ " " + EditResId(GetMetricId(MapUnit::MapPoint));
+ return true;
+ default: ; //prevent warning
+ }
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/autonamecache.cxx b/sc/source/core/data/autonamecache.cxx
new file mode 100644
index 000000000..f74219baf
--- /dev/null
+++ b/sc/source/core/data/autonamecache.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/.
+ *
+ * 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/transliterationwrapper.hxx>
+
+#include <autonamecache.hxx>
+#include <dociter.hxx>
+#include <formulacell.hxx>
+#include <editutil.hxx>
+
+ScAutoNameCache::ScAutoNameCache( ScDocument& rD ) :
+ rDoc( rD ),
+ nCurrentTab( 0 ) // doesn't matter - aNames is empty
+{
+}
+
+ScAutoNameCache::~ScAutoNameCache()
+{
+}
+
+const ScAutoNameAddresses& ScAutoNameCache::GetNameOccurrences( const OUString& rName, SCTAB nTab )
+{
+ if ( nTab != nCurrentTab )
+ {
+ // the lists are valid only for one sheet, so they are cleared when another sheet is used
+ aNames.clear();
+ nCurrentTab = nTab;
+ }
+
+ ScAutoNameHashMap::const_iterator aFound = aNames.find( rName );
+ if ( aFound != aNames.end() )
+ return aFound->second; // already initialized
+
+ ScAutoNameAddresses& rAddresses = aNames[rName];
+
+ ScCellIterator aIter( rDoc, ScRange( 0, 0, nCurrentTab, rDoc.MaxCol(), rDoc.MaxRow(), nCurrentTab ) );
+ for (bool bHasCell = aIter.first(); bHasCell; bHasCell = aIter.next())
+ {
+ // don't check code length here, always use the stored result
+ // (AutoCalc is disabled during CompileXML)
+ if (aIter.hasString())
+ {
+ OUString aStr;
+ switch (aIter.getType())
+ {
+ case CELLTYPE_STRING:
+ aStr = aIter.getString();
+ break;
+ case CELLTYPE_FORMULA:
+ aStr = aIter.getFormulaCell()->GetString().getString();
+ break;
+ case CELLTYPE_EDIT:
+ {
+ const EditTextObject* p = aIter.getEditText();
+ if (p)
+ aStr = ScEditUtil::GetMultilineString(*p); // string with line separators between paragraphs
+ }
+ break;
+ case CELLTYPE_NONE:
+ case CELLTYPE_VALUE:
+ ; // nothing, prevent compiler warning
+ break;
+ }
+ if ( ScGlobal::GetTransliteration().isEqual( aStr, rName ) )
+ {
+ rAddresses.push_back(aIter.GetPos());
+ }
+ }
+ }
+
+ return rAddresses;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/bcaslot.cxx b/sc/source/core/data/bcaslot.cxx
new file mode 100644
index 000000000..094939634
--- /dev/null
+++ b/sc/source/core/data/bcaslot.cxx
@@ -0,0 +1,1300 @@
+/* -*- 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 <svl/listener.hxx>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <document.hxx>
+#include <brdcst.hxx>
+#include <bcaslot.hxx>
+#include <scerrors.hxx>
+#include <refupdat.hxx>
+#include <bulkdatahint.hxx>
+#include <columnspanset.hxx>
+
+#if DEBUG_AREA_BROADCASTER
+#include <formulacell.hxx>
+#include <grouparealistener.hxx>
+#endif
+
+ScBroadcastArea::ScBroadcastArea( const ScRange& rRange ) :
+ pUpdateChainNext(nullptr),
+ aRange(rRange),
+ nRefCount(0),
+ mbInUpdateChain(false),
+ mbGroupListening(false) {}
+
+ScBroadcastAreaSlot::ScBroadcastAreaSlot( ScDocument* pDocument,
+ ScBroadcastAreaSlotMachine* pBASMa ) :
+ aTmpSeekBroadcastArea( ScRange()),
+ pDoc( pDocument ),
+ pBASM( pBASMa ),
+ mbInBroadcastIteration( false),
+ mbHasErasedArea(false)
+{
+}
+
+ScBroadcastAreaSlot::~ScBroadcastAreaSlot()
+{
+ for ( ScBroadcastAreas::iterator aIter( aBroadcastAreaTbl.begin());
+ aIter != aBroadcastAreaTbl.end(); /* none */)
+ {
+ // Prevent hash from accessing dangling pointer in case area is
+ // deleted.
+ ScBroadcastArea* pArea = (*aIter).mpArea;
+ // Erase all so no hash will be accessed upon destruction of the
+ // unordered_map.
+ aIter = aBroadcastAreaTbl.erase(aIter);
+ if (!pArea->DecRef())
+ delete pArea;
+ }
+}
+
+ScDocument::HardRecalcState ScBroadcastAreaSlot::CheckHardRecalcStateCondition() const
+{
+ ScDocument::HardRecalcState eState = pDoc->GetHardRecalcState();
+ if (eState == ScDocument::HardRecalcState::OFF)
+ {
+ if (aBroadcastAreaTbl.size() >= aBroadcastAreaTbl.max_size())
+ { // this is more hypothetical now, check existed for old SV_PTRARR_SORT
+ SfxObjectShell* pShell = pDoc->GetDocumentShell();
+ OSL_ENSURE( pShell, "Missing DocShell :-/" );
+
+ if ( pShell )
+ pShell->SetError(SCWARN_CORE_HARD_RECALC);
+
+ pDoc->SetAutoCalc( false );
+ eState = ScDocument::HardRecalcState::ETERNAL;
+ pDoc->SetHardRecalcState( eState );
+ }
+ }
+ return eState;
+}
+
+bool ScBroadcastAreaSlot::StartListeningArea(
+ const ScRange& rRange, bool bGroupListening, SvtListener* pListener, ScBroadcastArea*& rpArea )
+{
+ bool bNewArea = false;
+ OSL_ENSURE(pListener, "StartListeningArea: pListener Null");
+ assert(!pDoc->IsDelayedFormulaGrouping()); // otherwise the group size might be incorrect
+ if (CheckHardRecalcStateCondition() == ScDocument::HardRecalcState::ETERNAL)
+ return false;
+ if ( !rpArea )
+ {
+ // Even if most times the area doesn't exist yet and immediately trying
+ // to new and insert it would save an attempt to find it, on massive
+ // operations like identical large [HV]LOOKUP() areas the new/delete
+ // would add quite some penalty for all but the first formula cell.
+ ScBroadcastAreas::const_iterator aIter( FindBroadcastArea( rRange, bGroupListening));
+ if (aIter != aBroadcastAreaTbl.end())
+ rpArea = (*aIter).mpArea;
+ else
+ {
+ rpArea = new ScBroadcastArea( rRange);
+ rpArea->SetGroupListening(bGroupListening);
+ if (aBroadcastAreaTbl.insert( rpArea).second)
+ {
+ rpArea->IncRef();
+ bNewArea = true;
+ }
+ else
+ {
+ OSL_FAIL("StartListeningArea: area not found and not inserted in slot?!?");
+ delete rpArea;
+ rpArea = nullptr;
+ }
+ }
+ if (rpArea)
+ pListener->StartListening( rpArea->GetBroadcaster());
+ }
+ else
+ {
+ if (aBroadcastAreaTbl.insert( rpArea).second)
+ rpArea->IncRef();
+ }
+ return bNewArea;
+}
+
+void ScBroadcastAreaSlot::InsertListeningArea( ScBroadcastArea* pArea )
+{
+ OSL_ENSURE( pArea, "InsertListeningArea: pArea NULL");
+ if (CheckHardRecalcStateCondition() == ScDocument::HardRecalcState::ETERNAL)
+ return;
+ if (aBroadcastAreaTbl.insert( pArea).second)
+ pArea->IncRef();
+}
+
+// If rpArea != NULL then no listeners are stopped, only the area is removed
+// and the reference count decremented.
+void ScBroadcastAreaSlot::EndListeningArea(
+ const ScRange& rRange, bool bGroupListening, SvtListener* pListener, ScBroadcastArea*& rpArea )
+{
+ OSL_ENSURE(pListener, "EndListeningArea: pListener Null");
+ if ( !rpArea )
+ {
+ ScBroadcastAreas::iterator aIter( FindBroadcastArea( rRange, bGroupListening));
+ if (aIter == aBroadcastAreaTbl.end() || isMarkedErased( aIter))
+ return;
+ rpArea = (*aIter).mpArea;
+ pListener->EndListening( rpArea->GetBroadcaster() );
+ if ( !rpArea->GetBroadcaster().HasListeners() )
+ { // if nobody is listening we can dispose it
+ if (rpArea->GetRef() == 1)
+ rpArea = nullptr; // will be deleted by erase
+ EraseArea( aIter);
+ }
+ }
+ else
+ {
+ if (rpArea && !rpArea->GetBroadcaster().HasListeners())
+ {
+ ScBroadcastAreas::iterator aIter( FindBroadcastArea( rRange, bGroupListening));
+ if (aIter == aBroadcastAreaTbl.end() || isMarkedErased( aIter))
+ return;
+ OSL_ENSURE( (*aIter).mpArea == rpArea, "EndListeningArea: area pointer mismatch");
+ if (rpArea->GetRef() == 1)
+ rpArea = nullptr; // will be deleted by erase
+ EraseArea( aIter);
+ }
+ }
+}
+
+ScBroadcastAreas::iterator ScBroadcastAreaSlot::FindBroadcastArea(
+ const ScRange& rRange, bool bGroupListening )
+{
+ aTmpSeekBroadcastArea.UpdateRange( rRange);
+ aTmpSeekBroadcastArea.SetGroupListening(bGroupListening);
+ return aBroadcastAreaTbl.find( &aTmpSeekBroadcastArea);
+}
+
+namespace {
+
+void broadcastRangeByCell( SvtBroadcaster& rBC, const ScRange& rRange, SfxHintId nHint )
+{
+ ScHint aHint(nHint, ScAddress());
+ for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab)
+ {
+ aHint.SetAddressTab(nTab);
+ for (SCCOL nCol = rRange.aStart.Col(); nCol <= rRange.aEnd.Col(); ++nCol)
+ {
+ aHint.SetAddressCol(nCol);
+ for (SCROW nRow = rRange.aStart.Row(); nRow <= rRange.aEnd.Row(); ++nRow)
+ {
+ aHint.SetAddressRow(nRow);
+ rBC.Broadcast(aHint);
+ }
+ }
+ }
+}
+
+}
+
+bool ScBroadcastAreaSlot::AreaBroadcast( const ScRange& rRange, SfxHintId nHint )
+{
+ if (aBroadcastAreaTbl.empty())
+ return false;
+
+ bool bInBroadcast = mbInBroadcastIteration;
+ mbInBroadcastIteration = true;
+ bool bIsBroadcasted = false;
+
+ mbHasErasedArea = false;
+
+ for (ScBroadcastAreas::const_iterator aIter( aBroadcastAreaTbl.begin()),
+ aIterEnd( aBroadcastAreaTbl.end()); aIter != aIterEnd; ++aIter )
+ {
+ if (mbHasErasedArea && isMarkedErased( aIter))
+ continue;
+
+ ScBroadcastArea* pArea = (*aIter).mpArea;
+ const ScRange& rAreaRange = pArea->GetRange();
+
+ // Take the intersection of the area range and the broadcast range.
+ ScRange aIntersection = rAreaRange.Intersection(rRange);
+ if (!aIntersection.IsValid())
+ continue;
+
+ if (pArea->IsGroupListening())
+ {
+ if (pBASM->IsInBulkBroadcast())
+ {
+ pBASM->InsertBulkGroupArea(pArea, aIntersection);
+ }
+ else
+ {
+ broadcastRangeByCell(pArea->GetBroadcaster(), aIntersection, nHint);
+ bIsBroadcasted = true;
+ }
+ }
+ else if (!pBASM->IsInBulkBroadcast() || pBASM->InsertBulkArea( pArea))
+ {
+ broadcastRangeByCell(pArea->GetBroadcaster(), aIntersection, nHint);
+ bIsBroadcasted = true;
+ }
+ }
+
+ mbInBroadcastIteration = bInBroadcast;
+
+ // A Notify() during broadcast may call EndListeningArea() and thus dispose
+ // an area if it was the last listener, which would invalidate an iterator
+ // pointing to it, hence the real erase is done afterwards.
+ FinallyEraseAreas();
+
+ return bIsBroadcasted;
+}
+
+bool ScBroadcastAreaSlot::AreaBroadcast( const ScHint& rHint)
+{
+ if (aBroadcastAreaTbl.empty())
+ return false;
+
+ bool bInBroadcast = mbInBroadcastIteration;
+ mbInBroadcastIteration = true;
+ bool bIsBroadcasted = false;
+
+ mbHasErasedArea = false;
+
+ const ScRange& rRange = rHint.GetRange();
+ for (ScBroadcastAreas::const_iterator aIter( aBroadcastAreaTbl.begin()),
+ aIterEnd( aBroadcastAreaTbl.end()); aIter != aIterEnd; ++aIter )
+ {
+ if (mbHasErasedArea && isMarkedErased( aIter))
+ continue;
+
+ ScBroadcastArea* pArea = (*aIter).mpArea;
+ const ScRange& rAreaRange = pArea->GetRange();
+ if (rAreaRange.Intersects( rRange))
+ {
+ if (pArea->IsGroupListening())
+ {
+ if (pBASM->IsInBulkBroadcast())
+ {
+ pBASM->InsertBulkGroupArea(pArea, rRange);
+ }
+ else
+ {
+ pArea->GetBroadcaster().Broadcast( rHint);
+ bIsBroadcasted = true;
+ }
+ }
+ else if (!pBASM->IsInBulkBroadcast() || pBASM->InsertBulkArea( pArea))
+ {
+ pArea->GetBroadcaster().Broadcast( rHint);
+ bIsBroadcasted = true;
+ }
+ }
+ }
+
+ mbInBroadcastIteration = bInBroadcast;
+
+ // A Notify() during broadcast may call EndListeningArea() and thus dispose
+ // an area if it was the last listener, which would invalidate an iterator
+ // pointing to it, hence the real erase is done afterwards.
+ FinallyEraseAreas();
+
+ return bIsBroadcasted;
+}
+
+void ScBroadcastAreaSlot::DelBroadcastAreasInRange( const ScRange& rRange )
+{
+ if (aBroadcastAreaTbl.empty())
+ return;
+ for (ScBroadcastAreas::iterator aIter( aBroadcastAreaTbl.begin());
+ aIter != aBroadcastAreaTbl.end(); /* increment in body */ )
+ {
+ const ScRange& rAreaRange = (*aIter).mpArea->GetRange();
+ if (rRange.Contains( rAreaRange))
+ {
+ ScBroadcastArea* pArea = (*aIter).mpArea;
+ aIter = aBroadcastAreaTbl.erase(aIter); // erase before modifying
+ if (!pArea->DecRef())
+ {
+ if (pBASM->IsInBulkBroadcast())
+ pBASM->RemoveBulkArea( pArea);
+ delete pArea;
+ }
+ }
+ else
+ ++aIter;
+ }
+}
+
+void ScBroadcastAreaSlot::UpdateRemove( UpdateRefMode eUpdateRefMode,
+ const ScRange& rRange, SCCOL nDx, SCROW nDy, SCTAB nDz )
+{
+ if (aBroadcastAreaTbl.empty())
+ return;
+
+ SCCOL nCol1, nCol2, theCol1, theCol2;
+ SCROW nRow1, nRow2, theRow1, theRow2;
+ SCTAB nTab1, nTab2, theTab1, theTab2;
+ rRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
+ for ( ScBroadcastAreas::iterator aIter( aBroadcastAreaTbl.begin());
+ aIter != aBroadcastAreaTbl.end(); /* increment in body */ )
+ {
+ ScBroadcastArea* pArea = (*aIter).mpArea;
+ if ( pArea->IsInUpdateChain() )
+ {
+ aIter = aBroadcastAreaTbl.erase(aIter);
+ pArea->DecRef();
+ }
+ else
+ {
+ pArea->GetRange().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 ))
+ {
+ aIter = aBroadcastAreaTbl.erase(aIter);
+ pArea->DecRef();
+ if (pBASM->IsInBulkBroadcast())
+ pBASM->RemoveBulkArea( pArea);
+ pArea->SetInUpdateChain( true );
+ ScBroadcastArea* pUC = pBASM->GetEOUpdateChain();
+ if ( pUC )
+ pUC->SetUpdateChainNext( pArea );
+ else // no tail => no head
+ pBASM->SetUpdateChain( pArea );
+ pBASM->SetEOUpdateChain( pArea );
+ }
+ else
+ ++aIter;
+ }
+ }
+}
+
+void ScBroadcastAreaSlot::UpdateRemoveArea( ScBroadcastArea* pArea )
+{
+ ScBroadcastAreas::iterator aIter( aBroadcastAreaTbl.find( pArea));
+ if (aIter == aBroadcastAreaTbl.end())
+ return;
+ if ((*aIter).mpArea != pArea)
+ OSL_FAIL( "UpdateRemoveArea: area pointer mismatch");
+ else
+ {
+ aBroadcastAreaTbl.erase( aIter);
+ pArea->DecRef();
+ }
+}
+
+void ScBroadcastAreaSlot::UpdateInsert( ScBroadcastArea* pArea )
+{
+ ::std::pair< ScBroadcastAreas::iterator, bool > aPair =
+ aBroadcastAreaTbl.insert( pArea);
+ if (aPair.second)
+ pArea->IncRef();
+ else
+ {
+ // Identical area already exists, add listeners.
+ ScBroadcastArea* pTarget = (*(aPair.first)).mpArea;
+ if (pArea != pTarget)
+ {
+ SvtBroadcaster& rTarget = pTarget->GetBroadcaster();
+ SvtBroadcaster::ListenersType& rListeners = pArea->GetBroadcaster().GetAllListeners();
+ for (auto& pListener : rListeners)
+ {
+ SvtListener& rListener = *pListener;
+ rListener.StartListening(rTarget);
+ }
+ }
+ }
+}
+
+void ScBroadcastAreaSlot::EraseArea( ScBroadcastAreas::iterator& rIter )
+{
+ if (mbInBroadcastIteration)
+ {
+ (*rIter).mbErasure = true; // mark for erasure
+ mbHasErasedArea = true; // at least one area is marked for erasure.
+ pBASM->PushAreaToBeErased( this, rIter);
+ }
+ else
+ {
+ ScBroadcastArea* pArea = (*rIter).mpArea;
+ aBroadcastAreaTbl.erase( rIter);
+ if (!pArea->DecRef())
+ {
+ if (pBASM->IsInBulkBroadcast())
+ pBASM->RemoveBulkGroupArea(pArea);
+ delete pArea;
+ }
+ }
+}
+
+void ScBroadcastAreaSlot::GetAllListeners(
+ const ScRange& rRange, std::vector<sc::AreaListener>& rListeners,
+ sc::AreaOverlapType eType, sc::ListenerGroupType eGroup )
+{
+ for (ScBroadcastAreas::const_iterator aIter( aBroadcastAreaTbl.begin()),
+ aIterEnd( aBroadcastAreaTbl.end()); aIter != aIterEnd; ++aIter )
+ {
+ if (isMarkedErased( aIter))
+ continue;
+
+ ScBroadcastArea* pArea = (*aIter).mpArea;
+ const ScRange& rAreaRange = pArea->GetRange();
+ switch (eGroup)
+ {
+ case sc::ListenerGroupType::Group:
+ if (!pArea->IsGroupListening())
+ continue;
+ break;
+ case sc::ListenerGroupType::Both:
+ default:
+ ;
+ }
+
+ switch (eType)
+ {
+ case sc::AreaOverlapType::Inside:
+ if (!rRange.Contains(rAreaRange))
+ // The range needs to be fully inside specified range.
+ continue;
+ break;
+ case sc::AreaOverlapType::InsideOrOverlap:
+ if (!rRange.Intersects(rAreaRange))
+ // The range needs to be partially overlapping or fully inside.
+ continue;
+ break;
+ case sc::AreaOverlapType::OneRowInside:
+ if (rAreaRange.aStart.Row() != rAreaRange.aEnd.Row() || !rRange.Contains(rAreaRange))
+ // The range needs to be one single row and fully inside
+ // specified range.
+ continue;
+ break;
+ case sc::AreaOverlapType::OneColumnInside:
+ if (rAreaRange.aStart.Col() != rAreaRange.aEnd.Col() || !rRange.Contains(rAreaRange))
+ // The range needs to be one single column and fully inside
+ // specified range.
+ continue;
+ break;
+ }
+
+ SvtBroadcaster::ListenersType& rLst = pArea->GetBroadcaster().GetAllListeners();
+ for (const auto& pListener : rLst)
+ {
+ sc::AreaListener aEntry;
+ aEntry.maArea = rAreaRange;
+ aEntry.mbGroupListening = pArea->IsGroupListening();
+ aEntry.mpListener = pListener;
+ rListeners.push_back(aEntry);
+ }
+ }
+}
+
+#if DEBUG_AREA_BROADCASTER
+void ScBroadcastAreaSlot::Dump() const
+{
+ for (const ScBroadcastAreaEntry& rEntry : aBroadcastAreaTbl)
+ {
+ const ScBroadcastArea* pArea = rEntry.mpArea;
+ const SvtBroadcaster& rBC = pArea->GetBroadcaster();
+ const SvtBroadcaster::ListenersType& rListeners = rBC.GetAllListeners();
+ size_t n = rListeners.size();
+
+ cout << " * range: " << OUStringToOString(pArea->GetRange().Format(ScRefFlags::VALID|ScRefFlags::TAB_3D, pDoc), RTL_TEXTENCODING_UTF8).getStr()
+ << ", group: " << pArea->IsGroupListening()
+ << ", listener count: " << n << endl;
+
+ for (size_t i = 0; i < n; ++i)
+ {
+ const ScFormulaCell* pFC = dynamic_cast<const ScFormulaCell*>(rListeners[i]);
+ if (pFC)
+ {
+ cout << " * listener: formula cell: "
+ << OUStringToOString(pFC->aPos.Format(ScRefFlags::VALID|ScRefFlags::TAB_3D, pDoc), RTL_TEXTENCODING_UTF8).getStr()
+ << endl;
+ continue;
+ }
+
+ const sc::FormulaGroupAreaListener* pFGListener = dynamic_cast<const sc::FormulaGroupAreaListener*>(rListeners[i]);
+ if (pFGListener)
+ {
+ cout << " * listener: formula group: (pos: "
+ << OUStringToOString(pFGListener->getTopCellPos().Format(ScRefFlags::VALID | ScRefFlags::TAB_3D, pDoc), RTL_TEXTENCODING_UTF8).getStr()
+ << ", length: " << pFGListener->getGroupLength()
+ << ")" << endl;
+ continue;
+ }
+
+ cout << " * listener: unknown" << endl;
+ }
+ }
+}
+#endif
+
+void ScBroadcastAreaSlot::FinallyEraseAreas()
+{
+ pBASM->FinallyEraseAreas( this);
+}
+
+// --- ScBroadcastAreaSlotMachine -------------------------------------
+
+ScBroadcastAreaSlotMachine::TableSlots::TableSlots(SCSIZE nBcaSlots)
+ : mnBcaSlots(nBcaSlots)
+{
+ ppSlots.reset( new ScBroadcastAreaSlot* [ nBcaSlots ] );
+ memset( ppSlots.get(), 0 , sizeof( ScBroadcastAreaSlot* ) * nBcaSlots );
+}
+
+ScBroadcastAreaSlotMachine::TableSlots::~TableSlots()
+{
+ for ( ScBroadcastAreaSlot** pp = ppSlots.get() + mnBcaSlots; --pp >= ppSlots.get(); /* nothing */ )
+ delete *pp;
+}
+
+ScBroadcastAreaSlotMachine::ScBroadcastAreaSlotMachine(
+ ScDocument* pDocument ) :
+ pDoc( pDocument ),
+ pUpdateChain( nullptr ),
+ pEOUpdateChain( nullptr ),
+ nInBulkBroadcast( 0 )
+{
+ // initSlotDistribution ---------
+ // Logarithmic or any other distribution.
+ // Upper and leftmost sheet part usually is more populated and referenced and gets fine
+ // grained resolution, larger data in larger hunks.
+ // Just like with cells, slots are organized in columns. Slot 0 is for first nSliceRow x nSliceCol
+ // cells, slot 1 is for next nSliceRow x nSliceCel cells below, etc. After a while the size of row
+ // slice doubles (making more cells share the same slot), this distribution data is stored
+ // in ScSlotData including ranges of cells. This is repeated for another column of nSliceCol cells,
+ // again with the column slice doubling after some time.
+ // Functions ComputeSlotOffset(), ComputeArePoints() and ComputeNextSlot() do the necessary
+ // calculations.
+ SCSIZE nSlots = 0;
+ // This should be SCCOL, but that's only 16bit and would overflow when doubling 16k columns.
+ sal_Int32 nCol1 = 0;
+ sal_Int32 nCol2 = 1024;
+ SCSIZE nSliceCol = 16;
+ while (nCol2 <= pDoc->GetMaxColCount())
+ {
+ SCROW nRow1 = 0;
+ SCROW nRow2 = 32*1024;
+ SCSIZE nSliceRow = 128;
+ SCSIZE nSlotsCol = 0;
+ SCSIZE nSlotsStartCol = nSlots;
+ // Must be sorted by row1,row2!
+ while (nRow2 <= pDoc->GetMaxRowCount())
+ {
+ maSlotDistribution.emplace_back(nRow1, nRow2, nSliceRow, nSlotsCol, nCol1, nCol2, nSliceCol, nSlotsStartCol);
+ nSlotsCol += (nRow2 - nRow1) / nSliceRow;
+ nRow1 = nRow2;
+ nRow2 *= 2;
+ nSliceRow *= 2;
+ }
+ // Store the number of slots in a column in mnBcaSlotsCol, so that finding a slot
+ // to the right can be computed quickly in ComputeNextSlot().
+ if(nCol1 == 0)
+ mnBcaSlotsCol = nSlotsCol;
+ assert(nSlotsCol == mnBcaSlotsCol);
+ nSlots += (nCol2 - nCol1) / nSliceCol * nSlotsCol;
+ nCol1 = nCol2;
+ nCol2 *= 2;
+ nSliceCol *= 2;
+ }
+ mnBcaSlots = nSlots;
+#ifdef DBG_UTIL
+ DoChecks();
+#endif
+}
+
+ScBroadcastAreaSlotMachine::~ScBroadcastAreaSlotMachine()
+{
+ aTableSlotsMap.clear();
+ pBCAlways.reset();
+ // Areas to-be-erased still present is a serious error in handling, but at
+ // this stage there's nothing we can do anymore.
+ SAL_WARN_IF( !maAreasToBeErased.empty(), "sc.core", "ScBroadcastAreaSlotMachine::dtor: maAreasToBeErased not empty");
+}
+
+inline SCSIZE ScBroadcastAreaSlotMachine::ComputeSlotOffset(
+ const ScAddress& rAddress ) const
+{
+ SCROW nRow = rAddress.Row();
+ SCCOL nCol = rAddress.Col();
+ if ( !pDoc->ValidRow(nRow) || !pDoc->ValidCol(nCol) )
+ {
+ OSL_FAIL( "Row/Col invalid, using first slot!" );
+ return 0;
+ }
+ for (const ScSlotData& rSD : maSlotDistribution)
+ {
+ if (nRow < rSD.nStopRow && nCol < rSD.nStopCol)
+ {
+ assert(nRow >= rSD.nStartRow);
+ assert(nCol >= rSD.nStartCol);
+ SCSIZE slot = rSD.nCumulatedRow
+ + static_cast<SCSIZE>(nRow - rSD.nStartRow) / rSD.nSliceRow
+ + rSD.nCumulatedCol
+ + static_cast<SCSIZE>(nCol - rSD.nStartCol) / rSD.nSliceCol * mnBcaSlotsCol;
+ assert(slot < mnBcaSlots);
+ return slot;
+ }
+ }
+ OSL_FAIL( "No slot found, using last!" );
+ return mnBcaSlots - 1;
+}
+
+void ScBroadcastAreaSlotMachine::ComputeAreaPoints( const ScRange& rRange,
+ SCSIZE& rStart, SCSIZE& rEnd, SCSIZE& rRowBreak ) const
+{
+ rStart = ComputeSlotOffset( rRange.aStart );
+ rEnd = ComputeSlotOffset( rRange.aEnd );
+ // count of row slots per column minus one
+ rRowBreak = ComputeSlotOffset(
+ ScAddress( rRange.aStart.Col(), rRange.aEnd.Row(), 0 ) ) - rStart;
+}
+
+static void ComputeNextSlot( SCSIZE & nOff, SCSIZE & nBreak, ScBroadcastAreaSlot** & pp,
+ SCSIZE & nStart, ScBroadcastAreaSlot** const & ppSlots, SCSIZE nRowBreak, SCSIZE nBcaSlotsCol )
+{
+ if ( nOff < nBreak )
+ {
+ ++nOff;
+ ++pp;
+ }
+ else
+ {
+ nStart += nBcaSlotsCol;
+ nOff = nStart;
+ pp = ppSlots + nOff;
+ nBreak = nOff + nRowBreak;
+ }
+}
+
+#ifdef DBG_UTIL
+static void compare(SCSIZE value1, SCSIZE value2, int line)
+{
+ if(value1!=value2)
+ SAL_WARN("sc", "V1:" << value1 << " V2:" << value2 << " (" << line << ")");
+ assert(value1 == value2);
+}
+
+// Basic checks that the calculations work correctly.
+void ScBroadcastAreaSlotMachine::DoChecks()
+{
+ // Copy&paste from the ctor.
+ constexpr SCSIZE nSliceRow = 128;
+ constexpr SCSIZE nSliceCol = 16;
+ // First and second column are in the same slice and so get the same slot.
+ compare( ComputeSlotOffset( ScAddress( 0, 0, 0 )), ComputeSlotOffset( ScAddress( 1, 0, 0 )), __LINE__);
+ // Each nSliceRow rows are offset by one slot (at the start of the logarithmic distribution).
+ compare( ComputeSlotOffset( ScAddress( 0, 0, 0 )),
+ ComputeSlotOffset( ScAddress( 0, nSliceRow, 0 )) - 1, __LINE__ );
+ compare( ComputeSlotOffset( ScAddress( nSliceCol - 1, 0, 0 )),
+ ComputeSlotOffset( ScAddress( nSliceCol, 0, 0 )) - mnBcaSlotsCol, __LINE__ );
+ // Check that last cell is the last slot.
+ compare( ComputeSlotOffset( ScAddress( pDoc->GetMaxColCount() - 1, pDoc->GetMaxRowCount() - 1, 0 )),
+ mnBcaSlots - 1, __LINE__ );
+ // Check that adjacent rows in the same column but in different distribution areas differ by one slot.
+ for( size_t i = 0; i < maSlotDistribution.size() - 1; ++i )
+ {
+ const ScSlotData& s1 = maSlotDistribution[ i ];
+ const ScSlotData& s2 = maSlotDistribution[ i + 1 ];
+ if( s1.nStartCol == s2.nStartCol )
+ {
+ assert( s1.nStopRow == s2.nStartRow );
+ compare( ComputeSlotOffset( ScAddress( s1.nStartCol, s1.nStopRow - 1, 0 )),
+ ComputeSlotOffset( ScAddress( s1.nStartCol, s1.nStopRow, 0 )) - 1, __LINE__ );
+ }
+ }
+ // Check that adjacent columns in the same row but in different distribution areas differ by mnBcaSlotsCol.
+ for( size_t i = 0; i < maSlotDistribution.size() - 1; ++i )
+ {
+ const ScSlotData& s1 = maSlotDistribution[ i ];
+ for( size_t j = i + 1; j < maSlotDistribution.size(); ++j )
+ {
+ const ScSlotData& s2 = maSlotDistribution[ i + 1 ];
+ if( s1.nStartRow == s2.nStartRow && s1.nStopCol == s2.nStartCol )
+ {
+ assert( s1.nStopRow == s2.nStartRow );
+ compare( ComputeSlotOffset( ScAddress( s1.nStopCol - 1, s1.nStartRow, 0 )),
+ ComputeSlotOffset( ScAddress( s1.nStopCol, s1.nStartRow, 0 )) - mnBcaSlotsCol, __LINE__ );
+ }
+ }
+ }
+ // Iterate all slots.
+ ScRange range( ScAddress( 0, 0, 0 ), ScAddress( pDoc->MaxCol(), pDoc->MaxRow(), 0 ));
+ SCSIZE nStart, nEnd, nRowBreak;
+ ComputeAreaPoints( range, nStart, nEnd, nRowBreak );
+ assert( nStart == 0 );
+ assert( nEnd == mnBcaSlots - 1 );
+ SCSIZE nOff = nStart;
+ SCSIZE nBreak = nOff + nRowBreak;
+ std::unique_ptr<ScBroadcastAreaSlot*[]> slots( new ScBroadcastAreaSlot*[ mnBcaSlots ] ); // dummy, not accessed
+ ScBroadcastAreaSlot** ppSlots = slots.get();
+ ScBroadcastAreaSlot** pp = ppSlots;
+ while ( nOff <= nEnd )
+ {
+ SCSIZE previous = nOff;
+ ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol);
+ compare( nOff, previous + 1, __LINE__ );
+ }
+ // Iterate slots in the last row (each will differ by mnBcaSlotsCol).
+ range = ScRange( ScAddress( 0, pDoc->MaxRow(), 0 ),
+ ScAddress( pDoc->MaxCol(), pDoc->MaxRow() - 1, 0 ));
+ ComputeAreaPoints( range, nStart, nEnd, nRowBreak );
+ assert( nStart == mnBcaSlotsCol - 1 );
+ assert( nEnd == mnBcaSlots - 1 );
+ nOff = nStart;
+ nBreak = nOff + nRowBreak;
+ ppSlots = slots.get();
+ pp = ppSlots;
+ while ( nOff <= nEnd )
+ {
+ SCSIZE previous = nOff;
+ ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol);
+ compare( nOff, previous + mnBcaSlotsCol, __LINE__ );
+ }
+}
+#endif
+
+void ScBroadcastAreaSlotMachine::StartListeningArea(
+ const ScRange& rRange, bool bGroupListening, SvtListener* pListener )
+{
+ if ( rRange == BCA_LISTEN_ALWAYS )
+ {
+ if ( !pBCAlways )
+ pBCAlways.reset( new SvtBroadcaster );
+ pListener->StartListening( *pBCAlways );
+ }
+ else
+ {
+ // A new area needs to be inserted to the corresponding slots, for 3D
+ // ranges for all sheets, do not slice into per sheet areas or the
+ // !bDone will break too early (i.e. after the first sheet) if
+ // subsequent listeners are to be added.
+ ScBroadcastArea* pArea = nullptr;
+ bool bDone = false;
+ for (SCTAB nTab = rRange.aStart.Tab();
+ !bDone && nTab <= rRange.aEnd.Tab(); ++nTab)
+ {
+ TableSlotsMap::iterator iTab( aTableSlotsMap.find( nTab));
+ if (iTab == aTableSlotsMap.end())
+ iTab = aTableSlotsMap.emplace(nTab, std::make_unique<TableSlots>(mnBcaSlots)).first;
+ ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
+ SCSIZE nStart, nEnd, nRowBreak;
+ ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak );
+ SCSIZE nOff = nStart;
+ SCSIZE nBreak = nOff + nRowBreak;
+ ScBroadcastAreaSlot** pp = ppSlots + nOff;
+ while ( !bDone && nOff <= nEnd )
+ {
+ if ( !*pp )
+ *pp = new ScBroadcastAreaSlot( pDoc, this );
+ if (!pArea)
+ {
+ // If the call to StartListeningArea didn't create the
+ // ScBroadcastArea, listeners were added to an already
+ // existing identical area that doesn't need to be inserted
+ // to slots again.
+ if (!(*pp)->StartListeningArea( rRange, bGroupListening, pListener, pArea))
+ bDone = true;
+ }
+ else
+ (*pp)->InsertListeningArea( pArea);
+ ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol);
+ }
+ }
+ }
+}
+
+void ScBroadcastAreaSlotMachine::EndListeningArea(
+ const ScRange& rRange, bool bGroupListening, SvtListener* pListener )
+{
+ if ( rRange == BCA_LISTEN_ALWAYS )
+ {
+ if ( pBCAlways )
+ {
+ pListener->EndListening( *pBCAlways);
+ if (!pBCAlways->HasListeners())
+ {
+ pBCAlways.reset();
+ }
+ }
+ }
+ else
+ {
+ SCTAB nEndTab = rRange.aEnd.Tab();
+ for (TableSlotsMap::iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab()));
+ iTab != aTableSlotsMap.end() && (*iTab).first <= nEndTab; ++iTab)
+ {
+ ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
+ SCSIZE nStart, nEnd, nRowBreak;
+ ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak );
+ SCSIZE nOff = nStart;
+ SCSIZE nBreak = nOff + nRowBreak;
+ ScBroadcastAreaSlot** pp = ppSlots + nOff;
+ ScBroadcastArea* pArea = nullptr;
+ if (nOff == 0 && nEnd == mnBcaSlots-1)
+ {
+ // Slightly optimized for 0,0,MAXCOL,MAXROW calls as they
+ // happen for insertion and deletion of sheets.
+ ScBroadcastAreaSlot** const pStop = ppSlots + nEnd;
+ do
+ {
+ if ( *pp )
+ (*pp)->EndListeningArea( rRange, bGroupListening, pListener, pArea);
+ } while (++pp < pStop);
+ }
+ else
+ {
+ while ( nOff <= nEnd )
+ {
+ if ( *pp )
+ (*pp)->EndListeningArea( rRange, bGroupListening, pListener, pArea);
+ ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol);
+ }
+ }
+ }
+ }
+}
+
+bool ScBroadcastAreaSlotMachine::AreaBroadcast( const ScRange& rRange, SfxHintId nHint )
+{
+ bool bBroadcasted = false;
+ SCTAB nEndTab = rRange.aEnd.Tab();
+ for (TableSlotsMap::iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab()));
+ iTab != aTableSlotsMap.end() && (*iTab).first <= nEndTab; ++iTab)
+ {
+ ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
+ SCSIZE nStart, nEnd, nRowBreak;
+ ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak );
+ SCSIZE nOff = nStart;
+ SCSIZE nBreak = nOff + nRowBreak;
+ ScBroadcastAreaSlot** pp = ppSlots + nOff;
+ while ( nOff <= nEnd )
+ {
+ if ( *pp )
+ bBroadcasted |= (*pp)->AreaBroadcast( rRange, nHint );
+ ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol);
+ }
+ }
+ return bBroadcasted;
+}
+
+bool ScBroadcastAreaSlotMachine::AreaBroadcast( const ScHint& rHint ) const
+{
+ const ScAddress& rAddress = rHint.GetStartAddress();
+ if ( rAddress == BCA_BRDCST_ALWAYS )
+ {
+ if ( pBCAlways )
+ {
+ pBCAlways->Broadcast( rHint );
+ return true;
+ }
+ else
+ return false;
+ }
+ else
+ {
+ TableSlotsMap::const_iterator iTab( aTableSlotsMap.find( rAddress.Tab()));
+ if (iTab == aTableSlotsMap.end())
+ return false;
+ // Process all slots for the given row range.
+ ScRange broadcastRange( rAddress,
+ ScAddress( rAddress.Col(), rAddress.Row() + rHint.GetRowCount() - 1, rAddress.Tab()));
+ bool bBroadcasted = false;
+ ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
+ SCSIZE nStart, nEnd, nRowBreak;
+ ComputeAreaPoints( broadcastRange, nStart, nEnd, nRowBreak );
+ SCSIZE nOff = nStart;
+ SCSIZE nBreak = nOff + nRowBreak;
+ ScBroadcastAreaSlot** pp = ppSlots + nOff;
+ while ( nOff <= nEnd )
+ {
+ if ( *pp )
+ bBroadcasted |= (*pp)->AreaBroadcast( rHint );
+ ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol);
+ }
+ return bBroadcasted;
+ }
+}
+
+void ScBroadcastAreaSlotMachine::DelBroadcastAreasInRange(
+ const ScRange& rRange )
+{
+ SCTAB nEndTab = rRange.aEnd.Tab();
+ for (TableSlotsMap::iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab()));
+ iTab != aTableSlotsMap.end() && (*iTab).first <= nEndTab; ++iTab)
+ {
+ ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
+ SCSIZE nStart, nEnd, nRowBreak;
+ ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak );
+ SCSIZE nOff = nStart;
+ SCSIZE nBreak = nOff + nRowBreak;
+ ScBroadcastAreaSlot** pp = ppSlots + nOff;
+ if (nOff == 0 && nEnd == mnBcaSlots-1)
+ {
+ // Slightly optimized for 0,0,MAXCOL,MAXROW calls as they
+ // happen for insertion and deletion of sheets.
+ ScBroadcastAreaSlot** const pStop = ppSlots + nEnd;
+ do
+ {
+ if ( *pp )
+ (*pp)->DelBroadcastAreasInRange( rRange );
+ } while (++pp < pStop);
+ }
+ else
+ {
+ while ( nOff <= nEnd )
+ {
+ if ( *pp )
+ (*pp)->DelBroadcastAreasInRange( rRange );
+ ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol);
+ }
+ }
+ }
+}
+
+// for all affected: remove, chain, update range, insert, and maybe delete
+void ScBroadcastAreaSlotMachine::UpdateBroadcastAreas(
+ UpdateRefMode eUpdateRefMode,
+ const ScRange& rRange, SCCOL nDx, SCROW nDy, SCTAB nDz )
+{
+ // remove affected and put in chain
+ SCTAB nEndTab = rRange.aEnd.Tab();
+ for (TableSlotsMap::iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab()));
+ iTab != aTableSlotsMap.end() && (*iTab).first <= nEndTab; ++iTab)
+ {
+ ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
+ SCSIZE nStart, nEnd, nRowBreak;
+ ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak );
+ SCSIZE nOff = nStart;
+ SCSIZE nBreak = nOff + nRowBreak;
+ ScBroadcastAreaSlot** pp = ppSlots + nOff;
+ if (nOff == 0 && nEnd == mnBcaSlots-1)
+ {
+ // Slightly optimized for 0,0,MAXCOL,MAXROW calls as they
+ // happen for insertion and deletion of sheets.
+ ScBroadcastAreaSlot** const pStop = ppSlots + nEnd;
+ do
+ {
+ if ( *pp )
+ (*pp)->UpdateRemove( eUpdateRefMode, rRange, nDx, nDy, nDz );
+ } while (++pp < pStop);
+ }
+ else
+ {
+ while ( nOff <= nEnd )
+ {
+ if ( *pp )
+ (*pp)->UpdateRemove( eUpdateRefMode, rRange, nDx, nDy, nDz );
+ ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol);
+ }
+ }
+ }
+
+ // Updating an area's range will modify the hash key, remove areas from all
+ // affected slots. Will be reinserted later with the updated range.
+ ScBroadcastArea* pChain = pUpdateChain;
+ while (pChain)
+ {
+ ScBroadcastArea* pArea = pChain;
+ pChain = pArea->GetUpdateChainNext();
+ ScRange aRange( pArea->GetRange());
+ // remove from slots
+ for (SCTAB nTab = aRange.aStart.Tab(); nTab <= aRange.aEnd.Tab() && pArea->GetRef(); ++nTab)
+ {
+ TableSlotsMap::iterator iTab( aTableSlotsMap.find( nTab));
+ if (iTab == aTableSlotsMap.end())
+ {
+ OSL_FAIL( "UpdateBroadcastAreas: Where's the TableSlot?!?");
+ continue; // for
+ }
+ ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
+ SCSIZE nStart, nEnd, nRowBreak;
+ ComputeAreaPoints( aRange, nStart, nEnd, nRowBreak );
+ SCSIZE nOff = nStart;
+ SCSIZE nBreak = nOff + nRowBreak;
+ ScBroadcastAreaSlot** pp = ppSlots + nOff;
+ while ( nOff <= nEnd && pArea->GetRef() )
+ {
+ if (*pp)
+ (*pp)->UpdateRemoveArea( pArea);
+ ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol);
+ }
+ }
+
+ }
+
+ // shift sheets
+ if (nDz)
+ {
+ if (nDz < 0)
+ {
+ TableSlotsMap::iterator iDel( aTableSlotsMap.lower_bound( rRange.aStart.Tab()));
+ TableSlotsMap::iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab() - nDz));
+ // Remove sheets, if any, iDel or/and iTab may as well point to end().
+ while (iDel != iTab)
+ {
+ iDel = aTableSlotsMap.erase(iDel);
+ }
+ // shift remaining down
+ while (iTab != aTableSlotsMap.end())
+ {
+ SCTAB nTab = (*iTab).first + nDz;
+ aTableSlotsMap[nTab] = std::move((*iTab).second);
+ iTab = aTableSlotsMap.erase(iTab);
+ }
+ }
+ else
+ {
+ TableSlotsMap::iterator iStop( aTableSlotsMap.lower_bound( rRange.aStart.Tab()));
+ if (iStop != aTableSlotsMap.end())
+ {
+ bool bStopIsBegin = (iStop == aTableSlotsMap.begin());
+ if (!bStopIsBegin)
+ --iStop;
+ TableSlotsMap::iterator iTab( aTableSlotsMap.end());
+ --iTab;
+ while (iTab != iStop)
+ {
+ SCTAB nTab = (*iTab).first + nDz;
+ aTableSlotsMap[nTab] = std::move((*iTab).second);
+ aTableSlotsMap.erase( iTab--);
+ }
+ // Shift the very first, iTab==iStop in this case.
+ if (bStopIsBegin)
+ {
+ SCTAB nTab = (*iTab).first + nDz;
+ aTableSlotsMap[nTab] = std::move((*iTab).second);
+ aTableSlotsMap.erase( iStop);
+ }
+ }
+ }
+ }
+
+ // work off chain
+ SCCOL nCol1, nCol2, theCol1, theCol2;
+ SCROW nRow1, nRow2, theRow1, theRow2;
+ SCTAB nTab1, nTab2, theTab1, theTab2;
+ rRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
+ while ( pUpdateChain )
+ {
+ ScBroadcastArea* pArea = pUpdateChain;
+ ScRange aRange( pArea->GetRange());
+ pUpdateChain = pArea->GetUpdateChainNext();
+
+ // update range
+ aRange.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 ))
+ {
+ aRange = ScRange( theCol1,theRow1,theTab1, theCol2,theRow2,theTab2 );
+ pArea->UpdateRange( aRange );
+ // For DDE and ScLookupCache
+ pArea->GetBroadcaster().Broadcast( ScAreaChangedHint( aRange ) );
+ }
+
+ // insert to slots
+ for (SCTAB nTab = aRange.aStart.Tab(); nTab <= aRange.aEnd.Tab(); ++nTab)
+ {
+ TableSlotsMap::iterator iTab( aTableSlotsMap.find( nTab));
+ if (iTab == aTableSlotsMap.end())
+ iTab = aTableSlotsMap.emplace(nTab, std::make_unique<TableSlots>(mnBcaSlots)).first;
+ ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
+ SCSIZE nStart, nEnd, nRowBreak;
+ ComputeAreaPoints( aRange, nStart, nEnd, nRowBreak );
+ SCSIZE nOff = nStart;
+ SCSIZE nBreak = nOff + nRowBreak;
+ ScBroadcastAreaSlot** pp = ppSlots + nOff;
+ while ( nOff <= nEnd )
+ {
+ if (!*pp)
+ *pp = new ScBroadcastAreaSlot( pDoc, this );
+ (*pp)->UpdateInsert( pArea );
+ ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol);
+ }
+ }
+
+ // unchain
+ pArea->SetUpdateChainNext( nullptr );
+ pArea->SetInUpdateChain( false );
+
+ // Delete if not inserted to any slot. RemoveBulkArea(pArea) was
+ // already executed in UpdateRemove().
+ if (!pArea->GetRef())
+ delete pArea;
+ }
+ pEOUpdateChain = nullptr;
+}
+
+void ScBroadcastAreaSlotMachine::EnterBulkBroadcast()
+{
+ ++nInBulkBroadcast;
+}
+
+void ScBroadcastAreaSlotMachine::LeaveBulkBroadcast( SfxHintId nHintId )
+{
+ if (nInBulkBroadcast <= 0)
+ return;
+
+ if (--nInBulkBroadcast == 0)
+ {
+ ScBroadcastAreasBulk().swap( aBulkBroadcastAreas);
+ bool bBroadcasted = BulkBroadcastGroupAreas( nHintId );
+ // Trigger the "final" tracking.
+ if (pDoc->IsTrackFormulasPending())
+ pDoc->FinalTrackFormulas( nHintId );
+ else if (bBroadcasted)
+ pDoc->TrackFormulas( nHintId );
+ }
+}
+
+bool ScBroadcastAreaSlotMachine::InsertBulkArea( const ScBroadcastArea* pArea )
+{
+ return aBulkBroadcastAreas.insert( pArea ).second;
+}
+
+void ScBroadcastAreaSlotMachine::InsertBulkGroupArea( ScBroadcastArea* pArea, const ScRange& rRange )
+{
+ BulkGroupAreasType::iterator it = m_BulkGroupAreas.lower_bound(pArea);
+ if (it == m_BulkGroupAreas.end() || m_BulkGroupAreas.key_comp()(pArea, it->first))
+ {
+ // Insert a new one.
+ it = m_BulkGroupAreas.insert(it, std::make_pair(pArea, sc::ColumnSpanSet()));
+ }
+
+ sc::ColumnSpanSet& rSet = it->second;
+ rSet.set(*pDoc, rRange, true);
+}
+
+bool ScBroadcastAreaSlotMachine::BulkBroadcastGroupAreas( SfxHintId nHintId )
+{
+ if (m_BulkGroupAreas.empty())
+ return false;
+
+ sc::BulkDataHint aHint( *pDoc, nHintId);
+
+ bool bBroadcasted = false;
+ for (const auto& [pArea, rSpans] : m_BulkGroupAreas)
+ {
+ assert(pArea);
+ SvtBroadcaster& rBC = pArea->GetBroadcaster();
+ if (!rBC.HasListeners())
+ {
+ /* FIXME: find the cause where the last listener is removed and
+ * this area is still listed here. */
+ SAL_WARN("sc.core","ScBroadcastAreaSlotMachine::BulkBroadcastGroupAreas - pArea has no listeners and should had been removed already");
+ }
+ else
+ {
+ aHint.setSpans(&rSpans);
+ rBC.Broadcast(aHint);
+ bBroadcasted = true;
+ }
+ }
+
+ m_BulkGroupAreas.clear();
+
+ return bBroadcasted;
+}
+
+size_t ScBroadcastAreaSlotMachine::RemoveBulkArea( const ScBroadcastArea* pArea )
+{
+ return aBulkBroadcastAreas.erase( pArea );
+}
+
+void ScBroadcastAreaSlotMachine::RemoveBulkGroupArea( ScBroadcastArea* pArea )
+{
+ m_BulkGroupAreas.erase(pArea);
+}
+
+void ScBroadcastAreaSlotMachine::PushAreaToBeErased( ScBroadcastAreaSlot* pSlot,
+ ScBroadcastAreas::iterator& rIter )
+{
+ maAreasToBeErased.emplace_back( pSlot, rIter);
+}
+
+void ScBroadcastAreaSlotMachine::FinallyEraseAreas( ScBroadcastAreaSlot* pSlot )
+{
+ SAL_WARN_IF( pSlot->IsInBroadcastIteration(), "sc.core",
+ "ScBroadcastAreaSlotMachine::FinallyEraseAreas: during iteration? NO!");
+ if (pSlot->IsInBroadcastIteration())
+ return;
+
+ // maAreasToBeErased is a simple vector so erasing an element may
+ // invalidate iterators and would be inefficient anyway. Instead, copy
+ // elements to be preserved (usually none!) to temporary vector and swap.
+ AreasToBeErased aCopy;
+ for (auto& rArea : maAreasToBeErased)
+ {
+ if (rArea.first == pSlot)
+ pSlot->EraseArea( rArea.second);
+ else
+ aCopy.push_back( rArea);
+ }
+ maAreasToBeErased.swap( aCopy);
+}
+
+std::vector<sc::AreaListener> ScBroadcastAreaSlotMachine::GetAllListeners(
+ const ScRange& rRange, sc::AreaOverlapType eType, sc::ListenerGroupType eGroup )
+{
+ std::vector<sc::AreaListener> aRet;
+
+ SCTAB nEndTab = rRange.aEnd.Tab();
+ for (TableSlotsMap::const_iterator iTab( aTableSlotsMap.lower_bound( rRange.aStart.Tab()));
+ iTab != aTableSlotsMap.end() && (*iTab).first <= nEndTab; ++iTab)
+ {
+ ScBroadcastAreaSlot** ppSlots = (*iTab).second->getSlots();
+ SCSIZE nStart, nEnd, nRowBreak;
+ ComputeAreaPoints( rRange, nStart, nEnd, nRowBreak );
+ SCSIZE nOff = nStart;
+ SCSIZE nBreak = nOff + nRowBreak;
+ ScBroadcastAreaSlot** pp = ppSlots + nOff;
+ while ( nOff <= nEnd )
+ {
+ ScBroadcastAreaSlot* p = *pp;
+ if (p)
+ p->GetAllListeners(rRange, aRet, eType, eGroup);
+ ComputeNextSlot( nOff, nBreak, pp, nStart, ppSlots, nRowBreak, mnBcaSlotsCol);
+ }
+ }
+
+ return aRet;
+}
+
+#if DEBUG_AREA_BROADCASTER
+void ScBroadcastAreaSlotMachine::Dump() const
+{
+ cout << "slot distribution count: " << nBcaSlots << endl;
+ for (const auto& [rIndex, pTabSlots] : aTableSlotsMap)
+ {
+ cout << "-- sheet (index: " << rIndex << ")" << endl;
+
+ assert(pTabSlots);
+ ScBroadcastAreaSlot** ppSlots = pTabSlots->getSlots();
+ for (SCSIZE i = 0; i < nBcaSlots; ++i)
+ {
+ const ScBroadcastAreaSlot* pSlot = ppSlots[i];
+ if (pSlot)
+ {
+ cout << "* slot " << i << endl;
+ pSlot->Dump();
+ }
+ }
+ }
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/bigrange.cxx b/sc/source/core/data/bigrange.cxx
new file mode 100644
index 000000000..192765743
--- /dev/null
+++ b/sc/source/core/data/bigrange.cxx
@@ -0,0 +1,25 @@
+/* -*- 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 <bigrange.hxx>
+#include <document.hxx>
+
+bool ScBigAddress::IsValid( const ScDocument& rDoc ) const
+{ // min/max interval bounds define whole col/row/tab
+ return
+ ((0 <= nCol && nCol <= rDoc.MaxCol())
+ || nCol == ScBigRange::nRangeMin || nCol == ScBigRange::nRangeMax) &&
+ ((0 <= nRow && nRow <= rDoc.MaxRow())
+ || nRow == ScBigRange::nRangeMin || nRow == ScBigRange::nRangeMax) &&
+ ((0 <= nTab && nTab < rDoc.GetTableCount())
+ || nTab == ScBigRange::nRangeMin || nTab == ScBigRange::nRangeMax)
+ ;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/celltextattr.cxx b/sc/source/core/data/celltextattr.cxx
new file mode 100644
index 000000000..09bfb0829
--- /dev/null
+++ b/sc/source/core/data/celltextattr.cxx
@@ -0,0 +1,21 @@
+/* -*- 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 <celltextattr.hxx>
+#include <globalnames.hxx>
+
+namespace sc {
+
+CellTextAttr::CellTextAttr() :
+ mnTextWidth(TEXTWIDTH_DIRTY),
+ mnScriptType(SvtScriptType::UNKNOWN) {}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/cellvalue.cxx b/sc/source/core/data/cellvalue.cxx
new file mode 100644
index 000000000..64cd34e32
--- /dev/null
+++ b/sc/source/core/data/cellvalue.cxx
@@ -0,0 +1,691 @@
+/* -*- 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 <cellvalue.hxx>
+#include <document.hxx>
+#include <column.hxx>
+#include <formulacell.hxx>
+#include <editeng/editobj.hxx>
+#include <editeng/editstat.hxx>
+#include <stringutil.hxx>
+#include <editutil.hxx>
+#include <tokenarray.hxx>
+#include <formula/token.hxx>
+#include <formula/errorcodes.hxx>
+#include <svl/sharedstring.hxx>
+
+namespace {
+
+CellType adjustCellType( CellType eOrig )
+{
+ switch (eOrig)
+ {
+ case CELLTYPE_EDIT:
+ return CELLTYPE_STRING;
+ default:
+ ;
+ }
+ return eOrig;
+}
+
+template<typename T>
+OUString getString( const T& rVal )
+{
+ if (rVal.meType == CELLTYPE_STRING)
+ return rVal.mpString->getString();
+
+ if (rVal.meType == CELLTYPE_EDIT)
+ {
+ OUStringBuffer aRet;
+ sal_Int32 n = rVal.mpEditText->GetParagraphCount();
+ for (sal_Int32 i = 0; i < n; ++i)
+ {
+ if (i > 0)
+ aRet.append('\n');
+ aRet.append(rVal.mpEditText->GetText(i));
+ }
+ return aRet.makeStringAndClear();
+ }
+
+ return OUString();
+}
+
+bool equalsFormulaCells( const ScFormulaCell* p1, const ScFormulaCell* p2 )
+{
+ const ScTokenArray* pCode1 = p1->GetCode();
+ const ScTokenArray* pCode2 = p2->GetCode();
+
+ if (pCode1->GetLen() != pCode2->GetLen())
+ return false;
+
+ if (pCode1->GetCodeError() != pCode2->GetCodeError())
+ return false;
+
+ sal_uInt16 n = pCode1->GetLen();
+ formula::FormulaToken** ppToken1 = pCode1->GetArray();
+ formula::FormulaToken** ppToken2 = pCode2->GetArray();
+ for (sal_uInt16 i = 0; i < n; ++i)
+ {
+ if (!ppToken1[i]->TextEqual(*(ppToken2[i])))
+ return false;
+ }
+
+ return true;
+}
+
+template<typename T>
+bool equalsWithoutFormatImpl( const T& left, const T& right )
+{
+ CellType eType1 = adjustCellType(left.meType);
+ CellType eType2 = adjustCellType(right.meType);
+ if (eType1 != eType2)
+ return false;
+
+ switch (eType1)
+ {
+ case CELLTYPE_NONE:
+ return true;
+ case CELLTYPE_VALUE:
+ return left.mfValue == right.mfValue;
+ case CELLTYPE_STRING:
+ {
+ OUString aStr1 = getString(left);
+ OUString aStr2 = getString(right);
+ return aStr1 == aStr2;
+ }
+ case CELLTYPE_FORMULA:
+ return equalsFormulaCells(left.mpFormula, right.mpFormula);
+ default:
+ ;
+ }
+ return false;
+}
+
+void commitToColumn( const ScCellValue& rCell, ScColumn& rColumn, SCROW nRow )
+{
+ switch (rCell.meType)
+ {
+ case CELLTYPE_STRING:
+ rColumn.SetRawString(nRow, *rCell.mpString);
+ break;
+ case CELLTYPE_EDIT:
+ rColumn.SetEditText(nRow, ScEditUtil::Clone(*rCell.mpEditText, rColumn.GetDoc()));
+ break;
+ case CELLTYPE_VALUE:
+ rColumn.SetValue(nRow, rCell.mfValue);
+ break;
+ case CELLTYPE_FORMULA:
+ {
+ ScAddress aDestPos(rColumn.GetCol(), nRow, rColumn.GetTab());
+ rColumn.SetFormulaCell(nRow, new ScFormulaCell(*rCell.mpFormula, rColumn.GetDoc(), aDestPos));
+ }
+ break;
+ default:
+ rColumn.DeleteContent(nRow);
+ }
+}
+
+bool hasStringImpl( CellType eType, ScFormulaCell* pFormula )
+{
+ switch (eType)
+ {
+ case CELLTYPE_STRING:
+ case CELLTYPE_EDIT:
+ return true;
+ case CELLTYPE_FORMULA:
+ return !pFormula->IsValue();
+ default:
+ return false;
+ }
+}
+
+bool hasNumericImpl( CellType eType, ScFormulaCell* pFormula )
+{
+ switch (eType)
+ {
+ case CELLTYPE_VALUE:
+ return true;
+ case CELLTYPE_FORMULA:
+ return pFormula->IsValue();
+ default:
+ return false;
+ }
+}
+
+template<typename CellT>
+OUString getStringImpl( const CellT& rCell, const ScDocument* pDoc )
+{
+ switch (rCell.meType)
+ {
+ case CELLTYPE_VALUE:
+ return OUString::number(rCell.mfValue);
+ case CELLTYPE_STRING:
+ return rCell.mpString->getString();
+ case CELLTYPE_EDIT:
+ if (rCell.mpEditText)
+ return ScEditUtil::GetString(*rCell.mpEditText, pDoc);
+ break;
+ case CELLTYPE_FORMULA:
+ return rCell.mpFormula->GetString().getString();
+ default:
+ ;
+ }
+ return OUString();
+}
+
+template<typename CellT>
+OUString getRawStringImpl( const CellT& rCell, const ScDocument& rDoc )
+{
+ switch (rCell.meType)
+ {
+ case CELLTYPE_VALUE:
+ return OUString::number(rCell.mfValue);
+ case CELLTYPE_STRING:
+ return rCell.mpString->getString();
+ case CELLTYPE_EDIT:
+ if (rCell.mpEditText)
+ return ScEditUtil::GetString(*rCell.mpEditText, &rDoc);
+ break;
+ case CELLTYPE_FORMULA:
+ return rCell.mpFormula->GetRawString().getString();
+ default:
+ ;
+ }
+ return OUString();
+}
+
+}
+
+ScCellValue::ScCellValue() : meType(CELLTYPE_NONE), mfValue(0.0) {}
+
+ScCellValue::ScCellValue( const ScRefCellValue& rCell ) : meType(rCell.meType), mfValue(rCell.mfValue)
+{
+ switch (rCell.meType)
+ {
+ case CELLTYPE_STRING:
+ mpString = new svl::SharedString(*rCell.mpString);
+ break;
+ case CELLTYPE_EDIT:
+ mpEditText = rCell.mpEditText->Clone().release();
+ break;
+ case CELLTYPE_FORMULA:
+ mpFormula = rCell.mpFormula->Clone();
+ break;
+ default:
+ ;
+ }
+}
+
+ScCellValue::ScCellValue( double fValue ) : meType(CELLTYPE_VALUE), mfValue(fValue) {}
+
+ScCellValue::ScCellValue( const svl::SharedString& rString ) : meType(CELLTYPE_STRING), mpString(new svl::SharedString(rString)) {}
+
+ScCellValue::ScCellValue( const ScCellValue& r ) : meType(r.meType), mfValue(r.mfValue)
+{
+ switch (r.meType)
+ {
+ case CELLTYPE_STRING:
+ mpString = new svl::SharedString(*r.mpString);
+ break;
+ case CELLTYPE_EDIT:
+ mpEditText = r.mpEditText->Clone().release();
+ break;
+ case CELLTYPE_FORMULA:
+ mpFormula = r.mpFormula->Clone();
+ break;
+ default:
+ ;
+ }
+}
+
+ScCellValue::ScCellValue(ScCellValue&& r) noexcept
+ : meType(r.meType)
+ , mfValue(r.mfValue)
+{
+ switch (r.meType)
+ {
+ case CELLTYPE_STRING:
+ mpString = r.mpString;
+ break;
+ case CELLTYPE_EDIT:
+ mpEditText = r.mpEditText;
+ break;
+ case CELLTYPE_FORMULA:
+ mpFormula = r.mpFormula;
+ break;
+ default:
+ ;
+ }
+ r.meType = CELLTYPE_NONE;
+}
+
+ScCellValue::~ScCellValue()
+{
+ clear();
+}
+
+void ScCellValue::clear() noexcept
+{
+ switch (meType)
+ {
+ case CELLTYPE_STRING:
+ delete mpString;
+ break;
+ case CELLTYPE_EDIT:
+ delete mpEditText;
+ break;
+ case CELLTYPE_FORMULA:
+ delete mpFormula;
+ break;
+ default:
+ ;
+ }
+
+ // Reset to empty value.
+ meType = CELLTYPE_NONE;
+ mfValue = 0.0;
+}
+
+void ScCellValue::set( double fValue )
+{
+ clear();
+ meType = CELLTYPE_VALUE;
+ mfValue = fValue;
+}
+
+void ScCellValue::set( const svl::SharedString& rStr )
+{
+ clear();
+ meType = CELLTYPE_STRING;
+ mpString = new svl::SharedString(rStr);
+}
+
+void ScCellValue::set( const EditTextObject& rEditText )
+{
+ clear();
+ meType = CELLTYPE_EDIT;
+ mpEditText = rEditText.Clone().release();
+}
+
+void ScCellValue::set( EditTextObject* pEditText )
+{
+ clear();
+ meType = CELLTYPE_EDIT;
+ mpEditText = pEditText;
+}
+
+void ScCellValue::set( ScFormulaCell* pFormula )
+{
+ clear();
+ meType = CELLTYPE_FORMULA;
+ mpFormula = pFormula;
+}
+
+void ScCellValue::assign( const ScDocument& rDoc, const ScAddress& rPos )
+{
+ clear();
+
+ ScRefCellValue aRefVal(const_cast<ScDocument&>(rDoc), rPos);
+
+ meType = aRefVal.meType;
+ switch (meType)
+ {
+ case CELLTYPE_STRING:
+ mpString = new svl::SharedString(*aRefVal.mpString);
+ break;
+ case CELLTYPE_EDIT:
+ if (aRefVal.mpEditText)
+ mpEditText = aRefVal.mpEditText->Clone().release();
+ break;
+ case CELLTYPE_VALUE:
+ mfValue = aRefVal.mfValue;
+ break;
+ case CELLTYPE_FORMULA:
+ mpFormula = aRefVal.mpFormula->Clone();
+ break;
+ default:
+ meType = CELLTYPE_NONE; // reset to empty.
+ }
+}
+
+void ScCellValue::assign(const ScCellValue& rOther, ScDocument& rDestDoc, ScCloneFlags nCloneFlags)
+{
+ clear();
+
+ meType = rOther.meType;
+ switch (meType)
+ {
+ case CELLTYPE_STRING:
+ mpString = new svl::SharedString(*rOther.mpString);
+ break;
+ case CELLTYPE_EDIT:
+ {
+ // Switch to the pool of the destination document.
+ ScFieldEditEngine& rEngine = rDestDoc.GetEditEngine();
+ if (rOther.mpEditText->HasOnlineSpellErrors())
+ {
+ EEControlBits nControl = rEngine.GetControlWord();
+ const EEControlBits nSpellControl = EEControlBits::ONLINESPELLING | EEControlBits::ALLOWBIGOBJS;
+ bool bNewControl = ((nControl & nSpellControl) != nSpellControl);
+ if (bNewControl)
+ rEngine.SetControlWord(nControl | nSpellControl);
+ rEngine.SetTextCurrentDefaults(*rOther.mpEditText);
+ mpEditText = rEngine.CreateTextObject().release();
+ if (bNewControl)
+ rEngine.SetControlWord(nControl);
+ }
+ else
+ {
+ rEngine.SetTextCurrentDefaults(*rOther.mpEditText);
+ mpEditText = rEngine.CreateTextObject().release();
+ }
+ }
+ break;
+ case CELLTYPE_VALUE:
+ mfValue = rOther.mfValue;
+ break;
+ case CELLTYPE_FORMULA:
+ // Switch to the destination document.
+ mpFormula = new ScFormulaCell(*rOther.mpFormula, rDestDoc, rOther.mpFormula->aPos, nCloneFlags);
+ break;
+ default:
+ meType = CELLTYPE_NONE; // reset to empty.
+ }
+}
+
+void ScCellValue::commit( ScDocument& rDoc, const ScAddress& rPos ) const
+{
+ switch (meType)
+ {
+ case CELLTYPE_STRING:
+ {
+ ScSetStringParam aParam;
+ aParam.setTextInput();
+ rDoc.SetString(rPos, mpString->getString(), &aParam);
+ }
+ break;
+ case CELLTYPE_EDIT:
+ rDoc.SetEditText(rPos, mpEditText->Clone());
+ break;
+ case CELLTYPE_VALUE:
+ rDoc.SetValue(rPos, mfValue);
+ break;
+ case CELLTYPE_FORMULA:
+ rDoc.SetFormulaCell(rPos, mpFormula->Clone());
+ break;
+ default:
+ rDoc.SetEmptyCell(rPos);
+ }
+}
+
+void ScCellValue::commit( ScColumn& rColumn, SCROW nRow ) const
+{
+ commitToColumn(*this, rColumn, nRow);
+}
+
+void ScCellValue::release( ScDocument& rDoc, const ScAddress& rPos )
+{
+ switch (meType)
+ {
+ case CELLTYPE_STRING:
+ {
+ // Currently, string cannot be placed without copying.
+ ScSetStringParam aParam;
+ aParam.setTextInput();
+ rDoc.SetString(rPos, mpString->getString(), &aParam);
+ delete mpString;
+ }
+ break;
+ case CELLTYPE_EDIT:
+ // Cell takes the ownership of the text object.
+ rDoc.SetEditText(rPos, std::unique_ptr<EditTextObject>(mpEditText));
+ break;
+ case CELLTYPE_VALUE:
+ rDoc.SetValue(rPos, mfValue);
+ break;
+ case CELLTYPE_FORMULA:
+ // This formula cell instance is directly placed in the document without copying.
+ rDoc.SetFormulaCell(rPos, mpFormula);
+ break;
+ default:
+ rDoc.SetEmptyCell(rPos);
+ }
+
+ meType = CELLTYPE_NONE;
+ mfValue = 0.0;
+}
+
+void ScCellValue::release( ScColumn& rColumn, SCROW nRow, sc::StartListeningType eListenType )
+{
+ switch (meType)
+ {
+ case CELLTYPE_STRING:
+ {
+ // Currently, string cannot be placed without copying.
+ rColumn.SetRawString(nRow, *mpString);
+ delete mpString;
+ }
+ break;
+ case CELLTYPE_EDIT:
+ // Cell takes the ownership of the text object.
+ rColumn.SetEditText(nRow, std::unique_ptr<EditTextObject>(mpEditText));
+ break;
+ case CELLTYPE_VALUE:
+ rColumn.SetValue(nRow, mfValue);
+ break;
+ case CELLTYPE_FORMULA:
+ // This formula cell instance is directly placed in the document without copying.
+ rColumn.SetFormulaCell(nRow, mpFormula, eListenType);
+ break;
+ default:
+ rColumn.DeleteContent(nRow);
+ }
+
+ meType = CELLTYPE_NONE;
+ mfValue = 0.0;
+}
+
+OUString ScCellValue::getString( const ScDocument& rDoc ) const
+{
+ return getStringImpl(*this, &rDoc);
+}
+
+bool ScCellValue::isEmpty() const
+{
+ return meType == CELLTYPE_NONE;
+}
+
+bool ScCellValue::equalsWithoutFormat( const ScCellValue& r ) const
+{
+ return equalsWithoutFormatImpl(*this, r);
+}
+
+ScCellValue& ScCellValue::operator= ( const ScCellValue& r )
+{
+ ScCellValue aTmp(r);
+ swap(aTmp);
+ return *this;
+}
+
+ScCellValue& ScCellValue::operator=(ScCellValue&& rCell) noexcept
+{
+ clear();
+
+ meType = rCell.meType;
+ mfValue = rCell.mfValue;
+ switch (rCell.meType)
+ {
+ case CELLTYPE_STRING:
+ mpString = rCell.mpString;
+ break;
+ case CELLTYPE_EDIT:
+ mpEditText = rCell.mpEditText;
+ break;
+ case CELLTYPE_FORMULA:
+ mpFormula = rCell.mpFormula;
+ break;
+ default:
+ ;
+ }
+ //we don't need to reset mpString/mpEditText/mpFormula if we
+ //set meType to NONE as the ScCellValue dtor keys off the meType
+ rCell.meType = CELLTYPE_NONE;
+
+ return *this;
+}
+
+ScCellValue& ScCellValue::operator= ( const ScRefCellValue& r )
+{
+ ScCellValue aTmp(r);
+ swap(aTmp);
+ return *this;
+}
+
+void ScCellValue::swap( ScCellValue& r )
+{
+ std::swap(meType, r.meType);
+
+ // double is 8 bytes, whereas a pointer may be 4 or 8 bytes depending on
+ // the platform. Swap by double values.
+ std::swap(mfValue, r.mfValue);
+}
+
+ScRefCellValue::ScRefCellValue() : meType(CELLTYPE_NONE), mfValue(0.0) {}
+ScRefCellValue::ScRefCellValue( double fValue ) : meType(CELLTYPE_VALUE), mfValue(fValue) {}
+ScRefCellValue::ScRefCellValue( const svl::SharedString* pString ) : meType(CELLTYPE_STRING), mpString(pString) {}
+ScRefCellValue::ScRefCellValue( const EditTextObject* pEditText ) : meType(CELLTYPE_EDIT), mpEditText(pEditText) {}
+ScRefCellValue::ScRefCellValue( ScFormulaCell* pFormula ) : meType(CELLTYPE_FORMULA), mpFormula(pFormula) {}
+
+ScRefCellValue::ScRefCellValue( ScDocument& rDoc, const ScAddress& rPos )
+{
+ assign( rDoc, rPos);
+}
+
+ScRefCellValue::ScRefCellValue( ScDocument& rDoc, const ScAddress& rPos, sc::ColumnBlockPosition& rBlockPos )
+{
+ assign( rDoc, rPos, rBlockPos );
+}
+
+void ScRefCellValue::clear()
+{
+ // Reset to empty value.
+ meType = CELLTYPE_NONE;
+ mfValue = 0.0;
+}
+
+void ScRefCellValue::assign( ScDocument& rDoc, const ScAddress& rPos )
+{
+ *this = rDoc.GetRefCellValue(rPos);
+}
+
+void ScRefCellValue::assign( ScDocument& rDoc, const ScAddress& rPos, sc::ColumnBlockPosition& rBlockPos )
+{
+ *this = rDoc.GetRefCellValue(rPos, rBlockPos);
+}
+
+void ScRefCellValue::commit( ScDocument& rDoc, const ScAddress& rPos ) const
+{
+ switch (meType)
+ {
+ case CELLTYPE_STRING:
+ {
+ ScSetStringParam aParam;
+ aParam.setTextInput();
+ rDoc.SetString(rPos, mpString->getString(), &aParam);
+ }
+ break;
+ case CELLTYPE_EDIT:
+ rDoc.SetEditText(rPos, ScEditUtil::Clone(*mpEditText, rDoc));
+ break;
+ case CELLTYPE_VALUE:
+ rDoc.SetValue(rPos, mfValue);
+ break;
+ case CELLTYPE_FORMULA:
+ rDoc.SetFormulaCell(rPos, new ScFormulaCell(*mpFormula, rDoc, rPos));
+ break;
+ default:
+ rDoc.SetEmptyCell(rPos);
+ }
+}
+
+bool ScRefCellValue::hasString() const
+{
+ return hasStringImpl(meType, mpFormula);
+}
+
+bool ScRefCellValue::hasNumeric() const
+{
+ return hasNumericImpl(meType, mpFormula);
+}
+
+bool ScRefCellValue::hasError() const
+{
+ return meType == CELLTYPE_FORMULA && mpFormula->GetErrCode() != FormulaError::NONE;
+}
+
+double ScRefCellValue::getValue()
+{
+ switch (meType)
+ {
+ case CELLTYPE_VALUE:
+ return mfValue;
+ case CELLTYPE_FORMULA:
+ return mpFormula->GetValue();
+ default:
+ ;
+ }
+ return 0.0;
+}
+
+double ScRefCellValue::getRawValue() const
+{
+ switch (meType)
+ {
+ case CELLTYPE_VALUE:
+ return mfValue;
+ case CELLTYPE_FORMULA:
+ return mpFormula->GetRawValue();
+ default:
+ ;
+ }
+ return 0.0;
+}
+
+OUString ScRefCellValue::getString( const ScDocument* pDoc ) const
+{
+ return getStringImpl(*this, pDoc);
+}
+
+OUString ScRefCellValue::getRawString( const ScDocument& rDoc ) const
+{
+ return getRawStringImpl(*this, rDoc);
+}
+
+bool ScRefCellValue::isEmpty() const
+{
+ return meType == CELLTYPE_NONE;
+}
+
+bool ScRefCellValue::hasEmptyValue()
+{
+ if (isEmpty())
+ return true;
+
+ if (meType == CELLTYPE_FORMULA)
+ return mpFormula->IsEmpty();
+
+ return false;
+}
+
+bool ScRefCellValue::equalsWithoutFormat( const ScRefCellValue& r ) const
+{
+ return equalsWithoutFormatImpl(*this, r);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/cellvalues.cxx b/sc/source/core/data/cellvalues.cxx
new file mode 100644
index 000000000..290dc0d09
--- /dev/null
+++ b/sc/source/core/data/cellvalues.cxx
@@ -0,0 +1,357 @@
+/* -*- 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 <memory>
+#include <cellvalues.hxx>
+#include <column.hxx>
+#include <formulacell.hxx>
+
+#include <cassert>
+
+namespace sc {
+
+namespace {
+
+struct BlockPos
+{
+ size_t mnStart;
+ size_t mnEnd;
+};
+
+}
+
+CellValueSpan::CellValueSpan( SCROW nRow1, SCROW nRow2 ) :
+ mnRow1(nRow1), mnRow2(nRow2) {}
+
+struct CellValuesImpl
+{
+ CellStoreType maCells;
+ CellTextAttrStoreType maCellTextAttrs;
+ CellStoreType::iterator miCellPos;
+ CellTextAttrStoreType::iterator miAttrPos;
+
+ CellValuesImpl() = default;
+
+ CellValuesImpl(const CellValuesImpl&) = delete;
+ const CellValuesImpl& operator=(const CellValuesImpl&) = delete;
+};
+
+CellValues::CellValues() :
+ mpImpl(new CellValuesImpl) {}
+
+CellValues::~CellValues()
+{
+}
+
+void CellValues::transferFrom( ScColumn& rCol, SCROW nRow, size_t nLen )
+{
+ mpImpl->maCells.resize(nLen);
+ mpImpl->maCellTextAttrs.resize(nLen);
+ rCol.maCells.transfer(nRow, nRow+nLen-1, mpImpl->maCells, 0);
+ rCol.maCellTextAttrs.transfer(nRow, nRow+nLen-1, mpImpl->maCellTextAttrs, 0);
+}
+
+
+void CellValues::copyTo( ScColumn& rCol, SCROW nRow ) const
+{
+ copyCellsTo(rCol, nRow);
+ copyCellTextAttrsTo(rCol, nRow);
+}
+
+void CellValues::swapNonEmpty( ScColumn& rCol )
+{
+ std::vector<BlockPos> aBlocksToSwap;
+
+ // Go through static value blocks and record their positions and sizes.
+ for (const auto& rCell : mpImpl->maCells)
+ {
+ if (rCell.type == sc::element_type_empty)
+ continue;
+
+ BlockPos aPos;
+ aPos.mnStart = rCell.position;
+ aPos.mnEnd = aPos.mnStart + rCell.size - 1;
+ aBlocksToSwap.push_back(aPos);
+ }
+
+ // Do the swapping. The undo storage will store the replaced formula cells after this.
+ for (const auto& rBlock : aBlocksToSwap)
+ {
+ rCol.maCells.swap(rBlock.mnStart, rBlock.mnEnd, mpImpl->maCells, rBlock.mnStart);
+ rCol.maCellTextAttrs.swap(rBlock.mnStart, rBlock.mnEnd, mpImpl->maCellTextAttrs, rBlock.mnStart);
+ }
+}
+
+void CellValues::assign( const std::vector<double>& rVals )
+{
+ mpImpl->maCells.resize(rVals.size());
+ mpImpl->maCells.set(0, rVals.begin(), rVals.end());
+
+ // Set default text attributes.
+ std::vector<CellTextAttr> aDefaults(rVals.size(), CellTextAttr());
+ mpImpl->maCellTextAttrs.resize(rVals.size());
+ mpImpl->maCellTextAttrs.set(0, aDefaults.begin(), aDefaults.end());
+}
+
+void CellValues::assign( const std::vector<ScFormulaCell*>& rVals )
+{
+ std::vector<ScFormulaCell*> aCopyVals(rVals.size());
+ size_t nIdx = 0;
+ for (const auto* pCell : rVals)
+ {
+ aCopyVals[nIdx] = pCell->Clone();
+ ++nIdx;
+ }
+
+ mpImpl->maCells.resize(aCopyVals.size());
+ mpImpl->maCells.set(0, aCopyVals.begin(), aCopyVals.end());
+
+ // Set default text attributes.
+ std::vector<CellTextAttr> aDefaults(rVals.size(), CellTextAttr());
+ mpImpl->maCellTextAttrs.resize(rVals.size());
+ mpImpl->maCellTextAttrs.set(0, aDefaults.begin(), aDefaults.end());
+}
+
+size_t CellValues::size() const
+{
+ assert(mpImpl->maCells.size() == mpImpl->maCellTextAttrs.size());
+ return mpImpl->maCells.size();
+}
+
+void CellValues::reset( size_t nSize )
+{
+ mpImpl->maCells.clear();
+ mpImpl->maCells.resize(nSize);
+ mpImpl->maCellTextAttrs.clear();
+ mpImpl->maCellTextAttrs.resize(nSize);
+
+ mpImpl->miCellPos = mpImpl->maCells.begin();
+ mpImpl->miAttrPos = mpImpl->maCellTextAttrs.begin();
+}
+
+void CellValues::setValue( size_t nRow, double fVal )
+{
+ mpImpl->miCellPos = mpImpl->maCells.set(mpImpl->miCellPos, nRow, fVal);
+ mpImpl->miAttrPos = mpImpl->maCellTextAttrs.set(mpImpl->miAttrPos, nRow, sc::CellTextAttr());
+}
+
+void CellValues::setValue( size_t nRow, const svl::SharedString& rStr )
+{
+ mpImpl->miCellPos = mpImpl->maCells.set(mpImpl->miCellPos, nRow, rStr);
+ mpImpl->miAttrPos = mpImpl->maCellTextAttrs.set(mpImpl->miAttrPos, nRow, sc::CellTextAttr());
+}
+
+void CellValues::swap( CellValues& r )
+{
+ std::swap(mpImpl, r.mpImpl);
+}
+
+std::vector<CellValueSpan> CellValues::getNonEmptySpans() const
+{
+ std::vector<CellValueSpan> aRet;
+ for (const auto& rCell : mpImpl->maCells)
+ {
+ if (rCell.type != element_type_empty)
+ {
+ // Record this span.
+ size_t nRow1 = rCell.position;
+ size_t nRow2 = nRow1 + rCell.size - 1;
+ aRet.emplace_back(nRow1, nRow2);
+ }
+ }
+ return aRet;
+}
+
+void CellValues::copyCellsTo( ScColumn& rCol, SCROW nRow ) const
+{
+ CellStoreType& rDest = rCol.maCells;
+ const CellStoreType& rSrc = mpImpl->maCells;
+
+ // Caller must ensure the destination is long enough.
+ assert(rSrc.size() + static_cast<size_t>(nRow) <= rDest.size());
+
+ SCROW nCurRow = nRow;
+ CellStoreType::iterator itPos = rDest.begin();
+
+ for (const auto& rBlk : rSrc)
+ {
+ switch (rBlk.type)
+ {
+ case element_type_numeric:
+ {
+ numeric_block::const_iterator it = numeric_block::begin(*rBlk.data);
+ numeric_block::const_iterator itEnd = numeric_block::end(*rBlk.data);
+ itPos = rDest.set(itPos, nCurRow, it, itEnd);
+ }
+ break;
+ case element_type_string:
+ {
+ string_block::const_iterator it = string_block::begin(*rBlk.data);
+ string_block::const_iterator itEnd = string_block::end(*rBlk.data);
+ itPos = rDest.set(itPos, nCurRow, it, itEnd);
+ }
+ break;
+ case element_type_edittext:
+ {
+ edittext_block::const_iterator it = edittext_block::begin(*rBlk.data);
+ edittext_block::const_iterator itEnd = edittext_block::end(*rBlk.data);
+ std::vector<EditTextObject*> aVals;
+ aVals.reserve(rBlk.size);
+ for (; it != itEnd; ++it)
+ {
+ const EditTextObject* p = *it;
+ aVals.push_back(p->Clone().release());
+ }
+ itPos = rDest.set(itPos, nCurRow, aVals.begin(), aVals.end());
+ }
+ break;
+ case element_type_formula:
+ {
+ formula_block::const_iterator it = formula_block::begin(*rBlk.data);
+ formula_block::const_iterator itEnd = formula_block::end(*rBlk.data);
+ std::vector<ScFormulaCell*> aVals;
+ aVals.reserve(rBlk.size);
+ for (; it != itEnd; ++it)
+ {
+ const ScFormulaCell* p = *it;
+ aVals.push_back(p->Clone());
+ }
+ itPos = rDest.set(itPos, nCurRow, aVals.begin(), aVals.end());
+ }
+ break;
+ default:
+ itPos = rDest.set_empty(itPos, nCurRow, nCurRow+rBlk.size-1);
+ }
+
+ nCurRow += rBlk.size;
+ }
+}
+
+void CellValues::copyCellTextAttrsTo( ScColumn& rCol, SCROW nRow ) const
+{
+ CellTextAttrStoreType& rDest = rCol.maCellTextAttrs;
+ const CellTextAttrStoreType& rSrc = mpImpl->maCellTextAttrs;
+
+ // Caller must ensure the destination is long enough.
+ assert(rSrc.size() + static_cast<size_t>(nRow) <= rDest.size());
+
+ SCROW nCurRow = nRow;
+ CellTextAttrStoreType::iterator itPos = rDest.begin();
+
+ for (const auto& rBlk : rSrc)
+ {
+ switch (rBlk.type)
+ {
+ case element_type_celltextattr:
+ {
+ celltextattr_block::const_iterator it = celltextattr_block::begin(*rBlk.data);
+ celltextattr_block::const_iterator itEnd = celltextattr_block::end(*rBlk.data);
+ itPos = rDest.set(itPos, nCurRow, it, itEnd);
+ }
+ break;
+ default:
+ itPos = rDest.set_empty(itPos, nCurRow, nCurRow+rBlk.size-1);
+ }
+
+ nCurRow += rBlk.size;
+ }
+}
+
+typedef std::vector<std::unique_ptr<CellValues>> TableType;
+typedef std::vector<std::unique_ptr<TableType>> TablesType;
+
+struct TableValues::Impl
+{
+ ScRange maRange;
+ TablesType m_Tables;
+
+ explicit Impl( const ScRange& rRange ) : maRange(rRange)
+ {
+ size_t nTabs = rRange.aEnd.Tab() - rRange.aStart.Tab() + 1;
+ size_t nCols = rRange.aEnd.Col() - rRange.aStart.Col() + 1;
+
+ for (size_t nTab = 0; nTab < nTabs; ++nTab)
+ {
+ m_Tables.push_back(std::make_unique<TableType>());
+ std::unique_ptr<TableType>& rTab2 = m_Tables.back();
+ for (size_t nCol = 0; nCol < nCols; ++nCol)
+ rTab2->push_back(std::make_unique<CellValues>());
+ }
+ }
+
+ CellValues* getCellValues( SCTAB nTab, SCCOL nCol )
+ {
+ if (nTab < maRange.aStart.Tab() || maRange.aEnd.Tab() < nTab)
+ // sheet index out of bound.
+ return nullptr;
+ if (nCol < maRange.aStart.Col() || maRange.aEnd.Col() < nCol)
+ // column index out of bound.
+ return nullptr;
+ size_t nTabOffset = nTab - maRange.aStart.Tab();
+ if (nTabOffset >= m_Tables.size())
+ return nullptr;
+ std::unique_ptr<TableType>& rTab2 = m_Tables[nTab-maRange.aStart.Tab()];
+ size_t nColOffset = nCol - maRange.aStart.Col();
+ if (nColOffset >= rTab2->size())
+ return nullptr;
+ return &rTab2.get()[0][nColOffset].get()[0];
+ }
+};
+
+TableValues::TableValues() :
+ mpImpl(new Impl(ScRange(ScAddress::INITIALIZE_INVALID))) {}
+
+TableValues::TableValues( const ScRange& rRange ) :
+ mpImpl(new Impl(rRange)) {}
+
+TableValues::~TableValues()
+{
+}
+
+const ScRange& TableValues::getRange() const
+{
+ return mpImpl->maRange;
+}
+
+void TableValues::swap( SCTAB nTab, SCCOL nCol, CellValues& rColValue )
+{
+ CellValues* pCol = mpImpl->getCellValues(nTab, nCol);
+ if (!pCol)
+ return;
+
+ pCol->swap(rColValue);
+}
+
+void TableValues::swapNonEmpty( SCTAB nTab, SCCOL nCol, ScColumn& rCol )
+{
+ CellValues* pCol = mpImpl->getCellValues(nTab, nCol);
+ if (!pCol)
+ return;
+
+ pCol->swapNonEmpty(rCol);
+}
+
+std::vector<CellValueSpan> TableValues::getNonEmptySpans( SCTAB nTab, SCCOL nCol ) const
+{
+ std::vector<CellValueSpan> aRet;
+ CellValues* pCol = mpImpl->getCellValues(nTab, nCol);
+ if (pCol)
+ aRet = pCol->getNonEmptySpans();
+
+ return aRet;
+}
+
+void TableValues::swap( TableValues& rOther )
+{
+ std::swap(mpImpl, rOther.mpImpl);
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/clipcontext.cxx b/sc/source/core/data/clipcontext.cxx
new file mode 100644
index 000000000..f83ee2c9a
--- /dev/null
+++ b/sc/source/core/data/clipcontext.cxx
@@ -0,0 +1,440 @@
+/* -*- 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 <memory>
+#include <clipcontext.hxx>
+#include <document.hxx>
+#include <mtvelements.hxx>
+#include <column.hxx>
+#include <scitems.hxx>
+#include <tokenarray.hxx>
+#include <editutil.hxx>
+#include <clipparam.hxx>
+
+#include <svl/intitem.hxx>
+#include <svl/numformat.hxx>
+#include <formula/errorcodes.hxx>
+#include <refdata.hxx>
+#include <listenercontext.hxx>
+
+namespace sc {
+
+ClipContextBase::ClipContextBase(ScDocument& rDoc) :
+ mpSet(new ColumnBlockPositionSet(rDoc)) {}
+
+ClipContextBase::~ClipContextBase() {}
+
+ColumnBlockPosition* ClipContextBase::getBlockPosition(SCTAB nTab, SCCOL nCol)
+{
+ return mpSet->getBlockPosition(nTab, nCol);
+}
+
+CopyFromClipContext::CopyFromClipContext(ScDocument& rDoc,
+ ScDocument* pRefUndoDoc, ScDocument* pClipDoc, InsertDeleteFlags nInsertFlag,
+ bool bAsLink, bool bSkipEmptyCells) :
+ ClipContextBase(rDoc),
+ mnDestCol1(-1), mnDestCol2(-1),
+ mnDestRow1(-1), mnDestRow2(-1),
+ mnTabStart(-1), mnTabEnd(-1),
+ mrDestDoc(rDoc),
+ mpRefUndoDoc(pRefUndoDoc), mpClipDoc(pClipDoc),
+ mnInsertFlag(nInsertFlag), mnDeleteFlag(InsertDeleteFlags::NONE),
+ mpCondFormatList(nullptr),
+ mbAsLink(bAsLink), mbSkipEmptyCells(bSkipEmptyCells),
+ mbTableProtected(false)
+{
+}
+
+CopyFromClipContext::~CopyFromClipContext()
+{
+}
+
+void CopyFromClipContext::setTabRange(SCTAB nStart, SCTAB nEnd)
+{
+ mnTabStart = nStart;
+ mnTabEnd = nEnd;
+}
+
+SCTAB CopyFromClipContext::getTabStart() const
+{
+ return mnTabStart;
+}
+
+SCTAB CopyFromClipContext::getTabEnd() const
+{
+ return mnTabEnd;
+}
+
+void CopyFromClipContext::setDestRange( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
+{
+ mnDestCol1 = nCol1;
+ mnDestRow1 = nRow1;
+ mnDestCol2 = nCol2;
+ mnDestRow2 = nRow2;
+}
+
+CopyFromClipContext::Range CopyFromClipContext::getDestRange() const
+{
+ Range aRet;
+ aRet.mnCol1 = mnDestCol1;
+ aRet.mnCol2 = mnDestCol2;
+ aRet.mnRow1 = mnDestRow1;
+ aRet.mnRow2 = mnDestRow2;
+ return aRet;
+}
+
+ScDocument* CopyFromClipContext::getUndoDoc()
+{
+ return mpRefUndoDoc;
+}
+
+ScDocument* CopyFromClipContext::getClipDoc()
+{
+ return mpClipDoc;
+}
+
+InsertDeleteFlags CopyFromClipContext::getInsertFlag() const
+{
+ return mnInsertFlag;
+}
+
+void CopyFromClipContext::setDeleteFlag( InsertDeleteFlags nFlag )
+{
+ mnDeleteFlag = nFlag;
+}
+
+InsertDeleteFlags CopyFromClipContext::getDeleteFlag() const
+{
+ return mnDeleteFlag;
+}
+
+void CopyFromClipContext::setListeningFormulaSpans(
+ SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
+{
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ maListeningFormulaSpans.set(mrDestDoc, nTab, nCol, nRow1, nRow2, true);
+}
+
+namespace {
+
+class StartListeningAction : public sc::ColumnSpanSet::Action
+{
+ ScDocument& mrDestDoc;
+ sc::StartListeningContext& mrStartCxt;
+ sc::EndListeningContext& mrEndCxt;
+
+public:
+ StartListeningAction( ScDocument& rDestDoc, sc::StartListeningContext& rStartCxt, sc::EndListeningContext& rEndCxt ) :
+ mrDestDoc(rDestDoc), mrStartCxt(rStartCxt), mrEndCxt(rEndCxt)
+ {
+ }
+
+ virtual void execute( const ScAddress& rPos, SCROW nLength, bool bVal ) override
+ {
+ if (!bVal)
+ return;
+
+ SCROW nRow1 = rPos.Row();
+ SCROW nRow2 = nRow1 + nLength - 1;
+
+ mrDestDoc.StartListeningFromClip(
+ mrStartCxt, mrEndCxt, rPos.Tab(), rPos.Col(), nRow1, rPos.Col(), nRow2);
+ }
+};
+
+}
+
+void CopyFromClipContext::startListeningFormulas()
+{
+ auto pSet = std::make_shared<sc::ColumnBlockPositionSet>(mrDestDoc);
+ sc::StartListeningContext aStartCxt(mrDestDoc, pSet);
+ sc::EndListeningContext aEndCxt(mrDestDoc, pSet, nullptr);
+
+ StartListeningAction aAction(mrDestDoc, aStartCxt, aEndCxt);
+ maListeningFormulaSpans.executeAction(mrDestDoc, aAction);
+}
+
+void CopyFromClipContext::setSingleCellColumnSize( size_t nSize )
+{
+ maSingleCells.resize(nSize);
+ maSingleCellAttrs.resize(nSize);
+ maSinglePatterns.resize(nSize, nullptr);
+ maSingleNotes.resize(nSize, nullptr);
+ maSingleSparkline.resize(nSize);
+}
+
+ScCellValue& CopyFromClipContext::getSingleCell( size_t nColOffset )
+{
+ assert(nColOffset < maSingleCells.size());
+ return maSingleCells[nColOffset];
+}
+
+sc::CellTextAttr& CopyFromClipContext::getSingleCellAttr( size_t nColOffset )
+{
+ assert(nColOffset < maSingleCellAttrs.size());
+ return maSingleCellAttrs[nColOffset];
+}
+
+void CopyFromClipContext::setSingleCell( const ScAddress& rSrcPos, const ScColumn& rSrcCol )
+{
+ SCCOL nColOffset = rSrcPos.Col() - mpClipDoc->GetClipParam().getWholeRange().aStart.Col();
+ ScCellValue& rSrcCell = getSingleCell(nColOffset);
+
+ const sc::CellTextAttr* pAttr = rSrcCol.GetCellTextAttr(rSrcPos.Row());
+
+ if (pAttr)
+ {
+ sc::CellTextAttr& rAttr = getSingleCellAttr(nColOffset);
+ rAttr = *pAttr;
+ }
+
+ if (mbAsLink)
+ {
+ ScSingleRefData aRef;
+ aRef.InitAddress(rSrcPos);
+ aRef.SetFlag3D(true);
+
+ ScTokenArray aArr(*mpClipDoc);
+ aArr.AddSingleReference(aRef);
+ rSrcCell.set(new ScFormulaCell(*mpClipDoc, rSrcPos, aArr));
+ return;
+ }
+
+ rSrcCell.assign(*mpClipDoc, rSrcPos);
+
+ // Check the paste flag to see whether we want to paste this cell. If the
+ // flag says we don't want to paste this cell, we'll return with true.
+ InsertDeleteFlags nFlags = getInsertFlag();
+ bool bNumeric = (nFlags & InsertDeleteFlags::VALUE) != InsertDeleteFlags::NONE;
+ bool bDateTime = (nFlags & InsertDeleteFlags::DATETIME) != InsertDeleteFlags::NONE;
+ bool bString = (nFlags & InsertDeleteFlags::STRING) != InsertDeleteFlags::NONE;
+ bool bBoolean = (nFlags & InsertDeleteFlags::SPECIAL_BOOLEAN) != InsertDeleteFlags::NONE;
+ bool bFormula = (nFlags & InsertDeleteFlags::FORMULA) != InsertDeleteFlags::NONE;
+
+ switch (rSrcCell.meType)
+ {
+ case CELLTYPE_VALUE:
+ {
+ bool bPaste = isDateCell(rSrcCol, rSrcPos.Row()) ? bDateTime : bNumeric;
+ if (!bPaste)
+ // Don't paste this.
+ rSrcCell.clear();
+ }
+ break;
+ case CELLTYPE_STRING:
+ case CELLTYPE_EDIT:
+ {
+ if (!bString)
+ // Skip pasting.
+ rSrcCell.clear();
+ }
+ break;
+ case CELLTYPE_FORMULA:
+ {
+ if (bBoolean)
+ {
+ // Check if this formula cell is a boolean cell, and if so, go ahead and paste it.
+ const ScTokenArray* pCode = rSrcCell.mpFormula->GetCode();
+ if (pCode && pCode->GetLen() == 1)
+ {
+ const formula::FormulaToken* p = pCode->FirstToken();
+ if (p->GetOpCode() == ocTrue || p->GetOpCode() == ocFalse)
+ // This is a boolean formula. Good.
+ break;
+ }
+ }
+
+ if (bFormula)
+ // Good.
+ break;
+
+ FormulaError nErr = rSrcCell.mpFormula->GetErrCode();
+ if (nErr != FormulaError::NONE)
+ {
+ // error codes are cloned with values
+ if (!bNumeric)
+ // Error code is treated as numeric value. Don't paste it.
+ rSrcCell.clear();
+ else
+ {
+ // Turn this into a formula cell with just the error code.
+ ScFormulaCell* pErrCell = new ScFormulaCell(*mpClipDoc, rSrcPos);
+ pErrCell->SetErrCode(nErr);
+ rSrcCell.set(pErrCell);
+ }
+ }
+ else if (rSrcCell.mpFormula->IsEmptyDisplayedAsString())
+ {
+ // Empty stays empty and doesn't become 0.
+ rSrcCell.clear();
+ }
+ else if (rSrcCell.mpFormula->IsValue())
+ {
+ bool bPaste = isDateCell(rSrcCol, rSrcPos.Row()) ? bDateTime : bNumeric;
+ if (!bPaste)
+ {
+ // Don't paste this.
+ rSrcCell.clear();
+ break;
+ }
+
+ // Turn this into a numeric cell.
+ rSrcCell.set(rSrcCell.mpFormula->GetValue());
+ }
+ else if (bString)
+ {
+ svl::SharedString aStr = rSrcCell.mpFormula->GetString();
+ if (aStr.isEmpty())
+ {
+ // do not clone empty string
+ rSrcCell.clear();
+ break;
+ }
+
+ // Turn this into a string or edit cell.
+ if (rSrcCell.mpFormula->IsMultilineResult())
+ {
+ // TODO : Add shared string support to the edit engine to
+ // make this process simpler.
+ ScFieldEditEngine& rEngine = mrDestDoc.GetEditEngine();
+ rEngine.SetTextCurrentDefaults(rSrcCell.mpFormula->GetString().getString());
+ std::unique_ptr<EditTextObject> pObj(rEngine.CreateTextObject());
+ pObj->NormalizeString(mrDestDoc.GetSharedStringPool());
+ rSrcCell.set(*pObj);
+ }
+ else
+ rSrcCell.set(rSrcCell.mpFormula->GetString());
+ }
+ else
+ // We don't want to paste this.
+ rSrcCell.clear();
+ }
+ break;
+ case CELLTYPE_NONE:
+ default:
+ // There is nothing to paste.
+ rSrcCell.clear();
+ }
+}
+
+const ScPatternAttr* CopyFromClipContext::getSingleCellPattern( size_t nColOffset ) const
+{
+ assert(nColOffset < maSinglePatterns.size());
+ return maSinglePatterns[nColOffset];
+}
+
+void CopyFromClipContext::setSingleCellPattern( size_t nColOffset, const ScPatternAttr* pAttr )
+{
+ assert(nColOffset < maSinglePatterns.size());
+ maSinglePatterns[nColOffset] = pAttr;
+}
+
+const ScPostIt* CopyFromClipContext::getSingleCellNote( size_t nColOffset ) const
+{
+ assert(nColOffset < maSingleNotes.size());
+ return maSingleNotes[nColOffset];
+}
+
+void CopyFromClipContext::setSingleCellNote( size_t nColOffset, const ScPostIt* pNote )
+{
+ assert(nColOffset < maSingleNotes.size());
+ maSingleNotes[nColOffset] = pNote;
+}
+
+std::shared_ptr<sc::Sparkline> const& CopyFromClipContext::getSingleSparkline(size_t nColOffset) const
+{
+ assert(nColOffset < maSingleSparkline.size());
+ return maSingleSparkline[nColOffset];
+}
+
+void CopyFromClipContext::setSingleSparkline(size_t nColOffset, std::shared_ptr<sc::Sparkline> const& pSparkline)
+{
+ assert(nColOffset < maSingleSparkline.size());
+ maSingleSparkline[nColOffset] = pSparkline;
+}
+
+void CopyFromClipContext::setCondFormatList( ScConditionalFormatList* pCondFormatList )
+{
+ mpCondFormatList = pCondFormatList;
+}
+
+ScConditionalFormatList* CopyFromClipContext::getCondFormatList()
+{
+ return mpCondFormatList;
+}
+
+void CopyFromClipContext::setTableProtected( bool b )
+{
+ mbTableProtected = b;
+}
+
+bool CopyFromClipContext::isTableProtected() const
+{
+ return mbTableProtected;
+}
+
+bool CopyFromClipContext::isAsLink() const
+{
+ return mbAsLink;
+}
+
+bool CopyFromClipContext::isSkipEmptyCells() const
+{
+ return mbSkipEmptyCells;
+}
+
+bool CopyFromClipContext::isCloneNotes() const
+{
+ return bool(mnInsertFlag & (InsertDeleteFlags::NOTE | InsertDeleteFlags::ADDNOTES));
+}
+
+bool CopyFromClipContext::isCloneSparklines() const
+{
+ return bool(mnInsertFlag & InsertDeleteFlags::SPARKLINES);
+}
+
+bool CopyFromClipContext::isDateCell( const ScColumn& rCol, SCROW nRow ) const
+{
+ sal_uLong nNumIndex = rCol.GetAttr(nRow, ATTR_VALUE_FORMAT).GetValue();
+ SvNumFormatType nType = mpClipDoc->GetFormatTable()->GetType(nNumIndex);
+ return (nType == SvNumFormatType::DATE) || (nType == SvNumFormatType::TIME) || (nType == SvNumFormatType::DATETIME);
+}
+
+CopyToClipContext::CopyToClipContext(
+ ScDocument& rDoc, bool bKeepScenarioFlags) :
+ ClipContextBase(rDoc), mbKeepScenarioFlags(bKeepScenarioFlags) {}
+
+CopyToClipContext::~CopyToClipContext() {}
+
+bool CopyToClipContext::isKeepScenarioFlags() const
+{
+ return mbKeepScenarioFlags;
+}
+
+CopyToDocContext::CopyToDocContext(ScDocument& rDoc) :
+ ClipContextBase(rDoc), mbStartListening(true) {}
+
+CopyToDocContext::~CopyToDocContext() {}
+
+void CopyToDocContext::setStartListening( bool b )
+{
+ mbStartListening = b;
+}
+
+bool CopyToDocContext::isStartListening() const
+{
+ return mbStartListening;
+}
+
+MixDocContext::MixDocContext(ScDocument& rDoc) : ClipContextBase(rDoc) {}
+MixDocContext::~MixDocContext() {}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/clipparam.cxx b/sc/source/core/data/clipparam.cxx
new file mode 100644
index 000000000..c2255adee
--- /dev/null
+++ b/sc/source/core/data/clipparam.cxx
@@ -0,0 +1,189 @@
+/* -*- 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 <clipparam.hxx>
+
+
+ScClipParam::ScClipParam() :
+ meDirection(Unspecified),
+ mbCutMode(false),
+ mnSourceDocID(0)
+{
+}
+
+ScClipParam::ScClipParam(const ScRange& rRange, bool bCutMode) :
+ meDirection(Unspecified),
+ mbCutMode(bCutMode),
+ mnSourceDocID(0)
+{
+ maRanges.push_back(rRange);
+}
+
+bool ScClipParam::isMultiRange() const
+{
+ return maRanges.size() > 1;
+}
+
+SCCOL ScClipParam::getPasteColSize()
+{
+ if (maRanges.empty())
+ return 0;
+
+ switch (meDirection)
+ {
+ case ScClipParam::Column:
+ {
+ SCCOL nColSize = 0;
+ for ( size_t i = 0, nListSize = maRanges.size(); i < nListSize; ++i )
+ {
+ const ScRange& rRange = maRanges[ i ];
+ nColSize += rRange.aEnd.Col() - rRange.aStart.Col() + 1;
+ }
+ return nColSize;
+ }
+ case ScClipParam::Row:
+ {
+ // We assume that all ranges have identical column size.
+ const ScRange& rRange = maRanges.front();
+ return rRange.aEnd.Col() - rRange.aStart.Col() + 1;
+ }
+ case ScClipParam::Unspecified:
+ default:
+ ;
+ }
+ return 0;
+}
+
+SCROW ScClipParam::getPasteRowSize(const ScDocument& rSrcDoc, bool bIncludeFiltered)
+{
+ if (maRanges.empty())
+ return 0;
+
+ switch (meDirection)
+ {
+ case ScClipParam::Column:
+ {
+ // We assume that all ranges have identical row size.
+ const ScRange& rRange = maRanges.front();
+ return bIncludeFiltered
+ ? rRange.aEnd.Row() - rRange.aStart.Row() + 1
+ : rSrcDoc.CountNonFilteredRows(rRange.aStart.Row(), rRange.aEnd.Row(),
+ rRange.aStart.Tab());
+ }
+ case ScClipParam::Row:
+ {
+ SCROW nRowSize = 0;
+ for ( size_t i = 0, nListSize = maRanges.size(); i < nListSize; ++i )
+ {
+ const ScRange& rRange = maRanges[ i ];
+ nRowSize += bIncludeFiltered ? rRange.aEnd.Row() - rRange.aStart.Row() + 1
+ : rSrcDoc.CountNonFilteredRows(rRange.aStart.Row(),
+ rRange.aEnd.Row(),
+ rRange.aStart.Tab());
+ }
+ return nRowSize;
+ }
+ case ScClipParam::Unspecified:
+ default:
+ ;
+ }
+ return 0;
+}
+
+ScRange ScClipParam::getWholeRange() const
+{
+ return maRanges.Combine();
+}
+
+void ScClipParam::transpose(const ScDocument& rSrcDoc, bool bIncludeFiltered,
+ bool bIsMultiRangeRowFilteredTranspose)
+{
+ mbTransposed = true;
+
+ switch (meDirection)
+ {
+ case Column:
+ meDirection = ScClipParam::Row;
+ break;
+ case Row:
+ meDirection = ScClipParam::Column;
+ break;
+ case Unspecified:
+ default:
+ ;
+ }
+
+ ScRangeList aNewRanges;
+ if (!maRanges.empty())
+ {
+ const ScRange & rRange1 = maRanges.front();
+ SCCOL nColOrigin = rRange1.aStart.Col();
+ SCROW nRowOrigin = rRange1.aStart.Row();
+ SCROW nRowCount = 0;
+ for ( size_t i = 0, n = maRanges.size(); i < n; ++i )
+ {
+ const ScRange & rRange = maRanges[ i ];
+ SCCOL nColDelta = rRange.aStart.Col() - nColOrigin;
+ SCROW nRowDelta = rRange.aStart.Row() - nRowOrigin;
+ SCROW nNonFilteredRows = rSrcDoc.CountNonFilteredRows(
+ rRange.aStart.Row(), rRange.aEnd.Row(), rRange.aStart.Tab());
+ if (!bIsMultiRangeRowFilteredTranspose)
+ {
+ SCCOL nCol1 = 0;
+ SCCOL nCol2 = bIncludeFiltered
+ ? static_cast<SCCOL>(rRange.aEnd.Row() - rRange.aStart.Row())
+ : nNonFilteredRows - 1;
+ SCROW nRow1 = 0;
+ SCROW nRow2 = static_cast<SCROW>(rRange.aEnd.Col() - rRange.aStart.Col());
+ nCol1 += static_cast<SCCOL>(nRowDelta);
+ nCol2 += static_cast<SCCOL>(nRowDelta);
+ nRow1 += static_cast<SCROW>(nColDelta);
+ nRow2 += static_cast<SCROW>(nColDelta);
+ aNewRanges.push_back(ScRange(nColOrigin + nCol1, nRowOrigin + nRow1,
+ rRange.aStart.Tab(), nColOrigin + nCol2,
+ nRowOrigin + nRow2, rRange.aStart.Tab()));
+ }
+ else
+ nRowCount += nNonFilteredRows;
+ }
+
+ // Transpose of filtered multi range row selection is a special case since filtering
+ // and selection are in the same dimension (i.e. row), see ScDocument::TransposeClip()
+ if (bIsMultiRangeRowFilteredTranspose)
+ {
+ assert(!bIncludeFiltered && "bIsMultiRangeRowFilteredTranspose can only be true if bIncludeFiltered is false");
+ SCCOL nColDelta = rRange1.aStart.Col() - nColOrigin;
+ SCROW nRowDelta = rRange1.aStart.Row() - nRowOrigin;
+ SCCOL nCol1 = 0;
+ SCCOL nCol2 = nRowCount - 1;
+ SCROW nRow1 = 0;
+ SCROW nRow2 = static_cast<SCROW>(rRange1.aEnd.Col() - rRange1.aStart.Col());
+ nCol1 += static_cast<SCCOL>(nRowDelta);
+ nCol2 += static_cast<SCCOL>(nRowDelta);
+ nRow1 += static_cast<SCROW>(nColDelta);
+ nRow2 += static_cast<SCROW>(nColDelta);
+ aNewRanges.push_back(ScRange(nColOrigin + nCol1, nRowOrigin + nRow1,
+ rRange1.aStart.Tab(), nColOrigin + nCol2,
+ nRowOrigin + nRow2, rRange1.aStart.Tab()));
+ }
+ }
+ maRanges = aNewRanges;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/colcontainer.cxx b/sc/source/core/data/colcontainer.cxx
new file mode 100644
index 000000000..a0a9d8457
--- /dev/null
+++ b/sc/source/core/data/colcontainer.cxx
@@ -0,0 +1,55 @@
+/* -*- 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 <colcontainer.hxx>
+#include <column.hxx>
+
+ScColContainer::ScColContainer( ScSheetLimits const & rSheetLimits, const size_t nSize )
+{
+ aCols.resize( nSize );
+ for ( size_t nCol = 0; nCol < nSize; ++nCol )
+ aCols[nCol].reset( new ScColumn(rSheetLimits) );
+}
+
+ScColContainer::~ScColContainer() COVERITY_NOEXCEPT_FALSE
+{
+ Clear();
+}
+
+void ScColContainer::Clear()
+{
+ SCCOL nSize = size();
+ for ( SCCOL nIdx = 0; nIdx < nSize; ++nIdx )
+ {
+ aCols[nIdx]->PrepareBroadcastersForDestruction();
+ aCols[nIdx].reset();
+ }
+ aCols.clear();
+}
+
+void ScColContainer::resize( ScSheetLimits const & rSheetLimits, const size_t aNewColSize )
+{
+ size_t aOldColSize = aCols.size();
+ aCols.resize( aNewColSize );
+ for ( size_t nCol = aOldColSize; nCol < aNewColSize; ++nCol )
+ aCols[nCol].reset(new ScColumn(rSheetLimits));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/colorscale.cxx b/sc/source/core/data/colorscale.cxx
new file mode 100644
index 000000000..0a357828c
--- /dev/null
+++ b/sc/source/core/data/colorscale.cxx
@@ -0,0 +1,1445 @@
+/* -*- 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 <memory>
+#include <colorscale.hxx>
+#include <document.hxx>
+#include <formulacell.hxx>
+#include <fillinfo.hxx>
+#include <bitmaps.hlst>
+#include <tokenarray.hxx>
+#include <refupdatecontext.hxx>
+#include <refdata.hxx>
+#include <editeng/fhgtitem.hxx>
+#include <scitems.hxx>
+
+#include <formula/token.hxx>
+#include <vcl/bitmapex.hxx>
+
+#include <algorithm>
+#include <cassert>
+
+ScFormulaListener::ScFormulaListener(ScFormulaCell* pCell):
+ mbDirty(false),
+ mrDoc(pCell->GetDocument())
+{
+ startListening( pCell->GetCode(), pCell->aPos );
+}
+
+ScFormulaListener::ScFormulaListener(ScDocument& rDoc):
+ mbDirty(false),
+ mrDoc(rDoc)
+{
+}
+
+ScFormulaListener::ScFormulaListener(ScDocument& rDoc, const ScRangeList& rRange):
+ mbDirty(false),
+ mrDoc(rDoc)
+{
+ startListening(rRange);
+}
+
+void ScFormulaListener::startListening(const ScTokenArray* pArr, const ScRange& rRange)
+{
+ if (!pArr || mrDoc.IsClipOrUndo())
+ return;
+
+ for ( auto t: pArr->References() )
+ {
+ switch (t->GetType())
+ {
+ case formula::svSingleRef:
+ {
+ ScAddress aCell = t->GetSingleRef()->toAbs(mrDoc, rRange.aStart);
+ ScAddress aCell2 = t->GetSingleRef()->toAbs(mrDoc, rRange.aEnd);
+ ScRange aRange(aCell, aCell2);
+ if (aRange.IsValid())
+ mrDoc.StartListeningArea(aRange, false, this);
+ }
+ break;
+ case formula::svDoubleRef:
+ {
+ const ScSingleRefData& rRef1 = *t->GetSingleRef();
+ const ScSingleRefData& rRef2 = *t->GetSingleRef2();
+ ScAddress aCell1 = rRef1.toAbs(mrDoc, rRange.aStart);
+ ScAddress aCell2 = rRef2.toAbs(mrDoc, rRange.aStart);
+ ScAddress aCell3 = rRef1.toAbs(mrDoc, rRange.aEnd);
+ ScAddress aCell4 = rRef2.toAbs(mrDoc, rRange.aEnd);
+ ScRange aRange1(aCell1, aCell3);
+ ScRange aRange2(aCell2, aCell4);
+ aRange1.ExtendTo(aRange2);
+ if (aRange1.IsValid())
+ {
+ if (t->GetOpCode() == ocColRowNameAuto)
+ { // automagically
+ if ( rRef1.IsColRel() )
+ { // ColName
+ aRange1.aEnd.SetRow(mrDoc.MaxRow());
+ }
+ else
+ { // RowName
+ aRange1.aEnd.SetCol(mrDoc.MaxCol());
+ }
+ }
+ mrDoc.StartListeningArea(aRange1, false, this);
+ }
+ }
+ break;
+ default:
+ ; // nothing
+ }
+ }
+}
+
+void ScFormulaListener::startListening(const ScRangeList& rRange)
+{
+ if (mrDoc.IsClipOrUndo())
+ return;
+
+ size_t nLength = rRange.size();
+ for (size_t i = 0; i < nLength; ++i)
+ {
+ const ScRange& aRange = rRange[i];
+ mrDoc.StartListeningArea(aRange, false, this);
+ }
+}
+
+void ScFormulaListener::addTokenArray(const ScTokenArray* pArray, const ScRange& rRange)
+{
+ startListening(pArray, rRange);
+}
+
+void ScFormulaListener::setCallback(const std::function<void()>& aCallback)
+{
+ maCallbackFunction = aCallback;
+}
+
+void ScFormulaListener::stopListening()
+{
+ if (mrDoc.IsClipOrUndo())
+ return;
+
+ EndListeningAll();
+}
+
+ScFormulaListener::~ScFormulaListener()
+{
+ stopListening();
+}
+
+void ScFormulaListener::Notify(const SfxHint& rHint)
+{
+ mbDirty = true;
+
+ if (rHint.GetId() == SfxHintId::Dying)
+ return;
+
+ if (maCallbackFunction)
+ maCallbackFunction();
+}
+
+bool ScFormulaListener::NeedsRepaint() const
+{
+ bool bRet = mbDirty;
+ mbDirty = false;
+ return bRet;
+}
+
+ScColorScaleEntry::ScColorScaleEntry():
+ mnVal(0),
+ mpFormat(nullptr),
+ meType(COLORSCALE_VALUE)
+{
+}
+
+ScColorScaleEntry::ScColorScaleEntry(double nVal, const Color& rCol, ScColorScaleEntryType eType):
+ mnVal(nVal),
+ mpFormat(nullptr),
+ maColor(rCol),
+ meType(eType)
+{
+}
+
+ScColorScaleEntry::ScColorScaleEntry(const ScColorScaleEntry& rEntry):
+ mnVal(rEntry.mnVal),
+ mpFormat(rEntry.mpFormat),
+ maColor(rEntry.maColor),
+ meType(rEntry.meType)
+{
+ setListener();
+ if(rEntry.mpCell)
+ {
+ mpCell.reset(new ScFormulaCell(*rEntry.mpCell, rEntry.mpCell->GetDocument(), rEntry.mpCell->aPos, ScCloneFlags::NoMakeAbsExternal));
+ mpCell->StartListeningTo(mpCell->GetDocument());
+ mpListener.reset(new ScFormulaListener(mpCell.get()));
+ }
+}
+
+ScColorScaleEntry::ScColorScaleEntry(ScDocument* pDoc, const ScColorScaleEntry& rEntry):
+ mnVal(rEntry.mnVal),
+ mpFormat(rEntry.mpFormat),
+ maColor(rEntry.maColor),
+ meType(rEntry.meType)
+{
+ setListener();
+ if(rEntry.mpCell)
+ {
+ mpCell.reset(new ScFormulaCell(*rEntry.mpCell, *pDoc, rEntry.mpCell->aPos, ScCloneFlags::NoMakeAbsExternal));
+ mpCell->StartListeningTo( *pDoc );
+ mpListener.reset(new ScFormulaListener(mpCell.get()));
+ if (mpFormat)
+ mpListener->setCallback([&]() { mpFormat->DoRepaint();});
+ }
+}
+
+ScColorScaleEntry::~ScColorScaleEntry() COVERITY_NOEXCEPT_FALSE
+{
+ if(mpCell)
+ mpCell->EndListeningTo(mpCell->GetDocument());
+}
+
+void ScColorScaleEntry::SetFormula( const OUString& rFormula, ScDocument& rDoc, const ScAddress& rAddr, formula::FormulaGrammar::Grammar eGrammar )
+{
+ mpCell.reset(new ScFormulaCell( rDoc, rAddr, rFormula, eGrammar ));
+ mpCell->StartListeningTo( rDoc );
+ mpListener.reset(new ScFormulaListener(mpCell.get()));
+ if (mpFormat)
+ mpListener->setCallback([&]() { mpFormat->DoRepaint();});
+}
+
+const ScTokenArray* ScColorScaleEntry::GetFormula() const
+{
+ if(mpCell)
+ {
+ return mpCell->GetCode();
+ }
+
+ return nullptr;
+}
+
+OUString ScColorScaleEntry::GetFormula( formula::FormulaGrammar::Grammar eGrammar ) const
+{
+ if(mpCell)
+ {
+ return mpCell->GetFormula(eGrammar);
+ }
+
+ return OUString();
+}
+
+double ScColorScaleEntry::GetValue() const
+{
+ if(mpCell)
+ {
+ mpCell->Interpret();
+ if(mpCell->IsValue())
+ return mpCell->GetValue();
+
+ return std::numeric_limits<double>::max();
+ }
+
+ return mnVal;
+}
+
+void ScColorScaleEntry::SetValue(double nValue)
+{
+ mnVal = nValue;
+ mpCell.reset();
+ setListener();
+}
+
+void ScColorScaleEntry::UpdateReference( const sc::RefUpdateContext& rCxt )
+{
+ if (!mpCell)
+ {
+ setListener();
+ return;
+ }
+
+ mpCell->UpdateReference(rCxt);
+ mpListener.reset(new ScFormulaListener(mpCell.get()));
+ SetRepaintCallback(mpFormat);
+}
+
+void ScColorScaleEntry::UpdateInsertTab( const sc::RefUpdateInsertTabContext& rCxt )
+{
+ if (!mpCell)
+ {
+ setListener();
+ return;
+ }
+
+ mpCell->UpdateInsertTab(rCxt);
+ mpListener.reset(new ScFormulaListener(mpCell.get()));
+ SetRepaintCallback(mpFormat);
+}
+
+void ScColorScaleEntry::UpdateDeleteTab( const sc::RefUpdateDeleteTabContext& rCxt )
+{
+ if (!mpCell)
+ {
+ setListener();
+ return;
+ }
+
+ mpCell->UpdateDeleteTab(rCxt);
+ mpListener.reset(new ScFormulaListener(mpCell.get()));
+ SetRepaintCallback(mpFormat);
+}
+
+void ScColorScaleEntry::UpdateMoveTab( const sc::RefUpdateMoveTabContext& rCxt )
+{
+ if (!mpCell)
+ {
+ setListener();
+ return;
+ }
+
+ SCTAB nTabNo = rCxt.getNewTab(mpCell->aPos.Tab());
+ mpCell->UpdateMoveTab(rCxt, nTabNo);
+ mpListener.reset(new ScFormulaListener(mpCell.get()));
+ SetRepaintCallback(mpFormat);
+}
+
+void ScColorScaleEntry::SetColor(const Color& rColor)
+{
+ maColor = rColor;
+}
+
+void ScColorScaleEntry::SetRepaintCallback(ScConditionalFormat* pFormat)
+{
+ mpFormat = pFormat;
+ setListener();
+ if (mpFormat && mpListener)
+ mpListener->setCallback([&]() { mpFormat->DoRepaint();});
+}
+
+void ScColorScaleEntry::SetType( ScColorScaleEntryType eType )
+{
+ meType = eType;
+ if(eType != COLORSCALE_FORMULA)
+ {
+ mpCell.reset();
+ mpListener.reset();
+ }
+
+ setListener();
+}
+
+void ScColorScaleEntry::setListener()
+{
+ if (!mpFormat)
+ return;
+
+ if (meType == COLORSCALE_PERCENT || meType == COLORSCALE_PERCENTILE
+ || meType == COLORSCALE_MIN || meType == COLORSCALE_MAX
+ || meType == COLORSCALE_AUTO)
+ {
+ mpListener.reset(new ScFormulaListener(*mpFormat->GetDocument(), mpFormat->GetRange()));
+ mpListener->setCallback([&]() { mpFormat->DoRepaint();});
+ }
+}
+
+void ScColorScaleEntry::SetRepaintCallback(const std::function<void()>& func)
+{
+ mpListener->setCallback(func);
+}
+
+ScColorFormat::ScColorFormat(ScDocument* pDoc)
+ : ScFormatEntry(pDoc)
+ , mpParent(nullptr)
+{
+}
+
+ScColorFormat::~ScColorFormat()
+{
+}
+
+void ScColorFormat::SetParent( ScConditionalFormat* pParent )
+{
+ mpParent = pParent;
+}
+
+ScColorScaleFormat::ScColorScaleFormat(ScDocument* pDoc):
+ ScColorFormat(pDoc)
+{
+}
+
+ScColorScaleFormat::ScColorScaleFormat(ScDocument* pDoc, const ScColorScaleFormat& rFormat):
+ ScColorFormat(pDoc)
+{
+ for(const auto& rxEntry : rFormat)
+ {
+ maColorScales.emplace_back(new ScColorScaleEntry(pDoc, *rxEntry));
+ }
+}
+
+ScColorFormat* ScColorScaleFormat::Clone(ScDocument* pDoc) const
+{
+ return new ScColorScaleFormat(pDoc, *this);
+}
+
+ScColorScaleFormat::~ScColorScaleFormat()
+{
+}
+
+void ScColorScaleFormat::SetParent(ScConditionalFormat* pFormat)
+{
+ for (auto itr = begin(), itrEnd = end(); itr != itrEnd; ++itr)
+ {
+ (*itr)->SetRepaintCallback(pFormat);
+ }
+ ScColorFormat::SetParent(pFormat);
+}
+
+void ScColorScaleFormat::AddEntry( ScColorScaleEntry* pEntry )
+{
+ maColorScales.push_back(std::unique_ptr<ScColorScaleEntry, o3tl::default_delete<ScColorScaleEntry>>(pEntry));
+ maColorScales.back()->SetRepaintCallback(mpParent);
+}
+
+double ScColorScaleFormat::GetMinValue() const
+{
+ ScColorScaleEntries::const_iterator itr = maColorScales.begin();
+
+ if((*itr)->GetType() == COLORSCALE_VALUE || (*itr)->GetType() == COLORSCALE_FORMULA)
+ return (*itr)->GetValue();
+ else
+ {
+ return getMinValue();
+ }
+}
+
+double ScColorScaleFormat::GetMaxValue() const
+{
+ ScColorScaleEntries::const_reverse_iterator itr = maColorScales.rbegin();
+
+ if((*itr)->GetType() == COLORSCALE_VALUE || (*itr)->GetType() == COLORSCALE_FORMULA)
+ return (*itr)->GetValue();
+ else
+ {
+ return getMaxValue();
+ }
+}
+
+void ScColorScaleFormat::calcMinMax(double& rMin, double& rMax) const
+{
+ rMin = GetMinValue();
+ rMax = GetMaxValue();
+}
+
+const ScRangeList& ScColorFormat::GetRange() const
+{
+ return mpParent->GetRange();
+}
+
+std::vector<double>& ScColorFormat::getValues() const
+{
+ if(!mpCache)
+ {
+ mpCache.reset(new ScColorFormatCache);
+ std::vector<double>& rValues = mpCache->maValues;
+
+ size_t n = GetRange().size();
+ const ScRangeList& aRanges = GetRange();
+ for(size_t i = 0; i < n; ++i)
+ {
+ const ScRange & rRange = aRanges[i];
+ SCTAB nTab = rRange.aStart.Tab();
+
+ SCCOL nColStart = rRange.aStart.Col();
+ SCROW nRowStart = rRange.aStart.Row();
+ SCCOL nColEnd = rRange.aEnd.Col();
+ SCROW nRowEnd = rRange.aEnd.Row();
+
+ if(nRowEnd == mpDoc->MaxRow())
+ {
+ bool bShrunk = false;
+ mpDoc->ShrinkToUsedDataArea(bShrunk, nTab, nColStart, nRowStart,
+ nColEnd, nRowEnd, false);
+ }
+ for(SCCOL nCol = nColStart; nCol <= nColEnd; ++nCol)
+ {
+ for(SCROW nRow = nRowStart; nRow <= nRowEnd; ++nRow)
+ {
+ ScAddress aAddr(nCol, nRow, nTab);
+ ScRefCellValue rCell(*mpDoc, aAddr);
+ if(rCell.hasNumeric())
+ {
+ double aVal = rCell.getValue();
+ rValues.push_back(aVal);
+ }
+ }
+ }
+ }
+
+ std::sort(rValues.begin(), rValues.end());
+ }
+
+ return mpCache->maValues;
+}
+
+double ScColorFormat::getMinValue() const
+{
+ std::vector<double>& rValues = getValues();
+ if(rValues.empty())
+ return 0;
+ return rValues[0];
+}
+
+double ScColorFormat::getMaxValue() const
+{
+ std::vector<double>& rValues = getValues();
+ if(rValues.empty())
+ return 0;
+ return rValues[rValues.size()-1];
+}
+
+void ScColorFormat::startRendering()
+{
+ mpCache.reset();
+}
+
+void ScColorFormat::endRendering()
+{
+ mpCache.reset();
+}
+
+namespace {
+
+sal_uInt8 GetColorValue( double nVal, double nVal1, sal_uInt8 nColVal1, double nVal2, sal_uInt8 nColVal2 )
+{
+ if (nVal <= nVal1)
+ return nColVal1;
+
+ if (nVal >= nVal2)
+ return nColVal2;
+
+ sal_uInt8 nColVal = static_cast<int>((nVal - nVal1)/(nVal2-nVal1)*(nColVal2-nColVal1))+nColVal1;
+ return nColVal;
+}
+
+Color CalcColor( double nVal, double nVal1, const Color& rCol1, double nVal2, const Color& rCol2)
+{
+ sal_uInt8 nColRed = GetColorValue(nVal, nVal1, rCol1.GetRed(), nVal2, rCol2.GetRed());
+ sal_uInt8 nColBlue = GetColorValue(nVal, nVal1, rCol1.GetBlue(), nVal2, rCol2.GetBlue());
+ sal_uInt8 nColGreen = GetColorValue(nVal, nVal1, rCol1.GetGreen(), nVal2, rCol2.GetGreen());
+
+ return Color(nColRed, nColGreen, nColBlue);
+}
+
+/**
+ * @param rVector sorted vector of the array
+ * @param fPercentile percentile
+ */
+double GetPercentile( const std::vector<double>& rArray, double fPercentile )
+{
+ assert(!rArray.empty());
+ SAL_WARN_IF(fPercentile < 0, "sc", "negative percentile");
+ if (fPercentile < 0)
+ return rArray.front();
+ assert(fPercentile <= 1);
+ size_t nSize = rArray.size();
+ double fFloor = ::rtl::math::approxFloor(fPercentile * (nSize-1));
+ size_t nIndex = static_cast<size_t>(fFloor);
+ double fDiff = fPercentile * (nSize-1) - fFloor;
+ std::vector<double>::const_iterator iter = rArray.begin() + nIndex;
+ if (fDiff == 0.0)
+ return *iter;
+ else
+ {
+ double fVal = *iter;
+ iter = rArray.begin() + nIndex+1;
+ return fVal + fDiff * (*iter - fVal);
+ }
+}
+
+}
+
+double ScColorScaleFormat::CalcValue(double nMin, double nMax, const ScColorScaleEntries::const_iterator& itr) const
+{
+ switch((*itr)->GetType())
+ {
+ case COLORSCALE_PERCENT:
+ return nMin + (nMax-nMin)*((*itr)->GetValue()/100);
+ case COLORSCALE_MIN:
+ return nMin;
+ case COLORSCALE_MAX:
+ return nMax;
+ case COLORSCALE_PERCENTILE:
+ {
+ std::vector<double>& rValues = getValues();
+ if(rValues.size() == 1)
+ return rValues[0];
+ else
+ {
+ double fPercentile = (*itr)->GetValue()/100.0;
+ return GetPercentile(rValues, fPercentile);
+ }
+ }
+
+ default:
+ break;
+ }
+
+ return (*itr)->GetValue();
+}
+
+std::optional<Color> ScColorScaleFormat::GetColor( const ScAddress& rAddr ) const
+{
+ ScRefCellValue rCell(*mpDoc, rAddr);
+ if(!rCell.hasNumeric())
+ return std::optional<Color>();
+
+ // now we have for sure a value
+ double nVal = rCell.getValue();
+
+ if (maColorScales.size() < 2)
+ return std::optional<Color>();
+
+ double nMin = std::numeric_limits<double>::max();
+ double nMax = std::numeric_limits<double>::min();
+ calcMinMax(nMin, nMax);
+
+ // this check is for safety
+ if(nMin >= nMax)
+ return std::optional<Color>();
+
+ ScColorScaleEntries::const_iterator itr = begin();
+ double nValMin = CalcValue(nMin, nMax, itr);
+ Color rColMin = (*itr)->GetColor();
+ ++itr;
+ double nValMax = CalcValue(nMin, nMax, itr);
+ Color rColMax = (*itr)->GetColor();
+
+ ++itr;
+ while(itr != end() && nVal > nValMax)
+ {
+ rColMin = rColMax;
+ nValMin = nValMax;
+ rColMax = (*itr)->GetColor();
+ nValMax = CalcValue(nMin, nMax, itr);
+ ++itr;
+ }
+
+ Color aColor = CalcColor(nVal, nValMin, rColMin, nValMax, rColMax);
+
+ return aColor;
+}
+
+void ScColorScaleFormat::UpdateReference( sc::RefUpdateContext& rCxt )
+{
+ for(ScColorScaleEntries::iterator itr = begin(); itr != end(); ++itr)
+ (*itr)->UpdateReference(rCxt);
+}
+
+void ScColorScaleFormat::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt )
+{
+ for (ScColorScaleEntries::iterator it = begin(); it != end(); ++it)
+ (*it)->UpdateInsertTab(rCxt);
+}
+
+void ScColorScaleFormat::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt )
+{
+ for (ScColorScaleEntries::iterator it = begin(); it != end(); ++it)
+ (*it)->UpdateDeleteTab(rCxt);
+}
+
+void ScColorScaleFormat::UpdateMoveTab( sc::RefUpdateMoveTabContext& rCxt )
+{
+ for (ScColorScaleEntries::iterator it = begin(); it != end(); ++it)
+ (*it)->UpdateMoveTab(rCxt);
+}
+
+ScFormatEntry::Type ScColorScaleFormat::GetType() const
+{
+ return Type::Colorscale;
+}
+
+ScColorScaleEntries::iterator ScColorScaleFormat::begin()
+{
+ return maColorScales.begin();
+}
+
+ScColorScaleEntries::const_iterator ScColorScaleFormat::begin() const
+{
+ return maColorScales.begin();
+}
+
+ScColorScaleEntries::iterator ScColorScaleFormat::end()
+{
+ return maColorScales.end();
+}
+
+ScColorScaleEntries::const_iterator ScColorScaleFormat::end() const
+{
+ return maColorScales.end();
+}
+
+ScColorScaleEntry* ScColorScaleFormat::GetEntry(size_t nPos)
+{
+ if (maColorScales.size() <= nPos)
+ return nullptr;
+
+ return maColorScales[nPos].get();
+}
+
+const ScColorScaleEntry* ScColorScaleFormat::GetEntry(size_t nPos) const
+{
+ if (maColorScales.size() <= nPos)
+ return nullptr;
+
+ return maColorScales[nPos].get();
+}
+
+size_t ScColorScaleFormat::size() const
+{
+ return maColorScales.size();
+}
+
+void ScColorScaleFormat::EnsureSize()
+{
+ if (maColorScales.size() < 2)
+ {
+ // TODO: create 2 valid entries
+ }
+}
+
+ScDataBarFormat::ScDataBarFormat(ScDocument* pDoc):
+ ScColorFormat(pDoc),
+ mpFormatData(new ScDataBarFormatData())
+{
+}
+
+ScDataBarFormat::ScDataBarFormat(ScDocument* pDoc, const ScDataBarFormat& rFormat):
+ ScColorFormat(pDoc),
+ mpFormatData(new ScDataBarFormatData(*rFormat.mpFormatData))
+{
+}
+
+void ScDataBarFormat::SetDataBarData( ScDataBarFormatData* pData )
+{
+ mpFormatData.reset(pData);
+ if (mpParent)
+ {
+ mpFormatData->mpUpperLimit->SetRepaintCallback(mpParent);
+ mpFormatData->mpLowerLimit->SetRepaintCallback(mpParent);
+ }
+}
+
+ScDataBarFormatData* ScDataBarFormat::GetDataBarData()
+{
+ return mpFormatData.get();
+}
+
+const ScDataBarFormatData* ScDataBarFormat::GetDataBarData() const
+{
+ return mpFormatData.get();
+}
+
+ScColorFormat* ScDataBarFormat::Clone(ScDocument* pDoc) const
+{
+ return new ScDataBarFormat(pDoc, *this);
+}
+
+void ScDataBarFormat::SetParent(ScConditionalFormat* pFormat)
+{
+ if (mpFormatData)
+ {
+ mpFormatData->mpUpperLimit->SetRepaintCallback(pFormat);
+ mpFormatData->mpLowerLimit->SetRepaintCallback(pFormat);
+ }
+ ScColorFormat::SetParent(pFormat);
+}
+
+ScFormatEntry::Type ScDataBarFormat::GetType() const
+{
+ return Type::Databar;
+}
+
+void ScDataBarFormat::UpdateReference( sc::RefUpdateContext& rCxt )
+{
+ mpFormatData->mpUpperLimit->UpdateReference(rCxt);
+ mpFormatData->mpLowerLimit->UpdateReference(rCxt);
+}
+
+void ScDataBarFormat::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt )
+{
+ mpFormatData->mpUpperLimit->UpdateInsertTab(rCxt);
+ mpFormatData->mpLowerLimit->UpdateInsertTab(rCxt);
+}
+
+void ScDataBarFormat::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt )
+{
+ mpFormatData->mpUpperLimit->UpdateDeleteTab(rCxt);
+ mpFormatData->mpLowerLimit->UpdateDeleteTab(rCxt);
+}
+
+void ScDataBarFormat::UpdateMoveTab( sc::RefUpdateMoveTabContext& rCxt )
+{
+ mpFormatData->mpUpperLimit->UpdateMoveTab(rCxt);
+ mpFormatData->mpLowerLimit->UpdateMoveTab(rCxt);
+}
+
+double ScDataBarFormat::getMin(double nMin, double nMax) const
+{
+ switch(mpFormatData->mpLowerLimit->GetType())
+ {
+ case COLORSCALE_MIN:
+ return nMin;
+
+ case COLORSCALE_AUTO:
+ return std::min<double>(0, nMin);
+
+ case COLORSCALE_PERCENT:
+ return nMin + (nMax-nMin)/100*mpFormatData->mpLowerLimit->GetValue();
+
+ case COLORSCALE_PERCENTILE:
+ {
+ double fPercentile = mpFormatData->mpLowerLimit->GetValue()/100.0;
+ std::vector<double>& rValues = getValues();
+ return GetPercentile(rValues, fPercentile);
+ }
+
+ default:
+ break;
+ }
+
+ return mpFormatData->mpLowerLimit->GetValue();
+}
+
+double ScDataBarFormat::getMax(double nMin, double nMax) const
+{
+ switch(mpFormatData->mpUpperLimit->GetType())
+ {
+ case COLORSCALE_MAX:
+ return nMax;
+ case COLORSCALE_AUTO:
+ return std::max<double>(0, nMax);
+ case COLORSCALE_PERCENT:
+ return nMin + (nMax-nMin)/100*mpFormatData->mpUpperLimit->GetValue();
+ case COLORSCALE_PERCENTILE:
+ {
+ double fPercentile = mpFormatData->mpUpperLimit->GetValue()/100.0;
+ std::vector<double>& rValues = getValues();
+ return GetPercentile(rValues, fPercentile);
+ }
+
+ default:
+ break;
+ }
+
+ return mpFormatData->mpUpperLimit->GetValue();
+}
+
+std::unique_ptr<ScDataBarInfo> ScDataBarFormat::GetDataBarInfo(const ScAddress& rAddr) const
+{
+ ScRefCellValue rCell(*mpDoc, rAddr);
+ if(!rCell.hasNumeric())
+ return nullptr;
+
+ // now we have for sure a value
+
+ double nValMin = getMinValue();
+ double nValMax = getMaxValue();
+ double nMin = getMin(nValMin, nValMax);
+ double nMax = getMax(nValMin, nValMax);
+ double nMinLength = mpFormatData->mnMinLength;
+ double nMaxLength = mpFormatData->mnMaxLength;
+
+ double nValue = rCell.getValue();
+
+ std::unique_ptr<ScDataBarInfo> pInfo(new ScDataBarInfo);
+ if(mpFormatData->meAxisPosition == databar::NONE)
+ {
+ if(nValue <= nMin)
+ {
+ pInfo->mnLength = nMinLength;
+ }
+ else if(nValue >= nMax)
+ {
+ pInfo->mnLength = nMaxLength;
+ }
+ else
+ {
+ double nDiff = nMax - nMin;
+ pInfo->mnLength = nMinLength + (nValue - nMin)/nDiff * (nMaxLength-nMinLength);
+ }
+ pInfo->mnZero = 0;
+ }
+ else if (mpFormatData->meAxisPosition == databar::AUTOMATIC)
+ {
+ // if auto is used we may need to adjust it
+ // for the length calculation
+ if (mpFormatData->mpLowerLimit->GetType() == COLORSCALE_AUTO && nMin > 0)
+ nMin = 0;
+ if (mpFormatData->mpUpperLimit->GetType() == COLORSCALE_MAX && nMax < 0)
+ nMax = 0;
+
+ //calculate the zero position first
+ if(nMin < 0)
+ {
+ if(nMax < 0)
+ pInfo->mnZero = 100;
+ else
+ {
+ pInfo->mnZero = -100*nMin/(nMax-nMin);
+ }
+ }
+ else
+ pInfo->mnZero = 0;
+
+ double nMinNonNegative = std::max(0.0, nMin);
+ double nMaxNonPositive = std::min(0.0, nMax);
+ //calculate the length
+ if(nValue < 0 && nMin < 0)
+ {
+ if (nValue < nMin)
+ pInfo->mnLength = -100;
+ else
+ pInfo->mnLength = -100 * (nValue-nMaxNonPositive)/(nMin-nMaxNonPositive);
+ }
+ else
+ {
+ if ( nValue > nMax )
+ pInfo->mnLength = 100;
+ else if (nValue <= nMin)
+ pInfo->mnLength = 0;
+ else
+ pInfo->mnLength = 100 * (nValue-nMinNonNegative)/(nMax-nMinNonNegative);
+ }
+ }
+ else if( mpFormatData->meAxisPosition == databar::MIDDLE)
+ {
+ pInfo->mnZero = 50;
+ double nAbsMax = std::max(std::abs(nMin), std::abs(nMax));
+ if (nValue < 0 && nMin < 0)
+ {
+ if (nValue < nMin)
+ pInfo->mnLength = nMaxLength * (nMin/nAbsMax);
+ else
+ pInfo->mnLength = nMaxLength * (nValue/nAbsMax);
+ }
+ else
+ {
+ if (nValue > nMax)
+ pInfo->mnLength = nMaxLength * (nMax/nAbsMax);
+ else
+ pInfo->mnLength = nMaxLength * (std::max(nValue, nMin)/nAbsMax);
+ }
+ }
+ else
+ assert(false);
+
+ // set color
+ if(mpFormatData->mbNeg && nValue < 0)
+ {
+ if(mpFormatData->mxNegativeColor)
+ {
+ pInfo->maColor = *mpFormatData->mxNegativeColor;
+ }
+ else
+ {
+ // default negative color is red
+ pInfo->maColor = COL_LIGHTRED;
+ }
+
+ }
+ else
+ pInfo->maColor = mpFormatData->maPositiveColor;
+
+ pInfo->mbGradient = mpFormatData->mbGradient;
+ pInfo->mbShowValue = !mpFormatData->mbOnlyBar;
+ pInfo->maAxisColor = mpFormatData->maAxisColor;
+
+ return pInfo;
+}
+
+void ScDataBarFormat::EnsureSize()
+{
+ if (!mpFormatData->mpLowerLimit)
+ {
+ // TODO: implement
+ }
+ if (!mpFormatData->mpUpperLimit)
+ {
+ // TODO: implement
+ }
+}
+
+ScIconSetFormatData::ScIconSetFormatData(ScIconSetFormatData const& rOther)
+ : eIconSetType(rOther.eIconSetType)
+ , mbShowValue(rOther.mbShowValue)
+ , mbReverse(rOther.mbReverse)
+ , mbCustom(rOther.mbCustom)
+ , maCustomVector(rOther.maCustomVector)
+{
+ m_Entries.reserve(rOther.m_Entries.size());
+ for (auto const& it : rOther.m_Entries)
+ {
+ m_Entries.emplace_back(new ScColorScaleEntry(*it));
+ }
+}
+
+ScIconSetFormat::ScIconSetFormat(ScDocument* pDoc):
+ ScColorFormat(pDoc),
+ mpFormatData(new ScIconSetFormatData)
+{
+}
+
+ScIconSetFormat::ScIconSetFormat(ScDocument* pDoc, const ScIconSetFormat& rFormat):
+ ScColorFormat(pDoc),
+ mpFormatData(new ScIconSetFormatData(*rFormat.mpFormatData))
+{
+}
+
+ScColorFormat* ScIconSetFormat::Clone( ScDocument* pDoc ) const
+{
+ return new ScIconSetFormat(pDoc, *this);
+}
+
+void ScIconSetFormat::SetParent(ScConditionalFormat* pFormat)
+{
+ for(iterator itr = begin(); itr != end(); ++itr)
+ {
+ (*itr)->SetRepaintCallback(pFormat);
+ }
+ ScColorFormat::SetParent(pFormat);
+}
+
+void ScIconSetFormat::SetIconSetData( ScIconSetFormatData* pFormatData )
+{
+ mpFormatData.reset( pFormatData );
+ SetParent(mpParent);
+}
+
+ScIconSetFormatData* ScIconSetFormat::GetIconSetData()
+{
+ return mpFormatData.get();
+}
+
+const ScIconSetFormatData* ScIconSetFormat::GetIconSetData() const
+{
+ return mpFormatData.get();
+}
+
+std::unique_ptr<ScIconSetInfo> ScIconSetFormat::GetIconSetInfo(const ScAddress& rAddr) const
+{
+ ScRefCellValue rCell(*mpDoc, rAddr);
+ if(!rCell.hasNumeric())
+ return nullptr;
+
+ // now we have for sure a value
+ double nVal = rCell.getValue();
+
+ if (mpFormatData->m_Entries.size() < 2)
+ return nullptr;
+
+ double nMin = GetMinValue();
+ double nMax = GetMaxValue();
+
+ sal_Int32 nIndex = 0;
+ const_iterator itr = begin();
+ ++itr;
+ double nValMax = CalcValue(nMin, nMax, itr);
+
+ ++itr;
+ while(itr != end() && nVal >= nValMax)
+ {
+ ++nIndex;
+ nValMax = CalcValue(nMin, nMax, itr);
+ ++itr;
+ }
+
+ if(nVal >= nValMax)
+ ++nIndex;
+
+ std::unique_ptr<ScIconSetInfo> pInfo(new ScIconSetInfo);
+
+ const SfxPoolItem& rPoolItem = mpDoc->GetPattern(rAddr)->GetItem(ATTR_FONT_HEIGHT);
+ tools::Long aFontHeight = static_cast<const SvxFontHeightItem&>(rPoolItem).GetHeight();
+ pInfo->mnHeight = aFontHeight;
+
+ if(mpFormatData->mbReverse)
+ {
+ sal_Int32 nMaxIndex = mpFormatData->m_Entries.size() - 1;
+ nIndex = nMaxIndex - nIndex;
+ }
+
+ if (mpFormatData->mbCustom && sal_Int32(mpFormatData->maCustomVector.size()) > nIndex)
+ {
+ ScIconSetType eCustomType = mpFormatData->maCustomVector[nIndex].first;
+ sal_Int32 nCustomIndex = mpFormatData->maCustomVector[nIndex].second;
+ if (nCustomIndex == -1)
+ {
+ return nullptr;
+ }
+
+ pInfo->eIconSetType = eCustomType;
+ pInfo->nIconIndex = nCustomIndex;
+ }
+ else
+ {
+ pInfo->nIconIndex = nIndex;
+ pInfo->eIconSetType = mpFormatData->eIconSetType;
+ }
+
+ pInfo->mbShowValue = mpFormatData->mbShowValue;
+ return pInfo;
+}
+
+ScFormatEntry::Type ScIconSetFormat::GetType() const
+{
+ return Type::Iconset;
+}
+
+void ScIconSetFormat::UpdateReference( sc::RefUpdateContext& rCxt )
+{
+ for(iterator itr = begin(); itr != end(); ++itr)
+ {
+ (*itr)->UpdateReference(rCxt);
+ }
+}
+
+void ScIconSetFormat::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt )
+{
+ for(iterator itr = begin(); itr != end(); ++itr)
+ {
+ (*itr)->UpdateInsertTab(rCxt);
+ }
+}
+
+void ScIconSetFormat::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt )
+{
+ for(iterator itr = begin(); itr != end(); ++itr)
+ {
+ (*itr)->UpdateDeleteTab(rCxt);
+ }
+}
+
+void ScIconSetFormat::UpdateMoveTab( sc::RefUpdateMoveTabContext& rCxt )
+{
+ for(iterator itr = begin(); itr != end(); ++itr)
+ {
+ (*itr)->UpdateMoveTab(rCxt);
+ }
+}
+
+ScIconSetFormat::iterator ScIconSetFormat::begin()
+{
+ return mpFormatData->m_Entries.begin();
+}
+
+ScIconSetFormat::const_iterator ScIconSetFormat::begin() const
+{
+ return mpFormatData->m_Entries.begin();
+}
+
+ScIconSetFormat::iterator ScIconSetFormat::end()
+{
+ return mpFormatData->m_Entries.end();
+}
+
+ScIconSetFormat::const_iterator ScIconSetFormat::end() const
+{
+ return mpFormatData->m_Entries.end();
+}
+
+double ScIconSetFormat::GetMinValue() const
+{
+ const_iterator itr = begin();
+
+ if ((*itr)->GetType() == COLORSCALE_VALUE || (*itr)->GetType() == COLORSCALE_FORMULA)
+ return (*itr)->GetValue();
+ else
+ {
+ return getMinValue();
+ }
+}
+
+double ScIconSetFormat::GetMaxValue() const
+{
+ auto const itr = mpFormatData->m_Entries.rbegin();
+
+ if ((*itr)->GetType() == COLORSCALE_VALUE || (*itr)->GetType() == COLORSCALE_FORMULA)
+ return (*itr)->GetValue();
+ else
+ {
+ return getMaxValue();
+ }
+}
+
+double ScIconSetFormat::CalcValue(double nMin, double nMax, const ScIconSetFormat::const_iterator& itr) const
+{
+ switch ((*itr)->GetType())
+ {
+ case COLORSCALE_PERCENT:
+ return nMin + (nMax-nMin)*((*itr)->GetValue()/100);
+ case COLORSCALE_MIN:
+ return nMin;
+ case COLORSCALE_MAX:
+ return nMax;
+ case COLORSCALE_PERCENTILE:
+ {
+ std::vector<double>& rValues = getValues();
+ if(rValues.size() == 1)
+ return rValues[0];
+ else
+ {
+ double fPercentile = (*itr)->GetValue()/100.0;
+ return GetPercentile(rValues, fPercentile);
+ }
+ }
+
+ default:
+ break;
+ }
+
+ return (*itr)->GetValue();
+}
+
+const ScIconSetMap ScIconSetFormat::g_IconSetMap[] = {
+ { "3Arrows", IconSet_3Arrows, 3 },
+ { "3ArrowsGray", IconSet_3ArrowsGray, 3 },
+ { "3Flags", IconSet_3Flags, 3 },
+ { "3TrafficLights1", IconSet_3TrafficLights1, 3 },
+ { "3TrafficLights2", IconSet_3TrafficLights2, 3 },
+ { "3Signs", IconSet_3Signs, 3 },
+ { "3Symbols", IconSet_3Symbols, 3 },
+ { "3Symbols2", IconSet_3Symbols2, 3 },
+ { "3Smilies", IconSet_3Smilies, 3 },
+ { "3ColorSmilies", IconSet_3ColorSmilies, 3 },
+ { "3Stars", IconSet_3Stars, 3 },
+ { "3Triangles", IconSet_3Triangles, 3 },
+ { "4Arrows", IconSet_4Arrows, 4 },
+ { "4ArrowsGray", IconSet_4ArrowsGray, 4 },
+ { "4RedToBlack", IconSet_4RedToBlack, 4 },
+ { "4Rating", IconSet_4Rating, 4 },
+ { "4TrafficLights", IconSet_4TrafficLights, 4 },
+ { "5Arrows", IconSet_5Arrows, 5 },
+ { "5ArrowsGray", IconSet_5ArrowsGray, 5 },
+ { "5Rating", IconSet_5Ratings, 5 },
+ { "5Quarters", IconSet_5Quarters, 5 },
+ { "5Boxes", IconSet_5Boxes, 5 },
+ { nullptr, IconSet_3Arrows, 0 }
+};
+
+size_t ScIconSetFormat::size() const
+{
+ return mpFormatData->m_Entries.size();
+}
+
+
+namespace {
+
+constexpr rtl::OUStringConstExpr a3TrafficLights1[] = {
+ BMP_ICON_SET_CIRCLES1_RED, BMP_ICON_SET_CIRCLES1_YELLOW, BMP_ICON_SET_CIRCLES1_GREEN
+};
+
+constexpr rtl::OUStringConstExpr a3TrafficLights2[] = {
+ BMP_ICON_SET_TRAFFICLIGHTS_RED, BMP_ICON_SET_TRAFFICLIGHTS_YELLOW, BMP_ICON_SET_TRAFFICLIGHTS_GREEN
+};
+
+constexpr rtl::OUStringConstExpr a3Arrows[] = {
+ BMP_ICON_SET_COLORARROWS_DOWN, BMP_ICON_SET_COLORARROWS_SAME, BMP_ICON_SET_COLORARROWS_UP
+};
+
+constexpr rtl::OUStringConstExpr a3ArrowsGray[] = {
+ BMP_ICON_SET_GRAYARROWS_DOWN, BMP_ICON_SET_GRAYARROWS_SAME, BMP_ICON_SET_GRAYARROWS_UP
+};
+
+constexpr rtl::OUStringConstExpr a3Flags[] = {
+ BMP_ICON_SET_FLAGS_RED, BMP_ICON_SET_FLAGS_YELLOW, BMP_ICON_SET_FLAGS_GREEN
+};
+
+constexpr rtl::OUStringConstExpr a3Smilies[] = {
+ BMP_ICON_SET_POSITIVE_YELLOW_SMILIE, BMP_ICON_SET_NEUTRAL_YELLOW_SMILIE, BMP_ICON_SET_NEGATIVE_YELLOW_SMILIE
+};
+
+constexpr rtl::OUStringConstExpr a3ColorSmilies[] = {
+ BMP_ICON_SET_POSITIVE_GREEN_SMILIE, BMP_ICON_SET_NEUTRAL_YELLOW_SMILIE, BMP_ICON_SET_NEGATIVE_RED_SMILIE
+};
+
+constexpr rtl::OUStringConstExpr a3Stars[] = {
+ BMP_ICON_SET_STARS_EMPTY, BMP_ICON_SET_STARS_HALF, BMP_ICON_SET_STARS_FULL
+};
+
+constexpr rtl::OUStringConstExpr a3Triangles[] = {
+ BMP_ICON_SET_TRIANGLES_DOWN, BMP_ICON_SET_TRIANGLES_SAME, BMP_ICON_SET_TRIANGLES_UP
+};
+
+constexpr rtl::OUStringConstExpr a4Arrows[] = {
+ BMP_ICON_SET_COLORARROWS_DOWN, BMP_ICON_SET_COLORARROWS_SLIGHTLY_DOWN, BMP_ICON_SET_COLORARROWS_SLIGHTLY_UP, BMP_ICON_SET_COLORARROWS_UP
+};
+
+constexpr rtl::OUStringConstExpr a4ArrowsGray[] = {
+ BMP_ICON_SET_GRAYARROWS_DOWN, BMP_ICON_SET_GRAYARROWS_SLIGHTLY_DOWN, BMP_ICON_SET_GRAYARROWS_SLIGHTLY_UP, BMP_ICON_SET_GRAYARROWS_UP
+};
+
+constexpr rtl::OUStringConstExpr a5Arrows[] = {
+ BMP_ICON_SET_COLORARROWS_DOWN, BMP_ICON_SET_COLORARROWS_SLIGHTLY_DOWN,
+ BMP_ICON_SET_COLORARROWS_SAME, BMP_ICON_SET_COLORARROWS_SLIGHTLY_UP, BMP_ICON_SET_COLORARROWS_UP
+};
+
+constexpr rtl::OUStringConstExpr a5ArrowsGray[] = {
+ BMP_ICON_SET_GRAYARROWS_DOWN, BMP_ICON_SET_GRAYARROWS_SLIGHTLY_DOWN,
+ BMP_ICON_SET_GRAYARROWS_SAME, BMP_ICON_SET_GRAYARROWS_SLIGHTLY_UP, BMP_ICON_SET_GRAYARROWS_UP
+};
+
+constexpr rtl::OUStringConstExpr a4TrafficLights[] = {
+ BMP_ICON_SET_CIRCLES1_GRAY, BMP_ICON_SET_CIRCLES1_RED,
+ BMP_ICON_SET_CIRCLES1_YELLOW, BMP_ICON_SET_CIRCLES1_GREEN
+};
+
+constexpr rtl::OUStringConstExpr a5Quarters[] = {
+ BMP_ICON_SET_PIES_EMPTY, BMP_ICON_SET_PIES_ONE_QUARTER, BMP_ICON_SET_PIES_HALF,
+ BMP_ICON_SET_PIES_THREE_QUARTER, BMP_ICON_SET_PIES_FULL,
+};
+
+constexpr rtl::OUStringConstExpr a5Boxes[] = {
+ BMP_ICON_SET_SQUARES_EMPTY, BMP_ICON_SET_SQUARES_ONE_QUARTER,
+ BMP_ICON_SET_SQUARES_HALF, BMP_ICON_SET_SQUARES_THREE_QUARTER,
+ BMP_ICON_SET_SQUARES_FULL
+};
+
+constexpr rtl::OUStringConstExpr a3Symbols1[] = {
+ BMP_ICON_SET_SYMBOLS1_CROSS, BMP_ICON_SET_SYMBOLS1_EXCLAMATION_MARK, BMP_ICON_SET_SYMBOLS1_CHECK
+};
+
+constexpr rtl::OUStringConstExpr a3Signs[] = {
+ BMP_ICON_SET_SHAPES_DIAMOND, BMP_ICON_SET_SHAPES_TRIANGLE, BMP_ICON_SET_SHAPES_CIRCLE
+};
+
+constexpr rtl::OUStringConstExpr a4RedToBlack[] = {
+ BMP_ICON_SET_CIRCLES2_DARK_GRAY, BMP_ICON_SET_CIRCLES2_LIGHT_GRAY,
+ BMP_ICON_SET_CIRCLES2_LIGHT_RED, BMP_ICON_SET_CIRCLES2_DARK_RED
+};
+
+constexpr rtl::OUStringConstExpr a4Ratings[] = {
+ BMP_ICON_SET_BARS_ONE_QUARTER, BMP_ICON_SET_BARS_HALF,
+ BMP_ICON_SET_BARS_THREE_QUARTER, BMP_ICON_SET_BARS_FULL
+};
+
+constexpr rtl::OUStringConstExpr a5Ratings[] = {
+ BMP_ICON_SET_BARS_EMPTY, BMP_ICON_SET_BARS_ONE_QUARTER, BMP_ICON_SET_BARS_HALF,
+ BMP_ICON_SET_BARS_THREE_QUARTER, BMP_ICON_SET_BARS_FULL
+};
+
+struct ScIconSetBitmapMap {
+ ScIconSetType eType;
+ const rtl::OUStringConstExpr* pBitmaps;
+};
+
+const ScIconSetBitmapMap aBitmapMap[] = {
+ { IconSet_3Arrows, a3Arrows },
+ { IconSet_3ArrowsGray, a3ArrowsGray },
+ { IconSet_3Flags, a3Flags },
+ { IconSet_3Signs, a3Signs },
+ { IconSet_3Symbols, a3Symbols1 },
+ { IconSet_3Symbols2, a3Symbols1 },
+ { IconSet_3TrafficLights1, a3TrafficLights1 },
+ { IconSet_3TrafficLights2, a3TrafficLights2 },
+ { IconSet_3Smilies, a3Smilies },
+ { IconSet_3ColorSmilies, a3ColorSmilies },
+ { IconSet_3Triangles, a3Triangles },
+ { IconSet_3Stars, a3Stars },
+ { IconSet_4Arrows, a4Arrows },
+ { IconSet_4ArrowsGray, a4ArrowsGray },
+ { IconSet_4Rating, a4Ratings },
+ { IconSet_4RedToBlack, a4RedToBlack },
+ { IconSet_4TrafficLights, a4TrafficLights },
+ { IconSet_5Arrows, a5Arrows },
+ { IconSet_5ArrowsGray, a5ArrowsGray },
+ { IconSet_5Quarters, a5Quarters },
+ { IconSet_5Ratings, a5Ratings },
+ { IconSet_5Boxes, a5Boxes }
+};
+
+const ScIconSetMap* findIconSetType(ScIconSetType eType)
+{
+ const ScIconSetMap* pMap = ScIconSetFormat::g_IconSetMap;
+ for (; pMap->pName; ++pMap)
+ {
+ if (pMap->eType == eType)
+ return pMap;
+ }
+
+ return nullptr;
+}
+
+}
+
+const char* ScIconSetFormat::getIconSetName( ScIconSetType eType )
+{
+ const ScIconSetMap* pMap = findIconSetType(eType);
+ if (pMap)
+ return pMap->pName;
+
+ return "";
+}
+
+sal_Int32 ScIconSetFormat::getIconSetElements( ScIconSetType eType )
+{
+ const ScIconSetMap* pMap = findIconSetType(eType);
+ if (pMap)
+ return pMap->nElements;
+
+ return 0;
+}
+
+OUString ScIconSetFormat::getIconName(ScIconSetType const eType, sal_Int32 const nIndex)
+{
+ OUString sBitmap;
+
+ for(const ScIconSetBitmapMap & i : aBitmapMap)
+ {
+ if(i.eType == eType)
+ {
+ sBitmap = *(i.pBitmaps + nIndex);
+ break;
+ }
+ }
+
+ assert(!sBitmap.isEmpty());
+
+ return sBitmap;
+}
+
+BitmapEx& ScIconSetFormat::getBitmap(sc::IconSetBitmapMap & rIconSetBitmapMap,
+ ScIconSetType const eType, sal_Int32 const nIndex)
+{
+ OUString sBitmap(ScIconSetFormat::getIconName(eType, nIndex));
+
+ std::map<OUString, BitmapEx>::iterator itr = rIconSetBitmapMap.find(sBitmap);
+ if (itr != rIconSetBitmapMap.end())
+ return itr->second;
+
+ BitmapEx aBitmap(sBitmap);
+ std::pair<OUString, BitmapEx> aPair(sBitmap, aBitmap);
+ std::pair<std::map<OUString, BitmapEx>::iterator, bool> itrNew = rIconSetBitmapMap.insert(aPair);
+ assert(itrNew.second);
+
+ return itrNew.first->second;
+}
+
+void ScIconSetFormat::EnsureSize()
+{
+ ScIconSetType eType = mpFormatData->eIconSetType;
+ for (const ScIconSetMap & i : g_IconSetMap)
+ {
+ if (i.eType == eType)
+ {
+ // size_t nElements = aIconSetMap[i].nElements;
+ // TODO: implement
+ break;
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/column.cxx b/sc/source/core/data/column.cxx
new file mode 100644
index 000000000..06bbe0cf3
--- /dev/null
+++ b/sc/source/core/data/column.cxx
@@ -0,0 +1,3391 @@
+/* -*- 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 <column.hxx>
+#include <scitems.hxx>
+#include <formulacell.hxx>
+#include <document.hxx>
+#include <table.hxx>
+#include <docpool.hxx>
+#include <attarray.hxx>
+#include <patattr.hxx>
+#include <compiler.hxx>
+#include <brdcst.hxx>
+#include <markdata.hxx>
+#include <postit.hxx>
+#include <cellvalue.hxx>
+#include <tokenarray.hxx>
+#include <clipcontext.hxx>
+#include <types.hxx>
+#include <editutil.hxx>
+#include <mtvcellfunc.hxx>
+#include <columnspanset.hxx>
+#include <scopetools.hxx>
+#include <sharedformula.hxx>
+#include <refupdatecontext.hxx>
+#include <listenercontext.hxx>
+#include <formulagroup.hxx>
+#include <drwlayer.hxx>
+#include <mtvelements.hxx>
+#include <bcaslot.hxx>
+
+#include <svl/numformat.hxx>
+#include <svl/poolcach.hxx>
+#include <svl/zforlist.hxx>
+#include <svl/sharedstringpool.hxx>
+#include <editeng/fieldupdater.hxx>
+#include <formula/errorcodes.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+
+#include <map>
+#include <cstdio>
+#include <memory>
+
+using ::editeng::SvxBorderLine;
+using namespace formula;
+
+namespace {
+
+bool IsAmbiguousScriptNonZero( SvtScriptType nScript )
+{
+ //TODO: move to a header file
+ return ( nScript != SvtScriptType::LATIN &&
+ nScript != SvtScriptType::ASIAN &&
+ nScript != SvtScriptType::COMPLEX &&
+ nScript != SvtScriptType::NONE );
+}
+
+}
+
+ScNeededSizeOptions::ScNeededSizeOptions() :
+ pPattern(nullptr), bFormula(false), bSkipMerged(true), bGetFont(true), bTotalSize(false)
+{
+}
+
+ScColumn::ScColumn(ScSheetLimits const & rSheetLimits) :
+ maCellTextAttrs(rSheetLimits.GetMaxRowCount()),
+ maCellNotes(rSheetLimits.GetMaxRowCount()),
+ maBroadcasters(rSheetLimits.GetMaxRowCount()),
+ maCells(sc::CellStoreEvent(this)),
+ maSparklines(rSheetLimits.GetMaxRowCount()),
+ mnBlkCountFormula(0),
+ nCol( 0 ),
+ nTab( 0 ),
+ mbFiltering( false ),
+ mbEmptyBroadcastersPending( false )
+{
+ maCells.resize(rSheetLimits.GetMaxRowCount());
+}
+
+ScColumn::~ScColumn() COVERITY_NOEXCEPT_FALSE
+{
+ FreeAll();
+}
+
+void ScColumn::Init(SCCOL nNewCol, SCTAB nNewTab, ScDocument& rDoc, bool bEmptyAttrArray)
+{
+ nCol = nNewCol;
+ nTab = nNewTab;
+ if ( bEmptyAttrArray )
+ InitAttrArray(new ScAttrArray( nCol, nTab, rDoc, nullptr ));
+ else
+ InitAttrArray(new ScAttrArray( nCol, nTab, rDoc, &rDoc.maTabs[nTab]->aDefaultColData.AttrArray()));
+}
+
+sc::MatrixEdge ScColumn::GetBlockMatrixEdges( SCROW nRow1, SCROW nRow2, sc::MatrixEdge nMask,
+ bool bNoMatrixAtAll ) const
+{
+ using namespace sc;
+
+ if (!GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2) || nRow1 > nRow2)
+ return MatrixEdge::Nothing;
+
+ ScAddress aOrigin(ScAddress::INITIALIZE_INVALID);
+
+ if (nRow1 == nRow2)
+ {
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow1);
+ if (aPos.first->type != sc::element_type_formula)
+ return MatrixEdge::Nothing;
+
+ const ScFormulaCell* pCell = sc::formula_block::at(*aPos.first->data, aPos.second);
+ if (pCell->GetMatrixFlag() == ScMatrixMode::NONE)
+ return MatrixEdge::Nothing;
+
+ return pCell->GetMatrixEdge(GetDoc(), aOrigin);
+ }
+
+ bool bOpen = false;
+ MatrixEdge nEdges = MatrixEdge::Nothing;
+
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow1);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ size_t nOffset = aPos.second;
+ SCROW nRow = nRow1;
+ for (;it != maCells.end() && nRow <= nRow2; ++it, nOffset = 0)
+ {
+ if (it->type != sc::element_type_formula)
+ {
+ // Skip this block.
+ nRow += it->size - nOffset;
+ continue;
+ }
+
+ size_t nRowsToRead = nRow2 - nRow + 1;
+ size_t nEnd = std::min(it->size, nOffset+nRowsToRead); // last row + 1
+ sc::formula_block::const_iterator itCell = sc::formula_block::begin(*it->data);
+ std::advance(itCell, nOffset);
+ for (size_t i = nOffset; i < nEnd; ++itCell, ++i)
+ {
+ // Loop inside the formula block.
+ const ScFormulaCell* pCell = *itCell;
+ if (pCell->GetMatrixFlag() == ScMatrixMode::NONE)
+ continue;
+
+ nEdges = pCell->GetMatrixEdge(GetDoc(), aOrigin);
+ if (nEdges == MatrixEdge::Nothing)
+ continue;
+
+ // A 1x1 matrix array formula is OK even for no matrix at all.
+ if (bNoMatrixAtAll
+ && (nEdges != (MatrixEdge::Top | MatrixEdge::Left | MatrixEdge::Bottom | MatrixEdge::Right)))
+ return MatrixEdge::Inside; // per convention Inside
+
+ if (nEdges & MatrixEdge::Top)
+ bOpen = true; // top edge opens, keep on looking
+ else if (!bOpen)
+ return nEdges | MatrixEdge::Open; // there's something that wasn't opened
+ else if (nEdges & MatrixEdge::Inside)
+ return nEdges; // inside
+ if (((nMask & MatrixEdge::Right) && (nEdges & MatrixEdge::Left) && !(nEdges & MatrixEdge::Right)) ||
+ ((nMask & MatrixEdge::Left) && (nEdges & MatrixEdge::Right) && !(nEdges & MatrixEdge::Left)))
+ return nEdges; // only left/right edge
+
+ if (nEdges & MatrixEdge::Bottom)
+ bOpen = false; // bottom edge closes
+ }
+
+ nRow += nEnd - nOffset;
+ }
+ if (bOpen)
+ nEdges |= MatrixEdge::Open; // not closed, matrix continues
+
+ return nEdges;
+}
+
+bool ScColumn::HasSelectionMatrixFragment(const ScMarkData& rMark, const ScRangeList& rRangeList) const
+{
+ using namespace sc;
+
+ if (!rMark.IsMultiMarked())
+ return false;
+
+ ScAddress aOrigin(ScAddress::INITIALIZE_INVALID);
+ ScAddress aCurOrigin = aOrigin;
+
+ bool bOpen = false;
+ ScRangeList aRanges = rRangeList; // cached rMark.GetMarkedRanges(), for performance reasons (tdf#148147)
+ for (size_t i = 0, n = aRanges.size(); i < n; ++i)
+ {
+ const ScRange& r = aRanges[i];
+ if (nTab < r.aStart.Tab() || r.aEnd.Tab() < nTab)
+ continue;
+
+ if (nCol < r.aStart.Col() || r.aEnd.Col() < nCol)
+ continue;
+
+ SCROW nTop = r.aStart.Row(), nBottom = r.aEnd.Row();
+ SCROW nRow = nTop;
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ size_t nOffset = aPos.second;
+
+ for (;it != maCells.end() && nRow <= nBottom; ++it, nOffset = 0)
+ {
+ if (it->type != sc::element_type_formula)
+ {
+ // Skip this block.
+ nRow += it->size - nOffset;
+ continue;
+ }
+
+ // This is a formula cell block.
+ size_t nRowsToRead = nBottom - nRow + 1;
+ size_t nEnd = std::min(it->size, nRowsToRead);
+ sc::formula_block::const_iterator itCell = sc::formula_block::begin(*it->data);
+ std::advance(itCell, nOffset);
+ for (size_t j = nOffset; j < nEnd; ++itCell, ++j)
+ {
+ // Loop inside the formula block.
+ const ScFormulaCell* pCell = *itCell;
+ if (pCell->GetMatrixFlag() == ScMatrixMode::NONE)
+ // cell is not a part of a matrix.
+ continue;
+
+ MatrixEdge nEdges = pCell->GetMatrixEdge(GetDoc(), aOrigin);
+ if (nEdges == MatrixEdge::Nothing)
+ continue;
+
+ bool bFound = false;
+
+ if (nEdges & MatrixEdge::Top)
+ bOpen = true; // top edge opens, keep on looking
+ else if (!bOpen)
+ return true; // there's something that wasn't opened
+ else if (nEdges & MatrixEdge::Inside)
+ bFound = true; // inside, all selected?
+
+ if (((nEdges & MatrixEdge::Left) | MatrixEdge::Right) ^ ((nEdges & MatrixEdge::Right) | MatrixEdge::Left))
+ // either left or right, but not both.
+ bFound = true; // only left/right edge, all selected?
+
+ if (nEdges & MatrixEdge::Bottom)
+ bOpen = false; // bottom edge closes
+
+ if (bFound)
+ {
+ // Check if the matrix is inside the selection in its entirety.
+ //
+ // TODO: It's more efficient to skip the matrix range if
+ // it's within selection, to avoid checking it again and
+ // again.
+
+ if (aCurOrigin != aOrigin)
+ { // new matrix to check?
+ aCurOrigin = aOrigin;
+ const ScFormulaCell* pFCell;
+ if (pCell->GetMatrixFlag() == ScMatrixMode::Reference)
+ pFCell = GetDoc().GetFormulaCell(aOrigin);
+ else
+ pFCell = pCell;
+
+ SCCOL nC;
+ SCROW nR;
+ pFCell->GetMatColsRows(nC, nR);
+ ScRange aRange(aOrigin, ScAddress(aOrigin.Col()+nC-1, aOrigin.Row()+nR-1, aOrigin.Tab()));
+ if (rMark.IsAllMarked(aRange))
+ bFound = false;
+ }
+ else
+ bFound = false; // done already
+ }
+
+ if (bFound)
+ return true;
+ }
+
+ nRow += nEnd;
+ }
+ }
+
+ return bOpen;
+}
+
+bool ScColumn::HasAttribSelection( const ScMarkData& rMark, HasAttrFlags nMask ) const
+{
+ bool bFound = false;
+
+ SCROW nTop;
+ SCROW nBottom;
+
+ if (rMark.IsMultiMarked())
+ {
+ ScMultiSelIter aMultiIter( rMark.GetMultiSelData(), nCol );
+ while (aMultiIter.Next( nTop, nBottom ) && !bFound)
+ {
+ if (pAttrArray->HasAttrib( nTop, nBottom, nMask ))
+ bFound = true;
+ }
+ }
+
+ return bFound;
+}
+
+void ScColumn::MergeSelectionPattern( ScMergePatternState& rState, const ScMarkData& rMark, bool bDeep ) const
+{
+ SCROW nTop;
+ SCROW nBottom;
+
+ if ( rMark.IsMultiMarked() )
+ {
+ const ScMultiSel& rMultiSel = rMark.GetMultiSelData();
+ if ( rMultiSel.HasMarks( nCol ) )
+ {
+ ScMultiSelIter aMultiIter( rMultiSel, nCol );
+ while (aMultiIter.Next( nTop, nBottom ))
+ pAttrArray->MergePatternArea( nTop, nBottom, rState, bDeep );
+ }
+ }
+}
+
+const ScPatternAttr* ScColumnData::GetMostUsedPattern( SCROW nStartRow, SCROW nEndRow ) const
+{
+ ::std::map< const ScPatternAttr*, size_t > aAttrMap;
+ const ScPatternAttr* pMaxPattern = nullptr;
+ size_t nMaxCount = 0;
+
+ ScAttrIterator aAttrIter( pAttrArray.get(), nStartRow, nEndRow, GetDoc().GetDefPattern() );
+ const ScPatternAttr* pPattern;
+ SCROW nAttrRow1 = 0, nAttrRow2 = 0;
+
+ while( (pPattern = aAttrIter.Next( nAttrRow1, nAttrRow2 )) != nullptr )
+ {
+ size_t& rnCount = aAttrMap[ pPattern ];
+ rnCount += (nAttrRow2 - nAttrRow1 + 1);
+ if( rnCount > nMaxCount )
+ {
+ pMaxPattern = pPattern;
+ nMaxCount = rnCount;
+ }
+ }
+
+ return pMaxPattern;
+}
+
+sal_uInt32 ScColumnData::GetNumberFormat( SCROW nStartRow, SCROW nEndRow ) const
+{
+ SCROW nPatStartRow, nPatEndRow;
+ const ScPatternAttr* pPattern = pAttrArray->GetPatternRange(nPatStartRow, nPatEndRow, nStartRow);
+ sal_uInt32 nFormat = pPattern->GetNumberFormat(GetDoc().GetFormatTable());
+ while (nEndRow > nPatEndRow)
+ {
+ nStartRow = nPatEndRow + 1;
+ pPattern = pAttrArray->GetPatternRange(nPatStartRow, nPatEndRow, nStartRow);
+ sal_uInt32 nTmpFormat = pPattern->GetNumberFormat(GetDoc().GetFormatTable());
+ if (nFormat != nTmpFormat)
+ return 0;
+ }
+ return nFormat;
+}
+
+SCROW ScColumn::ApplySelectionCache( SfxItemPoolCache* pCache, const ScMarkData& rMark, ScEditDataArray* pDataArray,
+ bool* const pIsChanged )
+{
+ return ScColumnData::ApplySelectionCache( pCache, rMark, pDataArray, pIsChanged, nCol );
+}
+
+SCROW ScColumnData::ApplySelectionCache( SfxItemPoolCache* pCache, const ScMarkData& rMark, ScEditDataArray* pDataArray,
+ bool* const pIsChanged, SCCOL nCol )
+{
+ SCROW nTop = 0;
+ SCROW nBottom = 0;
+ bool bFound = false;
+
+ if ( rMark.IsMultiMarked() )
+ {
+ ScMultiSelIter aMultiIter( rMark.GetMultiSelData(), nCol );
+ while (aMultiIter.Next( nTop, nBottom ))
+ {
+ pAttrArray->ApplyCacheArea( nTop, nBottom, pCache, pDataArray, pIsChanged );
+ bFound = true;
+ }
+ }
+
+ if (!bFound)
+ return -1;
+ else if (nTop==0 && nBottom==GetDoc().MaxRow())
+ return 0;
+ else
+ return nBottom;
+}
+
+void ScColumnData::ChangeSelectionIndent( bool bIncrement, const ScMarkData& rMark, SCCOL nCol )
+{
+ assert(rMark.IsMultiMarked());
+ if ( pAttrArray && rMark.IsMultiMarked() )
+ {
+ ScMultiSelIter aMultiIter( rMark.GetMultiSelData(), nCol );
+ SCROW nTop;
+ SCROW nBottom;
+ while (aMultiIter.Next( nTop, nBottom ))
+ pAttrArray->ChangeIndent(nTop, nBottom, bIncrement);
+ }
+}
+
+void ScColumn::ChangeSelectionIndent( bool bIncrement, const ScMarkData& rMark )
+{
+ return ScColumnData::ChangeSelectionIndent( bIncrement, rMark, nCol );
+}
+
+void ScColumnData::ClearSelectionItems( const sal_uInt16* pWhich,const ScMarkData& rMark, SCCOL nCol )
+{
+ if (!pAttrArray)
+ return;
+
+ if (rMark.IsMultiMarked() )
+ {
+ ScMultiSelIter aMultiIter( rMark.GetMultiSelData(), nCol );
+ SCROW nTop;
+ SCROW nBottom;
+ while (aMultiIter.Next( nTop, nBottom ))
+ pAttrArray->ClearItems(nTop, nBottom, pWhich);
+ }
+ else if (rMark.IsMarked())
+ {
+ const ScRange& aRange = rMark.GetMarkArea();
+ if (aRange.aStart.Col() <= nCol && nCol <= aRange.aEnd.Col())
+ {
+ pAttrArray->ClearItems(aRange.aStart.Row(), aRange.aEnd.Row(), pWhich);
+ }
+ }
+}
+
+void ScColumn::ClearSelectionItems( const sal_uInt16* pWhich,const ScMarkData& rMark )
+{
+ ScColumnData::ClearSelectionItems( pWhich, rMark, nCol );
+}
+
+void ScColumn::DeleteSelection( InsertDeleteFlags nDelFlag, const ScMarkData& rMark, bool bBroadcast )
+{
+ SCROW nTop;
+ SCROW nBottom;
+
+ if ( rMark.IsMultiMarked() )
+ {
+ ScMultiSelIter aMultiIter( rMark.GetMultiSelData(), nCol );
+ while (aMultiIter.Next( nTop, nBottom ))
+ DeleteArea(nTop, nBottom, nDelFlag, bBroadcast);
+ }
+}
+
+void ScColumn::ApplyPattern( SCROW nRow, const ScPatternAttr& rPatAttr )
+{
+ const SfxItemSet* pSet = &rPatAttr.GetItemSet();
+ SfxItemPoolCache aCache( GetDoc().GetPool(), pSet );
+
+ const ScPatternAttr* pPattern = pAttrArray->GetPattern( nRow );
+
+ // true = keep old content
+
+ const ScPatternAttr* pNewPattern = static_cast<const ScPatternAttr*>( &aCache.ApplyTo( *pPattern ) );
+
+ if (pNewPattern != pPattern)
+ pAttrArray->SetPattern( nRow, pNewPattern );
+}
+
+void ScColumnData::ApplyPatternArea( SCROW nStartRow, SCROW nEndRow, const ScPatternAttr& rPatAttr,
+ ScEditDataArray* pDataArray, bool* const pIsChanged )
+{
+ const SfxItemSet* pSet = &rPatAttr.GetItemSet();
+ SfxItemPoolCache aCache( GetDoc().GetPool(), pSet );
+ pAttrArray->ApplyCacheArea( nStartRow, nEndRow, &aCache, pDataArray, pIsChanged );
+}
+
+void ScColumn::ApplyPatternIfNumberformatIncompatible( const ScRange& rRange,
+ const ScPatternAttr& rPattern, SvNumFormatType nNewType )
+{
+ const SfxItemSet* pSet = &rPattern.GetItemSet();
+ SfxItemPoolCache aCache( GetDoc().GetPool(), pSet );
+ SvNumberFormatter* pFormatter = GetDoc().GetFormatTable();
+ SCROW nEndRow = rRange.aEnd.Row();
+ for ( SCROW nRow = rRange.aStart.Row(); nRow <= nEndRow; nRow++ )
+ {
+ SCROW nRow1, nRow2;
+ const ScPatternAttr* pPattern = pAttrArray->GetPatternRange(
+ nRow1, nRow2, nRow );
+ sal_uInt32 nFormat = pPattern->GetNumberFormat( pFormatter );
+ SvNumFormatType nOldType = pFormatter->GetType( nFormat );
+ if ( nOldType == nNewType || SvNumberFormatter::IsCompatible( nOldType, nNewType ) )
+ nRow = nRow2;
+ else
+ {
+ SCROW nNewRow1 = std::max( nRow1, nRow );
+ SCROW nNewRow2 = std::min( nRow2, nEndRow );
+ pAttrArray->ApplyCacheArea( nNewRow1, nNewRow2, &aCache );
+ nRow = nNewRow2;
+ }
+ }
+}
+
+void ScColumn::ApplyStyle( SCROW nRow, const ScStyleSheet* rStyle )
+{
+ const ScPatternAttr* pPattern = pAttrArray->GetPattern(nRow);
+ std::unique_ptr<ScPatternAttr> pNewPattern(new ScPatternAttr(*pPattern));
+ pNewPattern->SetStyleSheet(const_cast<ScStyleSheet*>(rStyle));
+ pAttrArray->SetPattern(nRow, std::move(pNewPattern), true);
+}
+
+void ScColumn::ApplySelectionStyle(const ScStyleSheet& rStyle, const ScMarkData& rMark)
+{
+ SCROW nTop;
+ SCROW nBottom;
+
+ if ( rMark.IsMultiMarked() )
+ {
+ ScMultiSelIter aMultiIter( rMark.GetMultiSelData(), nCol );
+ while (aMultiIter.Next( nTop, nBottom ))
+ pAttrArray->ApplyStyleArea(nTop, nBottom, rStyle);
+ }
+}
+
+void ScColumn::ApplySelectionLineStyle( const ScMarkData& rMark,
+ const SvxBorderLine* pLine, bool bColorOnly )
+{
+ if ( bColorOnly && !pLine )
+ return;
+
+ SCROW nTop;
+ SCROW nBottom;
+
+ if (rMark.IsMultiMarked())
+ {
+ ScMultiSelIter aMultiIter( rMark.GetMultiSelData(), nCol );
+ while (aMultiIter.Next( nTop, nBottom ))
+ pAttrArray->ApplyLineStyleArea(nTop, nBottom, pLine, bColorOnly );
+ }
+}
+
+const ScStyleSheet* ScColumn::GetSelectionStyle( const ScMarkData& rMark, bool& rFound ) const
+{
+ rFound = false;
+ if (!rMark.IsMultiMarked())
+ {
+ OSL_FAIL("No selection in ScColumn::GetSelectionStyle");
+ return nullptr;
+ }
+
+ bool bEqual = true;
+
+ const ScStyleSheet* pStyle = nullptr;
+ const ScStyleSheet* pNewStyle;
+
+ ScDocument& rDocument = GetDoc();
+ ScMultiSelIter aMultiIter( rMark.GetMultiSelData(), nCol );
+ SCROW nTop;
+ SCROW nBottom;
+ while (bEqual && aMultiIter.Next( nTop, nBottom ))
+ {
+ ScAttrIterator aAttrIter( pAttrArray.get(), nTop, nBottom, rDocument.GetDefPattern() );
+ SCROW nRow;
+ SCROW nDummy;
+ while (bEqual)
+ {
+ const ScPatternAttr* pPattern = aAttrIter.Next( nRow, nDummy );
+ if (!pPattern)
+ break;
+ pNewStyle = pPattern->GetStyleSheet();
+ rFound = true;
+ if ( !pNewStyle || ( pStyle && pNewStyle != pStyle ) )
+ bEqual = false; // difference
+ pStyle = pNewStyle;
+ }
+ }
+
+ return bEqual ? pStyle : nullptr;
+}
+
+const ScStyleSheet* ScColumn::GetAreaStyle( bool& rFound, SCROW nRow1, SCROW nRow2 ) const
+{
+ rFound = false;
+
+ bool bEqual = true;
+
+ const ScStyleSheet* pStyle = nullptr;
+ const ScStyleSheet* pNewStyle;
+
+ ScAttrIterator aAttrIter( pAttrArray.get(), nRow1, nRow2, GetDoc().GetDefPattern() );
+ SCROW nRow;
+ SCROW nDummy;
+ while (bEqual)
+ {
+ const ScPatternAttr* pPattern = aAttrIter.Next( nRow, nDummy );
+ if (!pPattern)
+ break;
+ pNewStyle = pPattern->GetStyleSheet();
+ rFound = true;
+ if ( !pNewStyle || ( pStyle && pNewStyle != pStyle ) )
+ bEqual = false; // difference
+ pStyle = pNewStyle;
+ }
+
+ return bEqual ? pStyle : nullptr;
+}
+
+void ScColumn::ApplyAttr( SCROW nRow, const SfxPoolItem& rAttr )
+{
+ // in order to only create a new SetItem, we don't need SfxItemPoolCache.
+ //TODO: Warning: SfxItemPoolCache seems to create too many Refs for the new SetItem ??
+
+ ScDocumentPool* pDocPool = GetDoc().GetPool();
+
+ const ScPatternAttr* pOldPattern = pAttrArray->GetPattern( nRow );
+ ScPatternAttr aTemp(*pOldPattern);
+ aTemp.GetItemSet().Put(rAttr);
+ const ScPatternAttr* pNewPattern = &pDocPool->Put( aTemp );
+
+ if ( pNewPattern != pOldPattern )
+ pAttrArray->SetPattern( nRow, pNewPattern );
+ else
+ pDocPool->Remove( *pNewPattern ); // free up resources
+}
+
+ScRefCellValue ScColumn::GetCellValue( SCROW nRow ) const
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
+ if (aPos.first == maCells.end())
+ return ScRefCellValue();
+
+ return GetCellValue(aPos.first, aPos.second);
+}
+
+ScRefCellValue ScColumn::GetCellValue( sc::ColumnBlockPosition& rBlockPos, SCROW nRow )
+{
+ std::pair<sc::CellStoreType::iterator,size_t> aPos = maCells.position(rBlockPos.miCellPos, nRow);
+ if (aPos.first == maCells.end())
+ return ScRefCellValue();
+
+ rBlockPos.miCellPos = aPos.first; // Store this for next call.
+ return GetCellValue(aPos.first, aPos.second);
+}
+
+ScRefCellValue ScColumn::GetCellValue( sc::ColumnBlockConstPosition& rBlockPos, SCROW nRow ) const
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(rBlockPos.miCellPos, nRow);
+ if (aPos.first == maCells.end())
+ return ScRefCellValue();
+
+ rBlockPos.miCellPos = aPos.first; // Store this for next call.
+ return GetCellValue(aPos.first, aPos.second);
+}
+
+ScRefCellValue ScColumn::GetCellValue( const sc::CellStoreType::const_iterator& itPos, size_t nOffset )
+{
+ ScRefCellValue aVal; // Defaults to empty cell.
+ switch (itPos->type)
+ {
+ case sc::element_type_numeric:
+ // Numeric cell
+ aVal.mfValue = sc::numeric_block::at(*itPos->data, nOffset);
+ aVal.meType = CELLTYPE_VALUE;
+ break;
+ case sc::element_type_string:
+ // String cell
+ aVal.mpString = &sc::string_block::at(*itPos->data, nOffset);
+ aVal.meType = CELLTYPE_STRING;
+ break;
+ case sc::element_type_edittext:
+ // Edit cell
+ aVal.mpEditText = sc::edittext_block::at(*itPos->data, nOffset);
+ aVal.meType = CELLTYPE_EDIT;
+ break;
+ case sc::element_type_formula:
+ // Formula cell
+ aVal.mpFormula = sc::formula_block::at(*itPos->data, nOffset);
+ aVal.meType = CELLTYPE_FORMULA;
+ break;
+ default:
+ ;
+ }
+
+ return aVal;
+}
+
+const sc::CellTextAttr* ScColumn::GetCellTextAttr( SCROW nRow ) const
+{
+ sc::ColumnBlockConstPosition aBlockPos;
+ aBlockPos.miCellTextAttrPos = maCellTextAttrs.begin();
+ return GetCellTextAttr(aBlockPos, nRow);
+}
+
+const sc::CellTextAttr* ScColumn::GetCellTextAttr( sc::ColumnBlockConstPosition& rBlockPos, SCROW nRow ) const
+{
+ sc::CellTextAttrStoreType::const_position_type aPos = maCellTextAttrs.position(rBlockPos.miCellTextAttrPos, nRow);
+ if (aPos.first == maCellTextAttrs.end())
+ return nullptr;
+
+ rBlockPos.miCellTextAttrPos = aPos.first;
+
+ if (aPos.first->type != sc::element_type_celltextattr)
+ return nullptr;
+
+ return &sc::celltextattr_block::at(*aPos.first->data, aPos.second);
+}
+
+bool ScColumn::TestInsertCol( SCROW nStartRow, SCROW nEndRow) const
+{
+ if (IsEmptyData() && IsEmptyAttr())
+ return true;
+
+ // Return false if we have any non-empty cells between nStartRow and nEndRow inclusive.
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nStartRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it->type != sc::element_type_empty)
+ return false;
+
+ // Get the length of the remaining empty segment.
+ size_t nLen = it->size - aPos.second;
+ SCROW nNextNonEmptyRow = nStartRow + nLen;
+ if (nNextNonEmptyRow <= nEndRow)
+ return false;
+
+ // AttrArray only looks for merged cells
+
+ return pAttrArray == nullptr || pAttrArray->TestInsertCol(nStartRow, nEndRow);
+}
+
+bool ScColumn::TestInsertRow( SCROW nStartRow, SCSIZE nSize ) const
+{
+ // AttrArray only looks for merged cells
+ {
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nStartRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it->type == sc::element_type_empty && maCells.block_size() == 1)
+ // The entire cell array is empty.
+ return pAttrArray->TestInsertRow(nSize);
+ }
+
+ // See if there would be any non-empty cell that gets pushed out.
+
+ // Find the position of the last non-empty cell below nStartRow.
+ size_t nLastNonEmptyRow = GetDoc().MaxRow();
+ sc::CellStoreType::const_reverse_iterator it = maCells.rbegin();
+ if (it->type == sc::element_type_empty)
+ nLastNonEmptyRow -= it->size;
+
+ if (nLastNonEmptyRow < o3tl::make_unsigned(nStartRow))
+ // No cells would get pushed out.
+ return pAttrArray->TestInsertRow(nSize);
+
+ if (nLastNonEmptyRow + nSize > o3tl::make_unsigned(GetDoc().MaxRow()))
+ // At least one cell would get pushed out. Not good.
+ return false;
+
+ return pAttrArray->TestInsertRow(nSize);
+}
+
+void ScColumn::InsertRow( SCROW nStartRow, SCSIZE nSize )
+{
+ pAttrArray->InsertRow( nStartRow, nSize );
+
+ maCellNotes.insert_empty(nStartRow, nSize);
+ maCellNotes.resize(GetDoc().GetMaxRowCount());
+
+ maSparklines.insert_empty(nStartRow, nSize);
+ maSparklines.resize(GetDoc().GetSheetLimits().GetMaxRowCount());
+
+ maBroadcasters.insert_empty(nStartRow, nSize);
+ maBroadcasters.resize(GetDoc().GetMaxRowCount());
+
+ maCellTextAttrs.insert_empty(nStartRow, nSize);
+ maCellTextAttrs.resize(GetDoc().GetMaxRowCount());
+
+ maCells.insert_empty(nStartRow, nSize);
+ maCells.resize(GetDoc().GetMaxRowCount());
+
+ CellStorageModified();
+
+ // We *probably* don't need to broadcast here since the parent call seems
+ // to take care of it.
+}
+
+namespace {
+
+class CopyToClipHandler
+{
+ const ScDocument& mrSrcDoc;
+ const ScColumn& mrSrcCol;
+ ScColumn& mrDestCol;
+ sc::ColumnBlockPosition maDestPos;
+ sc::ColumnBlockPosition* mpDestPos;
+
+ void setDefaultAttrsToDest(size_t nRow, size_t nSize)
+ {
+ std::vector<sc::CellTextAttr> aAttrs(nSize); // default values
+ maDestPos.miCellTextAttrPos = mrDestCol.GetCellAttrStore().set(
+ maDestPos.miCellTextAttrPos, nRow, aAttrs.begin(), aAttrs.end());
+ }
+
+public:
+ CopyToClipHandler(const ScDocument& rSrcDoc, const ScColumn& rSrcCol, ScColumn& rDestCol, sc::ColumnBlockPosition* pDestPos) :
+ mrSrcDoc(rSrcDoc), mrSrcCol(rSrcCol), mrDestCol(rDestCol), mpDestPos(pDestPos)
+ {
+ if (mpDestPos)
+ maDestPos = *mpDestPos;
+ else
+ mrDestCol.InitBlockPosition(maDestPos);
+ }
+
+ ~CopyToClipHandler()
+ {
+ if (mpDestPos)
+ *mpDestPos = maDestPos;
+ }
+
+ void operator() (const sc::CellStoreType::value_type& aNode, size_t nOffset, size_t nDataSize)
+ {
+ size_t nTopRow = aNode.position + nOffset;
+
+ bool bSet = true;
+
+ switch (aNode.type)
+ {
+ case sc::element_type_numeric:
+ {
+ sc::numeric_block::const_iterator it = sc::numeric_block::begin(*aNode.data);
+ std::advance(it, nOffset);
+ sc::numeric_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+ maDestPos.miCellPos = mrDestCol.GetCellStore().set(maDestPos.miCellPos, nTopRow, it, itEnd);
+ }
+ break;
+ case sc::element_type_string:
+ {
+ sc::string_block::const_iterator it = sc::string_block::begin(*aNode.data);
+ std::advance(it, nOffset);
+ sc::string_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+ maDestPos.miCellPos = mrDestCol.GetCellStore().set(maDestPos.miCellPos, nTopRow, it, itEnd);
+
+ }
+ break;
+ case sc::element_type_edittext:
+ {
+ sc::edittext_block::const_iterator it = sc::edittext_block::begin(*aNode.data);
+ std::advance(it, nOffset);
+ sc::edittext_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+
+ std::vector<EditTextObject*> aCloned;
+ aCloned.reserve(nDataSize);
+ for (; it != itEnd; ++it)
+ aCloned.push_back(ScEditUtil::Clone(**it, mrDestCol.GetDoc()).release());
+
+ maDestPos.miCellPos = mrDestCol.GetCellStore().set(
+ maDestPos.miCellPos, nTopRow, aCloned.begin(), aCloned.end());
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ sc::formula_block::const_iterator it = sc::formula_block::begin(*aNode.data);
+ std::advance(it, nOffset);
+ sc::formula_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+
+ std::vector<ScFormulaCell*> aCloned;
+ aCloned.reserve(nDataSize);
+ ScAddress aDestPos(mrDestCol.GetCol(), nTopRow, mrDestCol.GetTab());
+ for (; it != itEnd; ++it, aDestPos.IncRow())
+ {
+ const ScFormulaCell& rOld = **it;
+ if (rOld.GetDirty() && mrSrcCol.GetDoc().GetAutoCalc())
+ const_cast<ScFormulaCell&>(rOld).Interpret();
+
+ aCloned.push_back(new ScFormulaCell(rOld, mrDestCol.GetDoc(), aDestPos));
+ }
+
+ // Group the cloned formula cells.
+ if (!aCloned.empty())
+ sc::SharedFormulaUtil::groupFormulaCells(aCloned.begin(), aCloned.end());
+
+ sc::CellStoreType& rDestCells = mrDestCol.GetCellStore();
+ maDestPos.miCellPos = rDestCells.set(
+ maDestPos.miCellPos, nTopRow, aCloned.begin(), aCloned.end());
+
+ // Merge adjacent formula cell groups (if applicable).
+ sc::CellStoreType::position_type aPos =
+ rDestCells.position(maDestPos.miCellPos, nTopRow);
+ maDestPos.miCellPos = aPos.first;
+ sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
+ size_t nLastRow = nTopRow + nDataSize;
+ if (nLastRow < o3tl::make_unsigned(mrSrcDoc.MaxRow()))
+ {
+ aPos = rDestCells.position(maDestPos.miCellPos, nLastRow+1);
+ sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
+ }
+ }
+ break;
+ default:
+ bSet = false;
+ }
+
+ if (bSet)
+ setDefaultAttrsToDest(nTopRow, nDataSize);
+
+ mrSrcCol.DuplicateNotes(nTopRow, nDataSize, mrDestCol, maDestPos, false);
+ mrSrcCol.DuplicateSparklines(nTopRow, nDataSize, mrDestCol, maDestPos);
+ }
+};
+
+class CopyTextAttrToClipHandler
+{
+ sc::CellTextAttrStoreType& mrDestAttrs;
+ sc::CellTextAttrStoreType::iterator miPos;
+
+public:
+ explicit CopyTextAttrToClipHandler( sc::CellTextAttrStoreType& rAttrs ) :
+ mrDestAttrs(rAttrs), miPos(mrDestAttrs.begin()) {}
+
+ void operator() ( const sc::CellTextAttrStoreType::value_type& aNode, size_t nOffset, size_t nDataSize )
+ {
+ if (aNode.type != sc::element_type_celltextattr)
+ return;
+
+ sc::celltextattr_block::const_iterator it = sc::celltextattr_block::begin(*aNode.data);
+ std::advance(it, nOffset);
+ sc::celltextattr_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+
+ size_t nPos = aNode.position + nOffset;
+ miPos = mrDestAttrs.set(miPos, nPos, it, itEnd);
+ }
+};
+
+
+}
+
+void ScColumn::CopyToClip(
+ sc::CopyToClipContext& rCxt, SCROW nRow1, SCROW nRow2, ScColumn& rColumn ) const
+{
+ pAttrArray->CopyArea( nRow1, nRow2, 0, *rColumn.pAttrArray,
+ rCxt.isKeepScenarioFlags() ? (ScMF::All & ~ScMF::Scenario) : ScMF::All );
+
+ {
+ CopyToClipHandler aFunc(GetDoc(), *this, rColumn, rCxt.getBlockPosition(rColumn.nTab, rColumn.nCol));
+ sc::ParseBlock(maCells.begin(), maCells, aFunc, nRow1, nRow2);
+ }
+
+ {
+ CopyTextAttrToClipHandler aFunc(rColumn.maCellTextAttrs);
+ sc::ParseBlock(maCellTextAttrs.begin(), maCellTextAttrs, aFunc, nRow1, nRow2);
+ }
+
+ rColumn.CellStorageModified();
+}
+
+void ScColumn::CopyStaticToDocument(
+ SCROW nRow1, SCROW nRow2, const SvNumberFormatterMergeMap& rMap, ScColumn& rDestCol )
+{
+ if (nRow1 > nRow2)
+ return;
+
+ sc::ColumnBlockPosition aDestPos;
+ CopyCellTextAttrsToDocument(nRow1, nRow2, rDestCol);
+ CopyCellNotesToDocument(nRow1, nRow2, rDestCol);
+
+ // First, clear the destination column for the specified row range.
+ rDestCol.maCells.set_empty(nRow1, nRow2);
+
+ aDestPos.miCellPos = rDestCol.maCells.begin();
+
+ ScDocument& rDocument = GetDoc();
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow1);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ size_t nOffset = aPos.second;
+ size_t nDataSize = 0;
+ size_t nCurRow = nRow1;
+
+ for (; it != maCells.end() && nCurRow <= o3tl::make_unsigned(nRow2); ++it, nOffset = 0, nCurRow += nDataSize)
+ {
+ bool bLastBlock = false;
+ nDataSize = it->size - nOffset;
+ if (nCurRow + nDataSize - 1 > o3tl::make_unsigned(nRow2))
+ {
+ // Truncate the block to copy to clipboard.
+ nDataSize = nRow2 - nCurRow + 1;
+ bLastBlock = true;
+ }
+
+ switch (it->type)
+ {
+ case sc::element_type_numeric:
+ {
+ sc::numeric_block::const_iterator itData = sc::numeric_block::begin(*it->data);
+ std::advance(itData, nOffset);
+ sc::numeric_block::const_iterator itDataEnd = itData;
+ std::advance(itDataEnd, nDataSize);
+ aDestPos.miCellPos = rDestCol.maCells.set(aDestPos.miCellPos, nCurRow, itData, itDataEnd);
+ }
+ break;
+ case sc::element_type_string:
+ {
+ sc::string_block::const_iterator itData = sc::string_block::begin(*it->data);
+ std::advance(itData, nOffset);
+ sc::string_block::const_iterator itDataEnd = itData;
+ std::advance(itDataEnd, nDataSize);
+ aDestPos.miCellPos = rDestCol.maCells.set(aDestPos.miCellPos, nCurRow, itData, itDataEnd);
+ }
+ break;
+ case sc::element_type_edittext:
+ {
+ sc::edittext_block::const_iterator itData = sc::edittext_block::begin(*it->data);
+ std::advance(itData, nOffset);
+ sc::edittext_block::const_iterator itDataEnd = itData;
+ std::advance(itDataEnd, nDataSize);
+
+ // Convert to simple strings.
+ std::vector<svl::SharedString> aConverted;
+ aConverted.reserve(nDataSize);
+ for (; itData != itDataEnd; ++itData)
+ {
+ const EditTextObject& rObj = **itData;
+ svl::SharedString aSS = rDocument.GetSharedStringPool().intern(ScEditUtil::GetString(rObj, &rDocument));
+ aConverted.push_back(aSS);
+ }
+ aDestPos.miCellPos = rDestCol.maCells.set(aDestPos.miCellPos, nCurRow, aConverted.begin(), aConverted.end());
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ sc::formula_block::const_iterator itData = sc::formula_block::begin(*it->data);
+ std::advance(itData, nOffset);
+ sc::formula_block::const_iterator itDataEnd = itData;
+ std::advance(itDataEnd, nDataSize);
+
+ // Interpret and convert to raw values.
+ for (SCROW i = 0; itData != itDataEnd; ++itData, ++i)
+ {
+ SCROW nRow = nCurRow + i;
+
+ ScFormulaCell& rFC = **itData;
+ if (rFC.GetDirty() && rDocument.GetAutoCalc())
+ rFC.Interpret();
+
+ if (rFC.GetErrCode() != FormulaError::NONE)
+ // Skip cells with error.
+ continue;
+
+ if (rFC.IsValue())
+ aDestPos.miCellPos = rDestCol.maCells.set(aDestPos.miCellPos, nRow, rFC.GetValue());
+ else
+ {
+ svl::SharedString aSS = rFC.GetString();
+ if (aSS.isValid())
+ aDestPos.miCellPos = rDestCol.maCells.set(aDestPos.miCellPos, nRow, aSS);
+ }
+ }
+ }
+ break;
+ default:
+ ;
+ }
+
+ if (bLastBlock)
+ break;
+ }
+
+ // Don't forget to copy the number formats over. Charts may reference them.
+ for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
+ {
+ sal_uInt32 nNumFmt = GetNumberFormat(rDocument.GetNonThreadedContext(), nRow);
+ SvNumberFormatterMergeMap::const_iterator itNum = rMap.find(nNumFmt);
+ if (itNum != rMap.end())
+ nNumFmt = itNum->second;
+
+ rDestCol.SetNumberFormat(nRow, nNumFmt);
+ }
+
+ rDestCol.CellStorageModified();
+}
+
+void ScColumn::CopyCellToDocument( SCROW nSrcRow, SCROW nDestRow, ScColumn& rDestCol )
+{
+ ScDocument& rDocument = GetDoc();
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nSrcRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ bool bSet = true;
+ switch (it->type)
+ {
+ case sc::element_type_numeric:
+ rDestCol.maCells.set(nDestRow, sc::numeric_block::at(*it->data, aPos.second));
+ break;
+ case sc::element_type_string:
+ rDestCol.maCells.set(nDestRow, sc::string_block::at(*it->data, aPos.second));
+ break;
+ case sc::element_type_edittext:
+ {
+ EditTextObject* p = sc::edittext_block::at(*it->data, aPos.second);
+ if (&rDocument == &rDestCol.GetDoc())
+ rDestCol.maCells.set(nDestRow, p->Clone().release());
+ else
+ rDestCol.maCells.set(nDestRow, ScEditUtil::Clone(*p, rDestCol.GetDoc()).release());
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ ScFormulaCell* p = sc::formula_block::at(*it->data, aPos.second);
+ if (p->GetDirty() && rDocument.GetAutoCalc())
+ p->Interpret();
+
+ ScAddress aDestPos = p->aPos;
+ aDestPos.SetRow(nDestRow);
+ ScFormulaCell* pNew = new ScFormulaCell(*p, rDestCol.GetDoc(), aDestPos);
+ rDestCol.SetFormulaCell(nDestRow, pNew);
+ }
+ break;
+ case sc::element_type_empty:
+ default:
+ // empty
+ rDestCol.maCells.set_empty(nDestRow, nDestRow);
+ bSet = false;
+ }
+
+ if (bSet)
+ {
+ rDestCol.maCellTextAttrs.set(nDestRow, maCellTextAttrs.get<sc::CellTextAttr>(nSrcRow));
+ ScPostIt* pNote = maCellNotes.get<ScPostIt*>(nSrcRow);
+ if (pNote)
+ {
+ pNote = pNote->Clone(ScAddress(nCol, nSrcRow, nTab),
+ rDestCol.GetDoc(),
+ ScAddress(rDestCol.nCol, nDestRow, rDestCol.nTab),
+ false).release();
+ rDestCol.maCellNotes.set(nDestRow, pNote);
+ pNote->UpdateCaptionPos(ScAddress(rDestCol.nCol, nDestRow, rDestCol.nTab));
+ }
+ else
+ rDestCol.maCellNotes.set_empty(nDestRow, nDestRow);
+ }
+ else
+ {
+ rDestCol.maCellTextAttrs.set_empty(nDestRow, nDestRow);
+ rDestCol.maCellNotes.set_empty(nDestRow, nDestRow);
+ }
+
+ rDestCol.CellStorageModified();
+}
+
+namespace {
+
+bool canCopyValue(const ScDocument& rDoc, const ScAddress& rPos, InsertDeleteFlags nFlags)
+{
+ sal_uInt32 nNumIndex = rDoc.GetAttr(rPos, ATTR_VALUE_FORMAT)->GetValue();
+ SvNumFormatType nType = rDoc.GetFormatTable()->GetType(nNumIndex);
+ if ((nType == SvNumFormatType::DATE) || (nType == SvNumFormatType::TIME) || (nType == SvNumFormatType::DATETIME))
+ return ((nFlags & InsertDeleteFlags::DATETIME) != InsertDeleteFlags::NONE);
+
+ return (nFlags & InsertDeleteFlags::VALUE) != InsertDeleteFlags::NONE;
+}
+
+class CopyAsLinkHandler
+{
+ const ScColumn& mrSrcCol;
+ ScColumn& mrDestCol;
+ sc::ColumnBlockPosition maDestPos;
+ sc::ColumnBlockPosition* mpDestPos;
+ InsertDeleteFlags mnCopyFlags;
+
+ sc::StartListeningType meListenType;
+
+ void setDefaultAttrToDest(size_t nRow)
+ {
+ maDestPos.miCellTextAttrPos = mrDestCol.GetCellAttrStore().set(
+ maDestPos.miCellTextAttrPos, nRow, sc::CellTextAttr());
+ }
+
+ void setDefaultAttrsToDest(size_t nRow, size_t nSize)
+ {
+ std::vector<sc::CellTextAttr> aAttrs(nSize); // default values
+ maDestPos.miCellTextAttrPos = mrDestCol.GetCellAttrStore().set(
+ maDestPos.miCellTextAttrPos, nRow, aAttrs.begin(), aAttrs.end());
+ }
+
+ ScFormulaCell* createRefCell(size_t nRow)
+ {
+ ScSingleRefData aRef;
+ aRef.InitAddress(ScAddress(mrSrcCol.GetCol(), nRow, mrSrcCol.GetTab())); // Absolute reference.
+ aRef.SetFlag3D(true);
+
+ ScTokenArray aArr(mrDestCol.GetDoc());
+ aArr.AddSingleReference(aRef);
+ return new ScFormulaCell(mrDestCol.GetDoc(), ScAddress(mrDestCol.GetCol(), nRow, mrDestCol.GetTab()), aArr);
+ }
+
+ void createRefBlock(const sc::CellStoreType::value_type& aNode, size_t nOffset, size_t nDataSize)
+ {
+ size_t nTopRow = aNode.position + nOffset;
+
+ for (size_t i = 0; i < nDataSize; ++i)
+ {
+ SCROW nRow = nTopRow + i;
+ mrDestCol.SetFormulaCell(maDestPos, nRow, createRefCell(nRow), meListenType);
+ }
+
+ setDefaultAttrsToDest(nTopRow, nDataSize);
+ }
+
+public:
+ CopyAsLinkHandler(const ScColumn& rSrcCol, ScColumn& rDestCol, sc::ColumnBlockPosition* pDestPos, InsertDeleteFlags nCopyFlags) :
+ mrSrcCol(rSrcCol),
+ mrDestCol(rDestCol),
+ mpDestPos(pDestPos),
+ mnCopyFlags(nCopyFlags),
+ meListenType(sc::SingleCellListening)
+ {
+ if (mpDestPos)
+ maDestPos = *mpDestPos;
+ }
+
+ ~CopyAsLinkHandler()
+ {
+ if (mpDestPos)
+ {
+ // Similar to CopyByCloneHandler, don't copy a singular iterator.
+ {
+ sc::ColumnBlockPosition aTempBlock;
+ mrDestCol.InitBlockPosition(aTempBlock);
+ maDestPos.miBroadcasterPos = aTempBlock.miBroadcasterPos;
+ }
+
+ *mpDestPos = maDestPos;
+ }
+ }
+
+ void setStartListening( bool b )
+ {
+ meListenType = b ? sc::SingleCellListening : sc::NoListening;
+ }
+
+ void operator() (const sc::CellStoreType::value_type& aNode, size_t nOffset, size_t nDataSize)
+ {
+ size_t nRow = aNode.position + nOffset;
+
+ if (mnCopyFlags & (InsertDeleteFlags::NOTE|InsertDeleteFlags::ADDNOTES))
+ {
+ bool bCloneCaption = (mnCopyFlags & InsertDeleteFlags::NOCAPTIONS) == InsertDeleteFlags::NONE;
+ mrSrcCol.DuplicateNotes(nRow, nDataSize, mrDestCol, maDestPos, bCloneCaption);
+ }
+
+ switch (aNode.type)
+ {
+ case sc::element_type_numeric:
+ {
+ if ((mnCopyFlags & (InsertDeleteFlags::DATETIME|InsertDeleteFlags::VALUE)) == InsertDeleteFlags::NONE)
+ return;
+
+ sc::numeric_block::const_iterator it = sc::numeric_block::begin(*aNode.data);
+ std::advance(it, nOffset);
+ sc::numeric_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+
+ ScAddress aSrcPos(mrSrcCol.GetCol(), nRow, mrSrcCol.GetTab());
+ for (; it != itEnd; ++it, aSrcPos.IncRow(), ++nRow)
+ {
+ if (!canCopyValue(mrSrcCol.GetDoc(), aSrcPos, mnCopyFlags))
+ continue;
+
+ maDestPos.miCellPos = mrDestCol.GetCellStore().set(maDestPos.miCellPos, nRow, createRefCell(nRow));
+ setDefaultAttrToDest(nRow);
+ }
+ }
+ break;
+ case sc::element_type_string:
+ case sc::element_type_edittext:
+ {
+ if (!(mnCopyFlags & InsertDeleteFlags::STRING))
+ return;
+
+ createRefBlock(aNode, nOffset, nDataSize);
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ if (!(mnCopyFlags & InsertDeleteFlags::FORMULA))
+ return;
+
+ createRefBlock(aNode, nOffset, nDataSize);
+ }
+ break;
+ default:
+ ;
+ }
+ }
+};
+
+class CopyByCloneHandler
+{
+ const ScColumn& mrSrcCol;
+ ScColumn& mrDestCol;
+ sc::ColumnBlockPosition maDestPos;
+ sc::ColumnBlockPosition* mpDestPos;
+ svl::SharedStringPool* mpSharedStringPool;
+ InsertDeleteFlags mnCopyFlags;
+
+ sc::StartListeningType meListenType;
+ ScCloneFlags mnFormulaCellCloneFlags;
+
+ void setDefaultAttrToDest(size_t nRow)
+ {
+ maDestPos.miCellTextAttrPos = mrDestCol.GetCellAttrStore().set(
+ maDestPos.miCellTextAttrPos, nRow, sc::CellTextAttr());
+ }
+
+ void setDefaultAttrsToDest(size_t nRow, size_t nSize)
+ {
+ std::vector<sc::CellTextAttr> aAttrs(nSize); // default values
+ maDestPos.miCellTextAttrPos = mrDestCol.GetCellAttrStore().set(
+ maDestPos.miCellTextAttrPos, nRow, aAttrs.begin(), aAttrs.end());
+ }
+
+ void cloneFormulaCell(size_t nRow, ScFormulaCell& rSrcCell)
+ {
+ ScAddress aDestPos(mrDestCol.GetCol(), nRow, mrDestCol.GetTab());
+
+ bool bCloneValue = (mnCopyFlags & InsertDeleteFlags::VALUE) != InsertDeleteFlags::NONE;
+ bool bCloneDateTime = (mnCopyFlags & InsertDeleteFlags::DATETIME) != InsertDeleteFlags::NONE;
+ bool bCloneString = (mnCopyFlags & InsertDeleteFlags::STRING) != InsertDeleteFlags::NONE;
+ bool bCloneSpecialBoolean = (mnCopyFlags & InsertDeleteFlags::SPECIAL_BOOLEAN) != InsertDeleteFlags::NONE;
+ bool bCloneFormula = (mnCopyFlags & InsertDeleteFlags::FORMULA) != InsertDeleteFlags::NONE;
+
+ bool bForceFormula = false;
+
+ if (bCloneSpecialBoolean)
+ {
+ // See if the formula consists of =TRUE() or =FALSE().
+ const ScTokenArray* pCode = rSrcCell.GetCode();
+ if (pCode && pCode->GetLen() == 1)
+ {
+ const formula::FormulaToken* p = pCode->FirstToken();
+ if (p->GetOpCode() == ocTrue || p->GetOpCode() == ocFalse)
+ // This is a boolean formula.
+ bForceFormula = true;
+ }
+ }
+
+ if (bForceFormula || bCloneFormula)
+ {
+ // Clone as formula cell.
+ ScFormulaCell* pCell = new ScFormulaCell(rSrcCell, mrDestCol.GetDoc(), aDestPos, mnFormulaCellCloneFlags);
+ pCell->SetDirtyVar();
+ mrDestCol.SetFormulaCell(maDestPos, nRow, pCell, meListenType, rSrcCell.NeedsNumberFormat());
+ setDefaultAttrToDest(nRow);
+ return;
+ }
+
+ if (mrDestCol.GetDoc().IsUndo())
+ return;
+
+ if (bCloneValue)
+ {
+ FormulaError nErr = rSrcCell.GetErrCode();
+ if (nErr != FormulaError::NONE)
+ {
+ // error codes are cloned with values
+ ScFormulaCell* pErrCell = new ScFormulaCell(mrDestCol.GetDoc(), aDestPos);
+ pErrCell->SetErrCode(nErr);
+ mrDestCol.SetFormulaCell(maDestPos, nRow, pErrCell, meListenType);
+ setDefaultAttrToDest(nRow);
+ return;
+ }
+ }
+
+ if (bCloneValue || bCloneDateTime)
+ {
+ if (rSrcCell.IsValue())
+ {
+ if (canCopyValue(mrSrcCol.GetDoc(), ScAddress(mrSrcCol.GetCol(), nRow, mrSrcCol.GetTab()), mnCopyFlags))
+ {
+ maDestPos.miCellPos = mrDestCol.GetCellStore().set(
+ maDestPos.miCellPos, nRow, rSrcCell.GetValue());
+ setDefaultAttrToDest(nRow);
+ }
+
+ return;
+ }
+ }
+
+ if (!bCloneString)
+ return;
+
+ svl::SharedString aStr = rSrcCell.GetString();
+ if (aStr.isEmpty())
+ // Don't create empty string cells.
+ return;
+
+ if (rSrcCell.IsMultilineResult())
+ {
+ // Clone as an edit text object.
+ EditEngine& rEngine = mrDestCol.GetDoc().GetEditEngine();
+ rEngine.SetText(aStr.getString());
+ maDestPos.miCellPos =
+ mrDestCol.GetCellStore().set(maDestPos.miCellPos, nRow, rEngine.CreateTextObject().release());
+ }
+ else
+ {
+ maDestPos.miCellPos =
+ mrDestCol.GetCellStore().set(maDestPos.miCellPos, nRow, aStr);
+ }
+
+ setDefaultAttrToDest(nRow);
+ }
+
+public:
+ CopyByCloneHandler(const ScColumn& rSrcCol, ScColumn& rDestCol, sc::ColumnBlockPosition* pDestPos,
+ InsertDeleteFlags nCopyFlags, svl::SharedStringPool* pSharedStringPool, bool bGlobalNamesToLocal) :
+ mrSrcCol(rSrcCol),
+ mrDestCol(rDestCol),
+ mpDestPos(pDestPos),
+ mpSharedStringPool(pSharedStringPool),
+ mnCopyFlags(nCopyFlags),
+ meListenType(sc::SingleCellListening),
+ mnFormulaCellCloneFlags(bGlobalNamesToLocal ? ScCloneFlags::NamesToLocal : ScCloneFlags::Default)
+ {
+ if (mpDestPos)
+ maDestPos = *mpDestPos;
+ }
+
+ ~CopyByCloneHandler()
+ {
+ if (!mpDestPos)
+ return;
+
+ // If broadcasters were setup in the same column,
+ // maDestPos.miBroadcasterPos doesn't match
+ // mrDestCol.maBroadcasters because it is never passed anywhere.
+ // Assign a corresponding iterator before copying all over.
+ // Otherwise this may result in wrongly copying a singular
+ // iterator.
+
+ {
+ /* XXX Using a temporary ColumnBlockPosition just for
+ * initializing from ScColumn::maBroadcasters.begin() is ugly,
+ * on the other hand we don't want to expose
+ * ScColumn::maBroadcasters to the outer world and have a
+ * getter. */
+ sc::ColumnBlockPosition aTempBlock;
+ mrDestCol.InitBlockPosition(aTempBlock);
+ maDestPos.miBroadcasterPos = aTempBlock.miBroadcasterPos;
+ }
+
+ *mpDestPos = maDestPos;
+ }
+
+ void setStartListening( bool b )
+ {
+ meListenType = b ? sc::SingleCellListening : sc::NoListening;
+ }
+
+ void operator() (const sc::CellStoreType::value_type& aNode, size_t nOffset, size_t nDataSize)
+ {
+ size_t nRow = aNode.position + nOffset;
+
+ if (mnCopyFlags & (InsertDeleteFlags::NOTE|InsertDeleteFlags::ADDNOTES))
+ {
+ bool bCloneCaption = (mnCopyFlags & InsertDeleteFlags::NOCAPTIONS) == InsertDeleteFlags::NONE;
+ mrSrcCol.DuplicateNotes(nRow, nDataSize, mrDestCol, maDestPos, bCloneCaption);
+ }
+
+ switch (aNode.type)
+ {
+ case sc::element_type_numeric:
+ {
+ if ((mnCopyFlags & (InsertDeleteFlags::DATETIME|InsertDeleteFlags::VALUE)) == InsertDeleteFlags::NONE)
+ return;
+
+ sc::numeric_block::const_iterator it = sc::numeric_block::begin(*aNode.data);
+ std::advance(it, nOffset);
+ sc::numeric_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+
+ ScAddress aSrcPos(mrSrcCol.GetCol(), nRow, mrSrcCol.GetTab());
+ for (; it != itEnd; ++it, aSrcPos.IncRow(), ++nRow)
+ {
+ if (!canCopyValue(mrSrcCol.GetDoc(), aSrcPos, mnCopyFlags))
+ continue;
+
+ maDestPos.miCellPos = mrDestCol.GetCellStore().set(maDestPos.miCellPos, nRow, *it);
+ setDefaultAttrToDest(nRow);
+ }
+ }
+ break;
+ case sc::element_type_string:
+ {
+ if (!(mnCopyFlags & InsertDeleteFlags::STRING))
+ return;
+
+ sc::string_block::const_iterator it = sc::string_block::begin(*aNode.data);
+ std::advance(it, nOffset);
+ sc::string_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+
+ for (; it != itEnd; ++it, ++nRow)
+ {
+ const svl::SharedString& rStr = *it;
+ if (rStr.isEmpty())
+ {
+ // String cell with empty value is used to special-case cell value removal.
+ maDestPos.miCellPos = mrDestCol.GetCellStore().set_empty(
+ maDestPos.miCellPos, nRow, nRow);
+ maDestPos.miCellTextAttrPos = mrDestCol.GetCellAttrStore().set_empty(
+ maDestPos.miCellTextAttrPos, nRow, nRow);
+ }
+ else
+ {
+ if (mpSharedStringPool)
+ {
+ // Re-intern the string if source is a different document.
+ svl::SharedString aInterned = mpSharedStringPool->intern( rStr.getString());
+ maDestPos.miCellPos =
+ mrDestCol.GetCellStore().set(maDestPos.miCellPos, nRow, aInterned);
+ }
+ else
+ {
+ maDestPos.miCellPos =
+ mrDestCol.GetCellStore().set(maDestPos.miCellPos, nRow, rStr);
+ }
+ setDefaultAttrToDest(nRow);
+ }
+ }
+ }
+ break;
+ case sc::element_type_edittext:
+ {
+ if (!(mnCopyFlags & InsertDeleteFlags::STRING))
+ return;
+
+ sc::edittext_block::const_iterator it = sc::edittext_block::begin(*aNode.data);
+ std::advance(it, nOffset);
+ sc::edittext_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+
+ std::vector<EditTextObject*> aCloned;
+ aCloned.reserve(nDataSize);
+ for (; it != itEnd; ++it)
+ aCloned.push_back(ScEditUtil::Clone(**it, mrDestCol.GetDoc()).release());
+
+ maDestPos.miCellPos = mrDestCol.GetCellStore().set(
+ maDestPos.miCellPos, nRow, aCloned.begin(), aCloned.end());
+
+ setDefaultAttrsToDest(nRow, nDataSize);
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ sc::formula_block::const_iterator it = sc::formula_block::begin(*aNode.data);
+ std::advance(it, nOffset);
+ sc::formula_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+
+ sc::DelayStartListeningFormulaCells startDelay(mrDestCol); // disabled
+ if(nDataSize > 1024 && (mnCopyFlags & InsertDeleteFlags::FORMULA) != InsertDeleteFlags::NONE)
+ {
+ // If the column to be replaced contains a long formula group (tdf#102364), there can
+ // be so many listeners in a single vector that the quadratic cost of repeatedly removing
+ // the first element becomes very high. Optimize this by removing them in one go.
+ sc::EndListeningContext context(mrDestCol.GetDoc());
+ mrDestCol.EndListeningFormulaCells( context, nRow, nRow + nDataSize - 1, nullptr, nullptr );
+ // There can be a similar problem with starting to listen to cells repeatedly (tdf#133302).
+ // Delay it.
+ startDelay.set();
+ }
+
+ for (; it != itEnd; ++it, ++nRow)
+ cloneFormulaCell(nRow, **it);
+ }
+ break;
+ default:
+ ;
+ }
+ }
+};
+
+}
+
+void ScColumn::CopyToColumn(
+ sc::CopyToDocContext& rCxt,
+ SCROW nRow1, SCROW nRow2, InsertDeleteFlags nFlags, bool bMarked, ScColumn& rColumn,
+ const ScMarkData* pMarkData, bool bAsLink, bool bGlobalNamesToLocal) const
+{
+ if (bMarked)
+ {
+ SCROW nStart, nEnd;
+ if (pMarkData && pMarkData->IsMultiMarked())
+ {
+ ScMultiSelIter aIter( pMarkData->GetMultiSelData(), nCol );
+
+ while ( aIter.Next( nStart, nEnd ) && nStart <= nRow2 )
+ {
+ if ( nEnd >= nRow1 )
+ CopyToColumn(rCxt, std::max(nRow1,nStart), std::min(nRow2,nEnd),
+ nFlags, false, rColumn, pMarkData, bAsLink );
+ }
+ }
+ else
+ {
+ OSL_FAIL("CopyToColumn: bMarked, but no mark");
+ }
+ return;
+ }
+
+ if ( (nFlags & InsertDeleteFlags::ATTRIB) != InsertDeleteFlags::NONE )
+ {
+ if ( (nFlags & InsertDeleteFlags::STYLES) != InsertDeleteFlags::STYLES )
+ { // keep the StyleSheets in the target document
+ // e.g. DIF and RTF Clipboard-Import
+ for ( SCROW nRow = nRow1; nRow <= nRow2; nRow++ )
+ {
+ const ScStyleSheet* pStyle =
+ rColumn.pAttrArray->GetPattern( nRow )->GetStyleSheet();
+ const ScPatternAttr* pPattern = pAttrArray->GetPattern( nRow );
+ std::unique_ptr<ScPatternAttr> pNewPattern(new ScPatternAttr( *pPattern ));
+ pNewPattern->SetStyleSheet( const_cast<ScStyleSheet*>(pStyle) );
+ rColumn.pAttrArray->SetPattern( nRow, std::move(pNewPattern), true );
+ }
+ }
+ else
+ pAttrArray->CopyArea( nRow1, nRow2, 0, *rColumn.pAttrArray);
+ }
+
+ if ((nFlags & InsertDeleteFlags::CONTENTS) == InsertDeleteFlags::NONE)
+ return;
+
+ if (bAsLink)
+ {
+ CopyAsLinkHandler aFunc(*this, rColumn, rCxt.getBlockPosition(rColumn.nTab, rColumn.nCol), nFlags);
+ aFunc.setStartListening(rCxt.isStartListening());
+ sc::ParseBlock(maCells.begin(), maCells, aFunc, nRow1, nRow2);
+ }
+ else
+ {
+ // Compare the ScDocumentPool* to determine if we are copying
+ // within the same document. If not, re-intern shared strings.
+ svl::SharedStringPool* pSharedStringPool =
+ (GetDoc().GetPool() != rColumn.GetDoc().GetPool()) ?
+ &rColumn.GetDoc().GetSharedStringPool() : nullptr;
+ CopyByCloneHandler aFunc(*this, rColumn, rCxt.getBlockPosition(rColumn.nTab, rColumn.nCol), nFlags,
+ pSharedStringPool, bGlobalNamesToLocal);
+ aFunc.setStartListening(rCxt.isStartListening());
+ sc::ParseBlock(maCells.begin(), maCells, aFunc, nRow1, nRow2);
+ }
+
+ rColumn.CellStorageModified();
+}
+
+void ScColumn::UndoToColumn(
+ sc::CopyToDocContext& rCxt, SCROW nRow1, SCROW nRow2, InsertDeleteFlags nFlags, bool bMarked,
+ ScColumn& rColumn ) const
+{
+ if (nRow1 > 0)
+ CopyToColumn(rCxt, 0, nRow1-1, InsertDeleteFlags::FORMULA, false, rColumn);
+
+ CopyToColumn(rCxt, nRow1, nRow2, nFlags, bMarked, rColumn); //TODO: bMarked ????
+
+ if (nRow2 < GetDoc().MaxRow())
+ CopyToColumn(rCxt, nRow2+1, GetDoc().MaxRow(), InsertDeleteFlags::FORMULA, false, rColumn);
+}
+
+void ScColumn::CopyUpdated( const ScColumn* pPosCol, ScColumn& rDestCol ) const
+{
+ // Copy cells from this column to the destination column only for those
+ // rows that are present in the position column (pPosCol).
+
+ // First, mark all the non-empty cell ranges from the position column.
+ sc::SingleColumnSpanSet aRangeSet(GetDoc().GetSheetLimits());
+ if(pPosCol)
+ aRangeSet.scan(*pPosCol);
+
+ // Now, copy cells from this column to the destination column for those
+ // marked row ranges.
+ sc::SingleColumnSpanSet::SpansType aRanges;
+ aRangeSet.getSpans(aRanges);
+
+ CopyToClipHandler aFunc(GetDoc(), *this, rDestCol, nullptr);
+ sc::CellStoreType::const_iterator itPos = maCells.begin();
+ for (const auto& rRange : aRanges)
+ itPos = sc::ParseBlock(itPos, maCells, aFunc, rRange.mnRow1, rRange.mnRow2);
+
+ rDestCol.CellStorageModified();
+}
+
+void ScColumn::CopyScenarioFrom( const ScColumn& rSrcCol )
+{
+ // This is the scenario table, the data is copied into it
+ ScDocument& rDocument = GetDoc();
+ ScAttrIterator aAttrIter( pAttrArray.get(), 0, GetDoc().MaxRow(), rDocument.GetDefPattern() );
+ SCROW nStart = -1, nEnd = -1;
+ const ScPatternAttr* pPattern = aAttrIter.Next( nStart, nEnd );
+ while (pPattern)
+ {
+ if ( pPattern->GetItem( ATTR_MERGE_FLAG ).IsScenario() )
+ {
+ DeleteArea( nStart, nEnd, InsertDeleteFlags::CONTENTS );
+ sc::CopyToDocContext aCxt(rDocument);
+ rSrcCol.
+ CopyToColumn(aCxt, nStart, nEnd, InsertDeleteFlags::CONTENTS, false, *this);
+
+ // UpdateUsed not needed, already done in TestCopyScenario (obsolete comment ?)
+
+ sc::RefUpdateContext aRefCxt(rDocument);
+ aRefCxt.meMode = URM_COPY;
+ aRefCxt.maRange = ScRange(nCol, nStart, nTab, nCol, nEnd, nTab);
+ aRefCxt.mnTabDelta = nTab - rSrcCol.nTab;
+ UpdateReferenceOnCopy(aRefCxt);
+ UpdateCompile();
+ }
+ pPattern = aAttrIter.Next( nStart, nEnd );
+ }
+}
+
+void ScColumn::CopyScenarioTo( ScColumn& rDestCol ) const
+{
+ // This is the scenario table, the data is copied to the other
+ ScDocument& rDocument = GetDoc();
+ ScAttrIterator aAttrIter( pAttrArray.get(), 0, GetDoc().MaxRow(), rDocument.GetDefPattern() );
+ SCROW nStart = -1, nEnd = -1;
+ const ScPatternAttr* pPattern = aAttrIter.Next( nStart, nEnd );
+ while (pPattern)
+ {
+ if ( pPattern->GetItem( ATTR_MERGE_FLAG ).IsScenario() )
+ {
+ rDestCol.DeleteArea( nStart, nEnd, InsertDeleteFlags::CONTENTS );
+ sc::CopyToDocContext aCxt(rDestCol.GetDoc());
+ CopyToColumn(aCxt, nStart, nEnd, InsertDeleteFlags::CONTENTS, false, rDestCol);
+
+ sc::RefUpdateContext aRefCxt(rDocument);
+ aRefCxt.meMode = URM_COPY;
+ aRefCxt.maRange = ScRange(rDestCol.nCol, nStart, rDestCol.nTab, rDestCol.nCol, nEnd, rDestCol.nTab);
+ aRefCxt.mnTabDelta = rDestCol.nTab - nTab;
+ rDestCol.UpdateReferenceOnCopy(aRefCxt);
+ rDestCol.UpdateCompile();
+ }
+ pPattern = aAttrIter.Next( nStart, nEnd );
+ }
+}
+
+bool ScColumn::TestCopyScenarioTo( const ScColumn& rDestCol ) const
+{
+ bool bOk = true;
+ ScAttrIterator aAttrIter( pAttrArray.get(), 0, GetDoc().MaxRow(), GetDoc().GetDefPattern() );
+ SCROW nStart = 0, nEnd = 0;
+ const ScPatternAttr* pPattern = aAttrIter.Next( nStart, nEnd );
+ while (pPattern && bOk)
+ {
+ if ( pPattern->GetItem( ATTR_MERGE_FLAG ).IsScenario() )
+ if ( rDestCol.pAttrArray->HasAttrib( nStart, nEnd, HasAttrFlags::Protected ) )
+ bOk = false;
+
+ pPattern = aAttrIter.Next( nStart, nEnd );
+ }
+ return bOk;
+}
+
+void ScColumn::MarkScenarioIn( ScMarkData& rDestMark ) const
+{
+ ScRange aRange( nCol, 0, nTab );
+
+ ScAttrIterator aAttrIter( pAttrArray.get(), 0, GetDoc().MaxRow(), GetDoc().GetDefPattern() );
+ SCROW nStart = -1, nEnd = -1;
+ const ScPatternAttr* pPattern = aAttrIter.Next( nStart, nEnd );
+ while (pPattern)
+ {
+ if ( pPattern->GetItem( ATTR_MERGE_FLAG ).IsScenario() )
+ {
+ aRange.aStart.SetRow( nStart );
+ aRange.aEnd.SetRow( nEnd );
+ rDestMark.SetMultiMarkArea( aRange );
+ }
+
+ pPattern = aAttrIter.Next( nStart, nEnd );
+ }
+}
+
+namespace {
+
+void resetColumnPosition(sc::CellStoreType& rCells, SCCOL nCol)
+{
+ for (auto& rCellItem : rCells)
+ {
+ if (rCellItem.type != sc::element_type_formula)
+ continue;
+
+ sc::formula_block::iterator itCell = sc::formula_block::begin(*rCellItem.data);
+ sc::formula_block::iterator itCellEnd = sc::formula_block::end(*rCellItem.data);
+ for (; itCell != itCellEnd; ++itCell)
+ {
+ ScFormulaCell& rCell = **itCell;
+ rCell.aPos.SetCol(nCol);
+ }
+ }
+}
+
+class NoteCaptionUpdater
+{
+ SCCOL mnCol;
+ SCTAB mnTab;
+public:
+ NoteCaptionUpdater( SCCOL nCol, SCTAB nTab ) : mnCol(nCol), mnTab(nTab) {}
+
+ void operator() ( size_t nRow, ScPostIt* p )
+ {
+ p->UpdateCaptionPos(ScAddress(mnCol,nRow,mnTab));
+ }
+};
+
+}
+
+void ScColumn::UpdateNoteCaptions( SCROW nRow1, SCROW nRow2 )
+{
+ NoteCaptionUpdater aFunc(nCol, nTab);
+ sc::ProcessNote(maCellNotes.begin(), maCellNotes, nRow1, nRow2, aFunc);
+}
+
+void ScColumn::UpdateDrawObjects(std::vector<std::vector<SdrObject*>>& pObjects, SCROW nRowStart, SCROW nRowEnd)
+{
+ assert(static_cast<int>(pObjects.size()) >= nRowEnd - nRowStart + 1);
+
+ int nObj = 0;
+ for (SCROW nCurrentRow = nRowStart; nCurrentRow <= nRowEnd; nCurrentRow++, nObj++)
+ {
+ if (pObjects[nObj].empty())
+ continue; // No draw objects in this row
+
+ UpdateDrawObjectsForRow(pObjects[nObj], nCol, nCurrentRow);
+ }
+}
+
+void ScColumn::UpdateDrawObjectsForRow( std::vector<SdrObject*>& pObjects, SCCOL nTargetCol, SCROW nTargetRow )
+{
+ for (auto &pObject : pObjects)
+ {
+ ScAddress aNewAddress(nTargetCol, nTargetRow, nTab);
+
+ // Update draw object according to new anchor
+ ScDrawLayer* pDrawLayer = GetDoc().GetDrawLayer();
+ if (pDrawLayer)
+ pDrawLayer->MoveObject(pObject, aNewAddress);
+ }
+}
+
+bool ScColumn::IsDrawObjectsEmptyBlock(SCROW nStartRow, SCROW nEndRow) const
+{
+ ScDrawLayer* pDrawLayer = GetDoc().GetDrawLayer();
+ if (!pDrawLayer)
+ return true;
+
+ ScRange aRange(nCol, nStartRow, nTab, nCol, nEndRow, nTab);
+ return !pDrawLayer->HasObjectsAnchoredInRange(aRange);
+}
+
+void ScColumn::SwapCol(ScColumn& rCol)
+{
+ maBroadcasters.swap(rCol.maBroadcasters);
+ maCells.swap(rCol.maCells);
+ maCellTextAttrs.swap(rCol.maCellTextAttrs);
+ maCellNotes.swap(rCol.maCellNotes);
+ maSparklines.swap(rCol.maSparklines);
+
+ // Swap all CellStoreEvent mdds event_func related.
+ maCells.event_handler().swap(rCol.maCells.event_handler());
+ std::swap( mnBlkCountFormula, rCol.mnBlkCountFormula);
+
+ // notes update caption
+ UpdateNoteCaptions(0, GetDoc().MaxRow());
+ rCol.UpdateNoteCaptions(0, GetDoc().MaxRow());
+
+ std::swap(pAttrArray, rCol.pAttrArray);
+
+ // AttrArray needs to have the right column number
+ pAttrArray->SetCol(nCol);
+ rCol.pAttrArray->SetCol(rCol.nCol);
+
+ // Reset column positions in formula cells.
+ resetColumnPosition(maCells, nCol);
+ resetColumnPosition(rCol.maCells, rCol.nCol);
+
+ CellStorageModified();
+ rCol.CellStorageModified();
+}
+
+void ScColumn::MoveTo(SCROW nStartRow, SCROW nEndRow, ScColumn& rCol)
+{
+ pAttrArray->MoveTo(nStartRow, nEndRow, *rCol.pAttrArray);
+
+ // Mark the non-empty cells within the specified range, for later broadcasting.
+ sc::SingleColumnSpanSet aNonEmpties(GetDoc().GetSheetLimits());
+ aNonEmpties.scan(*this, nStartRow, nEndRow);
+ sc::SingleColumnSpanSet::SpansType aRanges;
+ aNonEmpties.getSpans(aRanges);
+
+ // Split the formula grouping at the top and bottom boundaries.
+ sc::CellStoreType::position_type aPos = maCells.position(nStartRow);
+ sc::SharedFormulaUtil::splitFormulaCellGroup(aPos, nullptr);
+ if (GetDoc().ValidRow(nEndRow+1))
+ {
+ aPos = maCells.position(aPos.first, nEndRow+1);
+ sc::SharedFormulaUtil::splitFormulaCellGroup(aPos, nullptr);
+ }
+
+ // Do the same with the destination column.
+ aPos = rCol.maCells.position(nStartRow);
+ sc::SharedFormulaUtil::splitFormulaCellGroup(aPos, nullptr);
+ if (GetDoc().ValidRow(nEndRow+1))
+ {
+ aPos = rCol.maCells.position(aPos.first, nEndRow+1);
+ sc::SharedFormulaUtil::splitFormulaCellGroup(aPos, nullptr);
+ }
+
+ // Move the broadcasters to the destination column.
+ maBroadcasters.transfer(nStartRow, nEndRow, rCol.maBroadcasters, nStartRow);
+ maCells.transfer(nStartRow, nEndRow, rCol.maCells, nStartRow);
+ maCellTextAttrs.transfer(nStartRow, nEndRow, rCol.maCellTextAttrs, nStartRow);
+
+ // move the notes to the destination column
+ maCellNotes.transfer(nStartRow, nEndRow, rCol.maCellNotes, nStartRow);
+ UpdateNoteCaptions(0, GetDoc().MaxRow());
+
+ // Re-group transferred formula cells.
+ aPos = rCol.maCells.position(nStartRow);
+ sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
+ if (GetDoc().ValidRow(nEndRow+1))
+ {
+ aPos = rCol.maCells.position(aPos.first, nEndRow+1);
+ sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
+ }
+
+ CellStorageModified();
+ rCol.CellStorageModified();
+
+ // Broadcast on moved ranges. Area-broadcast only.
+ ScDocument& rDocument = GetDoc();
+ ScHint aHint(SfxHintId::ScDataChanged, ScAddress(nCol, 0, nTab));
+ for (const auto& rRange : aRanges)
+ {
+ for (SCROW nRow = rRange.mnRow1; nRow <= rRange.mnRow2; ++nRow)
+ {
+ aHint.SetAddressRow(nRow);
+ rDocument.AreaBroadcast(aHint);
+ }
+ }
+}
+
+namespace {
+
+class SharedTopFormulaCellPicker
+{
+public:
+ SharedTopFormulaCellPicker() = default;
+ SharedTopFormulaCellPicker(SharedTopFormulaCellPicker const &) = default;
+ SharedTopFormulaCellPicker(SharedTopFormulaCellPicker &&) = default;
+ SharedTopFormulaCellPicker & operator =(SharedTopFormulaCellPicker const &) = default;
+ SharedTopFormulaCellPicker & operator =(SharedTopFormulaCellPicker &&) = default;
+
+ virtual ~SharedTopFormulaCellPicker() {}
+
+ void operator() ( sc::CellStoreType::value_type& node )
+ {
+ if (node.type != sc::element_type_formula)
+ return;
+
+ size_t nTopRow = node.position;
+
+ sc::formula_block::iterator itBeg = sc::formula_block::begin(*node.data);
+ sc::formula_block::iterator itEnd = sc::formula_block::end(*node.data);
+
+ // Only pick shared formula cells that are the top cells of their
+ // respective shared ranges.
+ for (sc::formula_block::iterator it = itBeg; it != itEnd; ++it)
+ {
+ ScFormulaCell* pCell = *it;
+ size_t nRow = nTopRow + std::distance(itBeg, it);
+ if (!pCell->IsShared())
+ {
+ processNonShared(pCell, nRow);
+ continue;
+ }
+
+ if (pCell->IsSharedTop())
+ {
+ ScFormulaCell** pp = &(*it);
+ processSharedTop(pp, nRow, pCell->GetSharedLength());
+
+ // Move to the last cell in the group, to get incremented to
+ // the next cell in the next iteration.
+ size_t nOffsetToLast = pCell->GetSharedLength() - 1;
+ std::advance(it, nOffsetToLast);
+ }
+ }
+ }
+
+ virtual void processNonShared( ScFormulaCell* /*pCell*/, size_t /*nRow*/ ) {}
+ virtual void processSharedTop( ScFormulaCell** /*ppCells*/, size_t /*nRow*/, size_t /*nLength*/ ) {}
+};
+
+class UpdateRefOnCopy
+{
+ const sc::RefUpdateContext& mrCxt;
+ ScDocument* mpUndoDoc;
+ bool mbUpdated;
+
+public:
+ UpdateRefOnCopy(const sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc) :
+ mrCxt(rCxt), mpUndoDoc(pUndoDoc), mbUpdated(false) {}
+
+ bool isUpdated() const { return mbUpdated; }
+
+ void operator() (sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize)
+ {
+ if (node.type != sc::element_type_formula)
+ return;
+
+ sc::formula_block::iterator it = sc::formula_block::begin(*node.data);
+ std::advance(it, nOffset);
+ sc::formula_block::iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+
+ for (; it != itEnd; ++it)
+ {
+ ScFormulaCell& rCell = **it;
+ mbUpdated |= rCell.UpdateReference(mrCxt, mpUndoDoc);
+ }
+ }
+};
+
+class UpdateRefOnNonCopy
+{
+ SCCOL mnCol;
+ SCROW mnTab;
+ const sc::RefUpdateContext* mpCxt;
+ ScDocument* mpUndoDoc;
+ bool mbUpdated;
+ bool mbClipboardSource;
+
+ void recompileTokenArray( ScFormulaCell& rTopCell )
+ {
+ // We need to re-compile the token array when a range name is
+ // modified, to correctly reflect the new references in the
+ // name.
+ ScCompiler aComp(mpCxt->mrDoc, rTopCell.aPos, *rTopCell.GetCode(), mpCxt->mrDoc.GetGrammar(),
+ true, rTopCell.GetMatrixFlag() != ScMatrixMode::NONE);
+ aComp.CompileTokenArray();
+ }
+
+ void updateRefOnShift( sc::FormulaGroupEntry& rGroup )
+ {
+ if (!rGroup.mbShared)
+ {
+ ScAddress aUndoPos(mnCol, rGroup.mnRow, mnTab);
+ mbUpdated |= rGroup.mpCell->UpdateReferenceOnShift(*mpCxt, mpUndoDoc, &aUndoPos);
+ return;
+ }
+
+ // Update references of a formula group.
+ ScFormulaCell** pp = rGroup.mpCells;
+ ScFormulaCell** ppEnd = pp + rGroup.mnLength;
+ ScFormulaCell* pTop = *pp;
+ ScTokenArray* pCode = pTop->GetCode();
+ ScTokenArray aOldCode(pCode->CloneValue());
+ ScAddress aOldPos = pTop->aPos;
+
+ // Run this before the position gets updated.
+ sc::RefUpdateResult aRes = pCode->AdjustReferenceOnShift(*mpCxt, aOldPos);
+
+ bool bGroupShifted = false;
+ if (pTop->UpdatePosOnShift(*mpCxt))
+ {
+ ScAddress aErrorPos( ScAddress::UNINITIALIZED );
+ // Update the positions of all formula cells.
+ for (++pp; pp != ppEnd; ++pp) // skip the top cell.
+ {
+ ScFormulaCell* pFC = *pp;
+ if (!pFC->aPos.Move(mpCxt->mnColDelta, mpCxt->mnRowDelta, mpCxt->mnTabDelta,
+ aErrorPos, mpCxt->mrDoc))
+ {
+ assert(!"can't move formula cell");
+ }
+ }
+
+ if (pCode->IsRecalcModeOnRefMove())
+ aRes.mbValueChanged = true;
+
+ // FormulaGroupAreaListener (contrary to ScBroadcastArea) is not
+ // updated but needs to be re-setup, else at least its mpColumn
+ // would indicate the old column to collect cells from. tdf#129396
+ /* TODO: investigate if that could be short-cut to avoid all the
+ * EndListeningTo() / StartListeningTo() overhead and is really
+ * only necessary when shifting the column, not also when shifting
+ * rows. */
+ bGroupShifted = true;
+ }
+ else if (aRes.mbReferenceModified && pCode->IsRecalcModeOnRefMove())
+ {
+ // The cell itself hasn't shifted. But it may have ROW or COLUMN
+ // referencing another cell that has.
+ aRes.mbValueChanged = true;
+ }
+
+ if (aRes.mbNameModified)
+ recompileTokenArray(*pTop);
+
+ if (aRes.mbReferenceModified || aRes.mbNameModified || bGroupShifted)
+ {
+ sc::EndListeningContext aEndCxt(mpCxt->mrDoc, &aOldCode);
+ aEndCxt.setPositionDelta(
+ ScAddress(-mpCxt->mnColDelta, -mpCxt->mnRowDelta, -mpCxt->mnTabDelta));
+
+ for (pp = rGroup.mpCells; pp != ppEnd; ++pp)
+ {
+ ScFormulaCell* p = *pp;
+ p->EndListeningTo(aEndCxt);
+ p->SetNeedsListening(true);
+ }
+
+ mbUpdated = true;
+
+ fillUndoDoc(aOldPos, rGroup.mnLength, aOldCode);
+ }
+
+ if (aRes.mbValueChanged)
+ {
+ for (pp = rGroup.mpCells; pp != ppEnd; ++pp)
+ {
+ ScFormulaCell* p = *pp;
+ p->SetNeedsDirty(true);
+ }
+ }
+ }
+
+ void updateRefOnMove( sc::FormulaGroupEntry& rGroup )
+ {
+ if (!rGroup.mbShared)
+ {
+ ScAddress aUndoPos(mnCol, rGroup.mnRow, mnTab);
+ mbUpdated |= rGroup.mpCell->UpdateReferenceOnMove(*mpCxt, mpUndoDoc, &aUndoPos);
+ return;
+ }
+
+ // Update references of a formula group.
+ ScFormulaCell** pp = rGroup.mpCells;
+ ScFormulaCell** ppEnd = pp + rGroup.mnLength;
+ ScFormulaCell* pTop = *pp;
+ ScTokenArray* pCode = pTop->GetCode();
+ ScTokenArray aOldCode(pCode->CloneValue());
+
+ ScAddress aPos = pTop->aPos;
+ ScAddress aOldPos = aPos;
+
+ bool bCellMoved;
+ if (mpCxt->maRange.Contains(aPos))
+ {
+ bCellMoved = true;
+
+ // The cell is being moved or copied to a new position. The
+ // position has already been updated prior to this call.
+ // Determine its original position before the move which will be
+ // used to adjust relative references later.
+
+ aOldPos.Set(
+ aPos.Col() - mpCxt->mnColDelta,
+ aPos.Row() - mpCxt->mnRowDelta,
+ aPos.Tab() - mpCxt->mnTabDelta);
+ }
+ else
+ {
+ bCellMoved = false;
+ }
+
+ bool bRecalcOnMove = pCode->IsRecalcModeOnRefMove();
+ if (bRecalcOnMove)
+ bRecalcOnMove = aPos != aOldPos;
+
+ sc::RefUpdateResult aRes = pCode->AdjustReferenceOnMove(*mpCxt, aOldPos, aPos);
+
+ if (!(aRes.mbReferenceModified || aRes.mbNameModified || bRecalcOnMove))
+ return;
+
+ sc::AutoCalcSwitch aACSwitch(mpCxt->mrDoc, false);
+
+ if (aRes.mbNameModified)
+ recompileTokenArray(*pTop);
+
+ // Perform end-listening, start-listening, and dirtying on all
+ // formula cells in the group.
+
+ // Make sure that the start and end listening contexts share the
+ // same block position set, else an invalid iterator may ensue.
+ auto pPosSet = std::make_shared<sc::ColumnBlockPositionSet>(mpCxt->mrDoc);
+
+ sc::StartListeningContext aStartCxt(mpCxt->mrDoc, pPosSet);
+ sc::EndListeningContext aEndCxt(mpCxt->mrDoc, pPosSet, &aOldCode);
+
+ aEndCxt.setPositionDelta(
+ ScAddress(-mpCxt->mnColDelta, -mpCxt->mnRowDelta, -mpCxt->mnTabDelta));
+
+ for (; pp != ppEnd; ++pp)
+ {
+ ScFormulaCell* p = *pp;
+ p->EndListeningTo(aEndCxt);
+ p->StartListeningTo(aStartCxt);
+ p->SetDirty();
+ }
+
+ mbUpdated = true;
+
+ // Move from clipboard is Cut&Paste, then do not copy the original
+ // positions' formula cells to the Undo document.
+ if (!mbClipboardSource || !bCellMoved)
+ fillUndoDoc(aOldPos, rGroup.mnLength, aOldCode);
+ }
+
+ void fillUndoDoc( const ScAddress& rOldPos, SCROW nLength, const ScTokenArray& rOldCode )
+ {
+ if (!mpUndoDoc || nLength <= 0)
+ return;
+
+ // Insert the old formula group into the undo document.
+ ScAddress aUndoPos = rOldPos;
+ ScFormulaCell* pFC = new ScFormulaCell(*mpUndoDoc, aUndoPos, rOldCode.Clone());
+
+ if (nLength == 1)
+ {
+ mpUndoDoc->SetFormulaCell(aUndoPos, pFC);
+ return;
+ }
+
+ std::vector<ScFormulaCell*> aCells;
+ aCells.reserve(nLength);
+ ScFormulaCellGroupRef xGroup = pFC->CreateCellGroup(nLength, false);
+ aCells.push_back(pFC);
+ aUndoPos.IncRow();
+ for (SCROW i = 1; i < nLength; ++i, aUndoPos.IncRow())
+ {
+ pFC = new ScFormulaCell(*mpUndoDoc, aUndoPos, xGroup);
+ aCells.push_back(pFC);
+ }
+
+ if (!mpUndoDoc->SetFormulaCells(rOldPos, aCells))
+ // Insertion failed. Delete all formula cells.
+ std::for_each(aCells.begin(), aCells.end(), std::default_delete<ScFormulaCell>());
+ }
+
+public:
+ UpdateRefOnNonCopy(
+ SCCOL nCol, SCTAB nTab, const sc::RefUpdateContext* pCxt,
+ ScDocument* pUndoDoc) :
+ mnCol(nCol), mnTab(nTab), mpCxt(pCxt),
+ mpUndoDoc(pUndoDoc), mbUpdated(false),
+ mbClipboardSource(pCxt->mrDoc.IsClipboardSource()){}
+
+ void operator() ( sc::FormulaGroupEntry& rGroup )
+ {
+ switch (mpCxt->meMode)
+ {
+ case URM_INSDEL:
+ updateRefOnShift(rGroup);
+ return;
+ case URM_MOVE:
+ updateRefOnMove(rGroup);
+ return;
+ default:
+ ;
+ }
+
+ if (rGroup.mbShared)
+ {
+ ScAddress aUndoPos(mnCol, rGroup.mnRow, mnTab);
+ ScFormulaCell** pp = rGroup.mpCells;
+ ScFormulaCell** ppEnd = pp + rGroup.mnLength;
+ for (; pp != ppEnd; ++pp, aUndoPos.IncRow())
+ {
+ ScFormulaCell* p = *pp;
+ mbUpdated |= p->UpdateReference(*mpCxt, mpUndoDoc, &aUndoPos);
+ }
+ }
+ else
+ {
+ ScAddress aUndoPos(mnCol, rGroup.mnRow, mnTab);
+ mbUpdated |= rGroup.mpCell->UpdateReference(*mpCxt, mpUndoDoc, &aUndoPos);
+ }
+ }
+
+ bool isUpdated() const { return mbUpdated; }
+};
+
+class UpdateRefGroupBoundChecker : public SharedTopFormulaCellPicker
+{
+ const sc::RefUpdateContext& mrCxt;
+ std::vector<SCROW>& mrBounds;
+
+public:
+ UpdateRefGroupBoundChecker(const sc::RefUpdateContext& rCxt, std::vector<SCROW>& rBounds) :
+ mrCxt(rCxt), mrBounds(rBounds) {}
+
+ virtual void processSharedTop( ScFormulaCell** ppCells, size_t /*nRow*/, size_t /*nLength*/ ) override
+ {
+ // Check its tokens and record its reference boundaries.
+ ScFormulaCell& rCell = **ppCells;
+ const ScTokenArray& rCode = *rCell.GetCode();
+ rCode.CheckRelativeReferenceBounds(
+ mrCxt, rCell.aPos, rCell.GetSharedLength(), mrBounds);
+ }
+};
+
+class UpdateRefExpandGroupBoundChecker : public SharedTopFormulaCellPicker
+{
+ const sc::RefUpdateContext& mrCxt;
+ std::vector<SCROW>& mrBounds;
+
+public:
+ UpdateRefExpandGroupBoundChecker(const sc::RefUpdateContext& rCxt, std::vector<SCROW>& rBounds) :
+ mrCxt(rCxt), mrBounds(rBounds) {}
+
+ virtual void processSharedTop( ScFormulaCell** ppCells, size_t /*nRow*/, size_t /*nLength*/ ) override
+ {
+ // Check its tokens and record its reference boundaries.
+ ScFormulaCell& rCell = **ppCells;
+ const ScTokenArray& rCode = *rCell.GetCode();
+ rCode.CheckExpandReferenceBounds(
+ mrCxt, rCell.aPos, rCell.GetSharedLength(), mrBounds);
+ }
+};
+
+class FormulaGroupPicker : public SharedTopFormulaCellPicker
+{
+ std::vector<sc::FormulaGroupEntry>& mrGroups;
+
+public:
+ explicit FormulaGroupPicker( std::vector<sc::FormulaGroupEntry>& rGroups ) : mrGroups(rGroups) {}
+
+ virtual void processNonShared( ScFormulaCell* pCell, size_t nRow ) override
+ {
+ mrGroups.emplace_back(pCell, nRow);
+ }
+
+ virtual void processSharedTop( ScFormulaCell** ppCells, size_t nRow, size_t nLength ) override
+ {
+ mrGroups.emplace_back(ppCells, nRow, nLength);
+ }
+};
+
+}
+
+bool ScColumn::UpdateReferenceOnCopy( sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc )
+{
+ // When copying, the range equals the destination range where cells
+ // are pasted, and the dx, dy, dz refer to the distance from the
+ // source range.
+
+ UpdateRefOnCopy aHandler(rCxt, pUndoDoc);
+ sc::ColumnBlockPosition* blockPos = rCxt.getBlockPosition(nTab, nCol);
+ sc::CellStoreType::position_type aPos = blockPos
+ ? maCells.position(blockPos->miCellPos, rCxt.maRange.aStart.Row())
+ : maCells.position(rCxt.maRange.aStart.Row());
+ sc::ProcessBlock(aPos.first, maCells, aHandler, rCxt.maRange.aStart.Row(), rCxt.maRange.aEnd.Row());
+
+ // The formula groups at the top and bottom boundaries are expected to
+ // have been split prior to this call. Here, we only do the joining.
+ sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
+ if (rCxt.maRange.aEnd.Row() < GetDoc().MaxRow())
+ {
+ aPos = maCells.position(aPos.first, rCxt.maRange.aEnd.Row()+1);
+ sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
+ }
+
+ return aHandler.isUpdated();
+}
+
+bool ScColumn::UpdateReference( sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc )
+{
+ if (IsEmptyData() || GetDoc().IsClipOrUndo())
+ // Cells in this column are all empty, or clip or undo doc. No update needed.
+ return false;
+
+ if (rCxt.meMode == URM_COPY)
+ return UpdateReferenceOnCopy(rCxt, pUndoDoc);
+
+ std::vector<SCROW> aBounds;
+
+ bool bThisColShifted = (rCxt.maRange.aStart.Tab() <= nTab && nTab <= rCxt.maRange.aEnd.Tab() &&
+ rCxt.maRange.aStart.Col() <= nCol && nCol <= rCxt.maRange.aEnd.Col());
+ if (bThisColShifted)
+ {
+ // Cells in this column is being shifted. Split formula grouping at
+ // the top and bottom boundaries before they get shifted.
+ // Also, for deleted rows split at the top of the deleted area to adapt
+ // the affected group length.
+ SCROW nSplitPos;
+ if (rCxt.mnRowDelta < 0)
+ {
+ nSplitPos = rCxt.maRange.aStart.Row() + rCxt.mnRowDelta;
+ if (GetDoc().ValidRow(nSplitPos))
+ aBounds.push_back(nSplitPos);
+ }
+ nSplitPos = rCxt.maRange.aStart.Row();
+ if (GetDoc().ValidRow(nSplitPos))
+ {
+ aBounds.push_back(nSplitPos);
+ nSplitPos = rCxt.maRange.aEnd.Row() + 1;
+ if (GetDoc().ValidRow(nSplitPos))
+ aBounds.push_back(nSplitPos);
+ }
+ }
+
+ // Check the row positions at which the group must be split per relative
+ // references.
+ UpdateRefGroupBoundChecker aBoundChecker(rCxt, aBounds);
+ std::for_each(maCells.begin(), maCells.end(), aBoundChecker);
+
+ // If expand reference edges is on, splitting groups may happen anywhere
+ // where a reference points to an adjacent row of the insertion.
+ if (rCxt.mnRowDelta > 0 && rCxt.mrDoc.IsExpandRefs())
+ {
+ UpdateRefExpandGroupBoundChecker aExpandChecker(rCxt, aBounds);
+ std::for_each(maCells.begin(), maCells.end(), aExpandChecker);
+ }
+
+ // Do the actual splitting.
+ const bool bSplit = sc::SharedFormulaUtil::splitFormulaCellGroups(GetDoc(), maCells, aBounds);
+
+ // Collect all formula groups.
+ std::vector<sc::FormulaGroupEntry> aGroups = GetFormulaGroupEntries();
+
+ // Process all collected formula groups.
+ UpdateRefOnNonCopy aHandler(nCol, nTab, &rCxt, pUndoDoc);
+ aHandler = std::for_each(aGroups.begin(), aGroups.end(), aHandler);
+ if (bSplit || aHandler.isUpdated())
+ rCxt.maRegroupCols.set(nTab, nCol);
+
+ return aHandler.isUpdated();
+}
+
+std::vector<sc::FormulaGroupEntry> ScColumn::GetFormulaGroupEntries()
+{
+ std::vector<sc::FormulaGroupEntry> aGroups;
+ std::for_each(maCells.begin(), maCells.end(), FormulaGroupPicker(aGroups));
+ return aGroups;
+}
+
+namespace {
+
+class UpdateTransHandler
+{
+ ScColumn& mrColumn;
+ sc::CellStoreType::iterator miPos;
+ ScRange maSource;
+ ScAddress maDest;
+ ScDocument* mpUndoDoc;
+public:
+ UpdateTransHandler(ScColumn& rColumn, const ScRange& rSource, const ScAddress& rDest, ScDocument* pUndoDoc) :
+ mrColumn(rColumn),
+ miPos(rColumn.GetCellStore().begin()),
+ maSource(rSource), maDest(rDest), mpUndoDoc(pUndoDoc) {}
+
+ void operator() (size_t nRow, ScFormulaCell* pCell)
+ {
+ sc::CellStoreType::position_type aPos = mrColumn.GetCellStore().position(miPos, nRow);
+ miPos = aPos.first;
+ sc::SharedFormulaUtil::unshareFormulaCell(aPos, *pCell);
+ pCell->UpdateTranspose(maSource, maDest, mpUndoDoc);
+ ScColumn::JoinNewFormulaCell(aPos, *pCell);
+ }
+};
+
+class UpdateGrowHandler
+{
+ ScColumn& mrColumn;
+ sc::CellStoreType::iterator miPos;
+ ScRange maArea;
+ SCCOL mnGrowX;
+ SCROW mnGrowY;
+public:
+ UpdateGrowHandler(ScColumn& rColumn, const ScRange& rArea, SCCOL nGrowX, SCROW nGrowY) :
+ mrColumn(rColumn),
+ miPos(rColumn.GetCellStore().begin()),
+ maArea(rArea), mnGrowX(nGrowX), mnGrowY(nGrowY) {}
+
+ void operator() (size_t nRow, ScFormulaCell* pCell)
+ {
+ sc::CellStoreType::position_type aPos = mrColumn.GetCellStore().position(miPos, nRow);
+ miPos = aPos.first;
+ sc::SharedFormulaUtil::unshareFormulaCell(aPos, *pCell);
+ pCell->UpdateGrow(maArea, mnGrowX, mnGrowY);
+ ScColumn::JoinNewFormulaCell(aPos, *pCell);
+ }
+};
+
+class InsertTabUpdater
+{
+ sc::RefUpdateInsertTabContext& mrCxt;
+ sc::CellTextAttrStoreType& mrTextAttrs;
+ sc::CellTextAttrStoreType::iterator miAttrPos;
+ SCTAB mnTab;
+ bool mbModified;
+
+public:
+ InsertTabUpdater(sc::RefUpdateInsertTabContext& rCxt, sc::CellTextAttrStoreType& rTextAttrs, SCTAB nTab) :
+ mrCxt(rCxt),
+ mrTextAttrs(rTextAttrs),
+ miAttrPos(rTextAttrs.begin()),
+ mnTab(nTab),
+ mbModified(false) {}
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+ pCell->UpdateInsertTab(mrCxt);
+ mbModified = true;
+ }
+
+ void operator() (size_t nRow, EditTextObject* pCell)
+ {
+ editeng::FieldUpdater aUpdater = pCell->GetFieldUpdater();
+ aUpdater.updateTableFields(mnTab);
+ miAttrPos = mrTextAttrs.set(miAttrPos, nRow, sc::CellTextAttr());
+ mbModified = true;
+ }
+
+ bool isModified() const { return mbModified; }
+};
+
+class DeleteTabUpdater
+{
+ sc::RefUpdateDeleteTabContext& mrCxt;
+ sc::CellTextAttrStoreType& mrTextAttrs;
+ sc::CellTextAttrStoreType::iterator miAttrPos;
+ SCTAB mnTab;
+ bool mbModified;
+public:
+ DeleteTabUpdater(sc::RefUpdateDeleteTabContext& rCxt, sc::CellTextAttrStoreType& rTextAttrs, SCTAB nTab) :
+ mrCxt(rCxt),
+ mrTextAttrs(rTextAttrs),
+ miAttrPos(rTextAttrs.begin()),
+ mnTab(nTab),
+ mbModified(false) {}
+
+ void operator() (size_t, ScFormulaCell* pCell)
+ {
+ pCell->UpdateDeleteTab(mrCxt);
+ mbModified = true;
+ }
+
+ void operator() (size_t nRow, EditTextObject* pCell)
+ {
+ editeng::FieldUpdater aUpdater = pCell->GetFieldUpdater();
+ aUpdater.updateTableFields(mnTab);
+ miAttrPos = mrTextAttrs.set(miAttrPos, nRow, sc::CellTextAttr());
+ mbModified = true;
+ }
+
+ bool isModified() const { return mbModified; }
+};
+
+class InsertAbsTabUpdater
+{
+ sc::CellTextAttrStoreType& mrTextAttrs;
+ sc::CellTextAttrStoreType::iterator miAttrPos;
+ SCTAB mnTab;
+ SCTAB mnNewPos;
+ bool mbModified;
+public:
+ InsertAbsTabUpdater(sc::CellTextAttrStoreType& rTextAttrs, SCTAB nTab, SCTAB nNewPos) :
+ mrTextAttrs(rTextAttrs),
+ miAttrPos(rTextAttrs.begin()),
+ mnTab(nTab),
+ mnNewPos(nNewPos),
+ mbModified(false) {}
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+ pCell->UpdateInsertTabAbs(mnNewPos);
+ mbModified = true;
+ }
+
+ void operator() (size_t nRow, EditTextObject* pCell)
+ {
+ editeng::FieldUpdater aUpdater = pCell->GetFieldUpdater();
+ aUpdater.updateTableFields(mnTab);
+ miAttrPos = mrTextAttrs.set(miAttrPos, nRow, sc::CellTextAttr());
+ mbModified = true;
+ }
+
+ bool isModified() const { return mbModified; }
+};
+
+class MoveTabUpdater
+{
+ sc::RefUpdateMoveTabContext& mrCxt;
+ sc::CellTextAttrStoreType& mrTextAttrs;
+ sc::CellTextAttrStoreType::iterator miAttrPos;
+ SCTAB mnTab;
+ bool mbModified;
+public:
+ MoveTabUpdater(sc::RefUpdateMoveTabContext& rCxt, sc::CellTextAttrStoreType& rTextAttrs, SCTAB nTab) :
+ mrCxt(rCxt),
+ mrTextAttrs(rTextAttrs),
+ miAttrPos(rTextAttrs.begin()),
+ mnTab(nTab),
+ mbModified(false) {}
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+ pCell->UpdateMoveTab(mrCxt, mnTab);
+ mbModified = true;
+ }
+
+ void operator() (size_t nRow, EditTextObject* pCell)
+ {
+ editeng::FieldUpdater aUpdater = pCell->GetFieldUpdater();
+ aUpdater.updateTableFields(mnTab);
+ miAttrPos = mrTextAttrs.set(miAttrPos, nRow, sc::CellTextAttr());
+ mbModified = true;
+ }
+
+ bool isModified() const { return mbModified; }
+};
+
+class UpdateCompileHandler
+{
+ bool mbForceIfNameInUse:1;
+public:
+ explicit UpdateCompileHandler(bool bForceIfNameInUse) :
+ mbForceIfNameInUse(bForceIfNameInUse) {}
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+ pCell->UpdateCompile(mbForceIfNameInUse);
+ }
+};
+
+class TabNoSetter
+{
+ SCTAB mnTab;
+public:
+ explicit TabNoSetter(SCTAB nTab) : mnTab(nTab) {}
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+ pCell->aPos.SetTab(mnTab);
+ }
+};
+
+class UsedRangeNameFinder
+{
+ sc::UpdatedRangeNames& mrIndexes;
+public:
+ explicit UsedRangeNameFinder(sc::UpdatedRangeNames& rIndexes) : mrIndexes(rIndexes) {}
+
+ void operator() (size_t /*nRow*/, const ScFormulaCell* pCell)
+ {
+ pCell->FindRangeNamesInUse(mrIndexes);
+ }
+};
+
+class CheckVectorizationHandler
+{
+public:
+ CheckVectorizationHandler()
+ {}
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* p)
+ {
+ ScTokenArray* pCode = p->GetCode();
+ if (pCode && pCode->IsFormulaVectorDisabled())
+ {
+ pCode->ResetVectorState();
+ FormulaTokenArrayPlainIterator aIter(*pCode);
+ FormulaToken* pFT = aIter.First();
+ while (pFT)
+ {
+ pCode->CheckToken(*pFT);
+ pFT = aIter.Next();
+ }
+ }
+ }
+};
+
+struct SetDirtyVarHandler
+{
+ void operator() (size_t /*nRow*/, ScFormulaCell* p)
+ {
+ p->SetDirtyVar();
+ }
+};
+
+class SetDirtyHandler
+{
+ ScDocument& mrDoc;
+ const sc::SetFormulaDirtyContext& mrCxt;
+public:
+ SetDirtyHandler( ScDocument& rDoc, const sc::SetFormulaDirtyContext& rCxt ) :
+ mrDoc(rDoc), mrCxt(rCxt) {}
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* p)
+ {
+ if (mrCxt.mbClearTabDeletedFlag)
+ {
+ if (!p->IsShared() || p->IsSharedTop())
+ {
+ ScTokenArray* pCode = p->GetCode();
+ pCode->ClearTabDeleted(
+ p->aPos, mrCxt.mnTabDeletedStart, mrCxt.mnTabDeletedEnd);
+ }
+ }
+
+ p->SetDirtyVar();
+ if (!mrDoc.IsInFormulaTree(p))
+ mrDoc.PutInFormulaTree(p);
+ }
+};
+
+class SetDirtyOnRangeHandler
+{
+ sc::SingleColumnSpanSet maValueRanges;
+ ScColumn& mrColumn;
+public:
+ explicit SetDirtyOnRangeHandler(ScColumn& rColumn)
+ : maValueRanges(rColumn.GetDoc().GetSheetLimits()),
+ mrColumn(rColumn) {}
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* p)
+ {
+ p->SetDirty();
+ }
+
+ void operator() (mdds::mtv::element_t type, size_t nTopRow, size_t nDataSize)
+ {
+ if (type == sc::element_type_empty)
+ // Ignore empty blocks.
+ return;
+
+ // Non-formula cells.
+ SCROW nRow1 = nTopRow;
+ SCROW nRow2 = nTopRow + nDataSize - 1;
+ maValueRanges.set(nRow1, nRow2, true);
+ }
+
+ void broadcast()
+ {
+ std::vector<SCROW> aRows;
+ maValueRanges.getRows(aRows);
+ mrColumn.BroadcastCells(aRows, SfxHintId::ScDataChanged);
+ }
+
+ void fillBroadcastSpans( sc::ColumnSpanSet& rBroadcastSpans ) const
+ {
+ SCCOL nCol = mrColumn.GetCol();
+ SCTAB nTab = mrColumn.GetTab();
+ sc::SingleColumnSpanSet::SpansType aSpans;
+ maValueRanges.getSpans(aSpans);
+
+ for (const auto& rSpan : aSpans)
+ rBroadcastSpans.set(mrColumn.GetDoc(), nTab, nCol, rSpan.mnRow1, rSpan.mnRow2, true);
+ }
+};
+
+class SetTableOpDirtyOnRangeHandler
+{
+ sc::SingleColumnSpanSet maValueRanges;
+ ScColumn& mrColumn;
+public:
+ explicit SetTableOpDirtyOnRangeHandler(ScColumn& rColumn)
+ : maValueRanges(rColumn.GetDoc().GetSheetLimits()),
+ mrColumn(rColumn) {}
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* p)
+ {
+ p->SetTableOpDirty();
+ }
+
+ void operator() (mdds::mtv::element_t type, size_t nTopRow, size_t nDataSize)
+ {
+ if (type == sc::element_type_empty)
+ // Ignore empty blocks.
+ return;
+
+ // Non-formula cells.
+ SCROW nRow1 = nTopRow;
+ SCROW nRow2 = nTopRow + nDataSize - 1;
+ maValueRanges.set(nRow1, nRow2, true);
+ }
+
+ void broadcast()
+ {
+ std::vector<SCROW> aRows;
+ maValueRanges.getRows(aRows);
+ mrColumn.BroadcastCells(aRows, SfxHintId::ScTableOpDirty);
+ }
+};
+
+struct SetDirtyAfterLoadHandler
+{
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+#if 1
+ // Simply set dirty and append to FormulaTree, without broadcasting,
+ // which is a magnitude faster. This is used to calculate the entire
+ // document, e.g. when loading alien file formats.
+ pCell->SetDirtyAfterLoad();
+#else
+/* This was used with the binary file format that stored results, where only
+ * newly compiled and volatile functions and their dependents had to be
+ * recalculated, which was faster then. Since that was moved to 'binfilter' to
+ * convert to an XML file this isn't needed anymore, and not used for other
+ * file formats. Kept for reference in case mechanism needs to be reactivated
+ * for some file formats, we'd have to introduce a controlling parameter to
+ * this method here then.
+*/
+
+ // If the cell was already dirty because of CalcAfterLoad,
+ // FormulaTracking has to take place.
+ if (pCell->GetDirty())
+ pCell->SetDirty();
+#endif
+ }
+};
+
+struct SetDirtyIfPostponedHandler
+{
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+ if (pCell->IsPostponedDirty() || (pCell->HasRelNameReference() != ScFormulaCell::RelNameRef::NONE))
+ pCell->SetDirty();
+ }
+};
+
+struct CalcAllHandler
+{
+#define DEBUG_SC_CHECK_FORMULATREE_CALCULATION 0
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+#if DEBUG_SC_CHECK_FORMULATREE_CALCULATION
+ // after F9 ctrl-F9: check the calculation for each FormulaTree
+ double nOldVal, nNewVal;
+ nOldVal = pCell->GetValue();
+#endif
+ pCell->Interpret();
+#if DEBUG_SC_CHECK_FORMULATREE_CALCULATION
+ if (pCell->GetCode()->IsRecalcModeNormal())
+ nNewVal = pCell->GetValue();
+ else
+ nNewVal = nOldVal; // random(), jetzt() etc.
+
+ assert(nOldVal == nNewVal);
+#endif
+ }
+#undef DEBUG_SC_CHECK_FORMULATREE_CALCULATION
+};
+
+class CompileAllHandler
+{
+ sc::CompileFormulaContext& mrCxt;
+public:
+ explicit CompileAllHandler( sc::CompileFormulaContext& rCxt ) : mrCxt(rCxt) {}
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+ // for unconditional compilation
+ // bCompile=true and pCode->nError=0
+ pCell->GetCode()->SetCodeError(FormulaError::NONE);
+ pCell->SetCompile(true);
+ pCell->CompileTokenArray(mrCxt);
+ }
+};
+
+class CompileXMLHandler
+{
+ sc::CompileFormulaContext& mrCxt;
+ ScProgress& mrProgress;
+ const ScColumn& mrCol;
+public:
+ CompileXMLHandler( sc::CompileFormulaContext& rCxt, ScProgress& rProgress, const ScColumn& rCol) :
+ mrCxt(rCxt),
+ mrProgress(rProgress),
+ mrCol(rCol) {}
+
+ void operator() (size_t nRow, ScFormulaCell* pCell)
+ {
+ sal_uInt32 nFormat = mrCol.GetNumberFormat(mrCol.GetDoc().GetNonThreadedContext(), nRow);
+ if( (nFormat % SV_COUNTRY_LANGUAGE_OFFSET) != 0)
+ // Non-default number format is set.
+ pCell->SetNeedNumberFormat(false);
+ else if (pCell->NeedsNumberFormat())
+ pCell->SetDirtyVar();
+
+ if (pCell->GetMatrixFlag() != ScMatrixMode::NONE)
+ pCell->SetDirtyVar();
+
+ pCell->CompileXML(mrCxt, mrProgress);
+ }
+};
+
+class CompileErrorCellsHandler
+{
+ sc::CompileFormulaContext& mrCxt;
+ ScColumn& mrColumn;
+ sc::CellStoreType::iterator miPos;
+ FormulaError mnErrCode;
+ bool mbCompiled;
+public:
+ CompileErrorCellsHandler( sc::CompileFormulaContext& rCxt, ScColumn& rColumn, FormulaError nErrCode ) :
+ mrCxt(rCxt),
+ mrColumn(rColumn),
+ miPos(mrColumn.GetCellStore().begin()),
+ mnErrCode(nErrCode),
+ mbCompiled(false)
+ {
+ }
+
+ void operator() (size_t nRow, ScFormulaCell* pCell)
+ {
+ FormulaError nCurError = pCell->GetRawError();
+ if (nCurError == FormulaError::NONE)
+ // It's not an error cell. Skip it.
+ return;
+
+ if (mnErrCode != FormulaError::NONE && nCurError != mnErrCode)
+ // Error code is specified, and it doesn't match. Skip it.
+ return;
+
+ sc::CellStoreType::position_type aPos = mrColumn.GetCellStore().position(miPos, nRow);
+ miPos = aPos.first;
+ sc::SharedFormulaUtil::unshareFormulaCell(aPos, *pCell);
+ pCell->GetCode()->SetCodeError(FormulaError::NONE);
+ OUString aFormula = pCell->GetFormula(mrCxt);
+ pCell->Compile(mrCxt, aFormula);
+ ScColumn::JoinNewFormulaCell(aPos, *pCell);
+
+ mbCompiled = true;
+ }
+
+ bool isCompiled() const { return mbCompiled; }
+};
+
+class CalcAfterLoadHandler
+{
+ sc::CompileFormulaContext& mrCxt;
+ bool mbStartListening;
+
+public:
+ CalcAfterLoadHandler( sc::CompileFormulaContext& rCxt, bool bStartListening ) :
+ mrCxt(rCxt), mbStartListening(bStartListening) {}
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+ pCell->CalcAfterLoad(mrCxt, mbStartListening);
+ }
+};
+
+struct ResetChangedHandler
+{
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+ pCell->SetChanged(false);
+ }
+};
+
+/**
+ * Ambiguous script type counts as edit cell.
+ */
+class FindEditCellsHandler
+{
+ ScColumn& mrColumn;
+ sc::CellTextAttrStoreType::iterator miAttrPos;
+ sc::CellStoreType::iterator miCellPos;
+
+public:
+ explicit FindEditCellsHandler(ScColumn& rCol) :
+ mrColumn(rCol),
+ miAttrPos(rCol.GetCellAttrStore().begin()),
+ miCellPos(rCol.GetCellStore().begin()) {}
+
+ bool operator() (size_t, const EditTextObject*)
+ {
+ // This is definitely an edit text cell.
+ return true;
+ }
+
+ bool operator() (size_t nRow, const ScFormulaCell* p)
+ {
+ // With a formula cell, it's considered an edit text cell when either
+ // the result is multi-line or it has more than one script types.
+ SvtScriptType nScriptType = mrColumn.GetRangeScriptType(miAttrPos, nRow, nRow, miCellPos);
+ if (IsAmbiguousScriptNonZero(nScriptType))
+ return true;
+
+ return const_cast<ScFormulaCell*>(p)->IsMultilineResult();
+ }
+
+ /**
+ * Callback for a block of other types.
+ */
+ std::pair<size_t,bool> operator() (const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize)
+ {
+ typedef std::pair<size_t,bool> RetType;
+
+ if (node.type == sc::element_type_empty)
+ // Ignore empty blocks.
+ return RetType(0, false);
+
+ // Check the script type of a non-empty element and see if it has
+ // multiple script types.
+ for (size_t i = 0; i < nDataSize; ++i)
+ {
+ SCROW nRow = node.position + i + nOffset;
+ SvtScriptType nScriptType = mrColumn.GetRangeScriptType(miAttrPos, nRow, nRow, miCellPos);
+ if (IsAmbiguousScriptNonZero(nScriptType))
+ // Return the offset from the first row.
+ return RetType(i+nOffset, true);
+ }
+
+ // No edit text cell found.
+ return RetType(0, false);
+ }
+};
+
+}
+
+void ScColumn::UpdateTranspose( const ScRange& rSource, const ScAddress& rDest,
+ ScDocument* pUndoDoc )
+{
+ UpdateTransHandler aFunc(*this, rSource, rDest, pUndoDoc);
+ sc::ProcessFormula(maCells, aFunc);
+}
+
+void ScColumn::UpdateGrow( const ScRange& rArea, SCCOL nGrowX, SCROW nGrowY )
+{
+ UpdateGrowHandler aFunc(*this, rArea, nGrowX, nGrowY);
+ sc::ProcessFormula(maCells, aFunc);
+}
+
+void ScColumn::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt )
+{
+ if (nTab >= rCxt.mnInsertPos)
+ {
+ nTab += rCxt.mnSheets;
+ pAttrArray->SetTab(nTab);
+ }
+
+ UpdateInsertTabOnlyCells(rCxt);
+}
+
+void ScColumn::UpdateInsertTabOnlyCells( sc::RefUpdateInsertTabContext& rCxt )
+{
+ InsertTabUpdater aFunc(rCxt, maCellTextAttrs, nTab);
+ sc::ProcessFormulaEditText(maCells, aFunc);
+ if (aFunc.isModified())
+ CellStorageModified();
+}
+
+void ScColumn::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt )
+{
+ if (nTab > rCxt.mnDeletePos)
+ {
+ nTab -= rCxt.mnSheets;
+ pAttrArray->SetTab(nTab);
+ }
+
+ DeleteTabUpdater aFunc(rCxt, maCellTextAttrs, nTab);
+ sc::ProcessFormulaEditText(maCells, aFunc);
+ if (aFunc.isModified())
+ CellStorageModified();
+}
+
+void ScColumn::UpdateInsertTabAbs(SCTAB nNewPos)
+{
+ InsertAbsTabUpdater aFunc(maCellTextAttrs, nTab, nNewPos);
+ sc::ProcessFormulaEditText(maCells, aFunc);
+ if (aFunc.isModified())
+ CellStorageModified();
+}
+
+void ScColumn::UpdateMoveTab( sc::RefUpdateMoveTabContext& rCxt, SCTAB nTabNo )
+{
+ nTab = nTabNo;
+ pAttrArray->SetTab( nTabNo );
+
+ MoveTabUpdater aFunc(rCxt, maCellTextAttrs, nTab);
+ sc::ProcessFormulaEditText(maCells, aFunc);
+ if (aFunc.isModified())
+ CellStorageModified();
+}
+
+void ScColumn::UpdateCompile( bool bForceIfNameInUse )
+{
+ UpdateCompileHandler aFunc(bForceIfNameInUse);
+ sc::ProcessFormula(maCells, aFunc);
+}
+
+void ScColumn::SetTabNo(SCTAB nNewTab)
+{
+ nTab = nNewTab;
+ pAttrArray->SetTab( nNewTab );
+
+ TabNoSetter aFunc(nTab);
+ sc::ProcessFormula(maCells, aFunc);
+}
+
+void ScColumn::FindRangeNamesInUse(SCROW nRow1, SCROW nRow2, sc::UpdatedRangeNames& rIndexes) const
+{
+ UsedRangeNameFinder aFunc(rIndexes);
+ sc::ParseFormula(maCells.begin(), maCells, nRow1, nRow2, aFunc);
+}
+
+void ScColumn::SetDirtyVar()
+{
+ SetDirtyVarHandler aFunc;
+ sc::ProcessFormula(maCells, aFunc);
+}
+
+bool ScColumn::IsFormulaDirty( SCROW nRow ) const
+{
+ if (!GetDoc().ValidRow(nRow))
+ return false;
+
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it->type != sc::element_type_formula)
+ // This is not a formula cell block.
+ return false;
+
+ const ScFormulaCell* p = sc::formula_block::at(*it->data, aPos.second);
+ return p->GetDirty();
+}
+
+void ScColumn::CheckVectorizationState()
+{
+ sc::AutoCalcSwitch aSwitch(GetDoc(), false);
+ CheckVectorizationHandler aFunc;
+ sc::ProcessFormula(maCells, aFunc);
+}
+
+void ScColumn::SetAllFormulasDirty( const sc::SetFormulaDirtyContext& rCxt )
+{
+ // is only done documentwide, no FormulaTracking
+ sc::AutoCalcSwitch aSwitch(GetDoc(), false);
+ SetDirtyHandler aFunc(GetDoc(), rCxt);
+ sc::ProcessFormula(maCells, aFunc);
+}
+
+void ScColumn::SetDirtyFromClip( SCROW nRow1, SCROW nRow2, sc::ColumnSpanSet& rBroadcastSpans )
+{
+ // Set all formula cells in the range dirty, and pick up all non-formula
+ // cells for later broadcasting. We don't broadcast here.
+ sc::AutoCalcSwitch aSwitch(GetDoc(), false);
+
+ SetDirtyOnRangeHandler aHdl(*this);
+ sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aHdl, aHdl);
+ aHdl.fillBroadcastSpans(rBroadcastSpans);
+}
+
+namespace {
+
+class BroadcastBroadcastersHandler
+{
+ ScHint maHint;
+ bool mbBroadcasted;
+
+public:
+ explicit BroadcastBroadcastersHandler( SfxHintId nHint, SCTAB nTab, SCCOL nCol )
+ : maHint(nHint, ScAddress(nCol, 0, nTab))
+ , mbBroadcasted(false)
+ {
+ }
+
+ void operator() ( size_t nRow, SvtBroadcaster* pBroadcaster )
+ {
+ maHint.SetAddressRow(nRow);
+ pBroadcaster->Broadcast(maHint);
+ mbBroadcasted = true;
+ }
+
+ bool wasBroadcasted() { return mbBroadcasted; }
+};
+
+}
+
+bool ScColumn::BroadcastBroadcasters( SCROW nRow1, SCROW nRow2, SfxHintId nHint )
+{
+ BroadcastBroadcastersHandler aBroadcasterHdl(nHint, nTab, nCol);
+ sc::ProcessBroadcaster(maBroadcasters.begin(), maBroadcasters, nRow1, nRow2, aBroadcasterHdl);
+ return aBroadcasterHdl.wasBroadcasted();
+}
+
+void ScColumn::SetDirty( SCROW nRow1, SCROW nRow2, BroadcastMode eMode )
+{
+ // broadcasts everything within the range, with FormulaTracking
+ sc::AutoCalcSwitch aSwitch(GetDoc(), false);
+
+ switch (eMode)
+ {
+ case BROADCAST_NONE:
+ {
+ // Handler only used with formula cells.
+ SetDirtyOnRangeHandler aHdl(*this);
+ sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aHdl);
+ }
+ break;
+ case BROADCAST_DATA_POSITIONS:
+ {
+ // Handler used with both, formula and non-formula cells.
+ SetDirtyOnRangeHandler aHdl(*this);
+ sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aHdl, aHdl);
+ aHdl.broadcast();
+ }
+ break;
+ case BROADCAST_BROADCASTERS:
+ {
+ // Handler only used with formula cells.
+ SetDirtyOnRangeHandler aHdl(*this);
+ sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aHdl);
+ // Broadcast all broadcasters in range.
+ if (BroadcastBroadcasters( nRow1, nRow2, SfxHintId::ScDataChanged))
+ {
+ // SetDirtyOnRangeHandler implicitly tracks notified
+ // formulas via ScDocument::Broadcast(), which
+ // BroadcastBroadcastersHandler doesn't, so explicitly
+ // track them here.
+ GetDoc().TrackFormulas();
+ }
+ }
+ break;
+ }
+}
+
+void ScColumn::SetTableOpDirty( const ScRange& rRange )
+{
+ sc::AutoCalcSwitch aSwitch(GetDoc(), false);
+
+ SCROW nRow1 = rRange.aStart.Row(), nRow2 = rRange.aEnd.Row();
+ SetTableOpDirtyOnRangeHandler aHdl(*this);
+ sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aHdl, aHdl);
+ aHdl.broadcast();
+}
+
+void ScColumn::SetDirtyAfterLoad()
+{
+ sc::AutoCalcSwitch aSwitch(GetDoc(), false);
+ SetDirtyAfterLoadHandler aFunc;
+ sc::ProcessFormula(maCells, aFunc);
+}
+
+namespace {
+
+class RecalcOnRefMoveCollector
+{
+ std::vector<SCROW> maDirtyRows;
+public:
+ void operator() (size_t nRow, ScFormulaCell* pCell)
+ {
+ if (pCell->GetDirty() && pCell->GetCode()->IsRecalcModeOnRefMove())
+ maDirtyRows.push_back(nRow);
+ }
+
+ const std::vector<SCROW>& getDirtyRows() const
+ {
+ return maDirtyRows;
+ }
+};
+
+}
+
+void ScColumn::SetDirtyIfPostponed()
+{
+ sc::AutoCalcSwitch aSwitch(GetDoc(), false);
+ SetDirtyIfPostponedHandler aFunc;
+ ScBulkBroadcast aBulkBroadcast( GetDoc().GetBASM(), SfxHintId::ScDataChanged);
+ sc::ProcessFormula(maCells, aFunc);
+}
+
+void ScColumn::BroadcastRecalcOnRefMove()
+{
+ sc::AutoCalcSwitch aSwitch(GetDoc(), false);
+ RecalcOnRefMoveCollector aFunc;
+ sc::ProcessFormula(maCells, aFunc);
+ BroadcastCells(aFunc.getDirtyRows(), SfxHintId::ScDataChanged);
+}
+
+void ScColumn::CalcAll()
+{
+ CalcAllHandler aFunc;
+ sc::ProcessFormula(maCells, aFunc);
+}
+
+void ScColumn::CompileAll( sc::CompileFormulaContext& rCxt )
+{
+ CompileAllHandler aFunc(rCxt);
+ sc::ProcessFormula(maCells, aFunc);
+}
+
+void ScColumn::CompileXML( sc::CompileFormulaContext& rCxt, ScProgress& rProgress )
+{
+ CompileXMLHandler aFunc(rCxt, rProgress, *this);
+ sc::ProcessFormula(maCells, aFunc);
+ RegroupFormulaCells();
+}
+
+bool ScColumn::CompileErrorCells( sc::CompileFormulaContext& rCxt, FormulaError nErrCode )
+{
+ CompileErrorCellsHandler aHdl(rCxt, *this, nErrCode);
+ sc::ProcessFormula(maCells, aHdl);
+ return aHdl.isCompiled();
+}
+
+void ScColumn::CalcAfterLoad( sc::CompileFormulaContext& rCxt, bool bStartListening )
+{
+ CalcAfterLoadHandler aFunc(rCxt, bStartListening);
+ sc::ProcessFormula(maCells, aFunc);
+}
+
+void ScColumn::ResetChanged( SCROW nStartRow, SCROW nEndRow )
+{
+ ResetChangedHandler aFunc;
+ sc::ProcessFormula(maCells.begin(), maCells, nStartRow, nEndRow, aFunc);
+}
+
+bool ScColumn::HasEditCells(SCROW nStartRow, SCROW nEndRow, SCROW& rFirst)
+{
+ // used in GetOptimalHeight - ambiguous script type counts as edit cell
+
+ FindEditCellsHandler aFunc(*this);
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos =
+ sc::FindFormulaEditText(maCells, nStartRow, nEndRow, aFunc);
+
+ if (aPos.first == maCells.end())
+ return false;
+
+ rFirst = aPos.first->position + aPos.second;
+ return true;
+}
+
+SCROW ScColumn::SearchStyle(
+ SCROW nRow, const ScStyleSheet* pSearchStyle, bool bUp, bool bInSelection,
+ const ScMarkData& rMark) const
+{
+ if (bInSelection)
+ {
+ if (rMark.IsMultiMarked())
+ {
+ ScMarkArray aArray(rMark.GetMarkArray(nCol));
+ return pAttrArray->SearchStyle(nRow, pSearchStyle, bUp, &aArray);
+ }
+ else
+ return -1;
+ }
+ else
+ return pAttrArray->SearchStyle( nRow, pSearchStyle, bUp );
+}
+
+bool ScColumn::SearchStyleRange(
+ SCROW& rRow, SCROW& rEndRow, const ScStyleSheet* pSearchStyle, bool bUp,
+ bool bInSelection, const ScMarkData& rMark) const
+{
+ if (bInSelection)
+ {
+ if (rMark.IsMultiMarked())
+ {
+ ScMarkArray aArray(rMark.GetMarkArray(nCol));
+ return pAttrArray->SearchStyleRange(
+ rRow, rEndRow, pSearchStyle, bUp, &aArray);
+ }
+ else
+ return false;
+ }
+ else
+ return pAttrArray->SearchStyleRange( rRow, rEndRow, pSearchStyle, bUp );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/column2.cxx b/sc/source/core/data/column2.cxx
new file mode 100644
index 000000000..b7922d740
--- /dev/null
+++ b/sc/source/core/data/column2.cxx
@@ -0,0 +1,3793 @@
+/* -*- 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/.
+ *
+ * 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 <column.hxx>
+#include <docsh.hxx>
+#include <scitems.hxx>
+#include <formulacell.hxx>
+#include <document.hxx>
+#include <drwlayer.hxx>
+#include <attarray.hxx>
+#include <patattr.hxx>
+#include <cellform.hxx>
+#include <editutil.hxx>
+#include <subtotal.hxx>
+#include <markdata.hxx>
+#include <fillinfo.hxx>
+#include <segmenttree.hxx>
+#include <docparam.hxx>
+#include <cellvalue.hxx>
+#include <tokenarray.hxx>
+#include <formulagroup.hxx>
+#include <listenercontext.hxx>
+#include <mtvcellfunc.hxx>
+#include <progress.hxx>
+#include <scmatrix.hxx>
+#include <rowheightcontext.hxx>
+#include <tokenstringcontext.hxx>
+#include <sortparam.hxx>
+#include <SparklineGroup.hxx>
+#include <SparklineList.hxx>
+
+#include <editeng/eeitem.hxx>
+#include <o3tl/safeint.hxx>
+#include <o3tl/unit_conversion.hxx>
+#include <svx/algitem.hxx>
+#include <editeng/editobj.hxx>
+#include <editeng/editstat.hxx>
+#include <editeng/emphasismarkitem.hxx>
+#include <editeng/fhgtitem.hxx>
+#include <svx/rotmodit.hxx>
+#include <editeng/unolingu.hxx>
+#include <editeng/justifyitem.hxx>
+#include <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+#include <svl/broadcast.hxx>
+#include <vcl/outdev.hxx>
+#include <formula/errorcodes.hxx>
+#include <formula/vectortoken.hxx>
+
+#include <algorithm>
+#include <limits>
+#include <memory>
+#include <numeric>
+
+// factor from font size to optimal cell height (text width)
+#define SC_ROT_BREAK_FACTOR 6
+
+static bool IsAmbiguousScript( SvtScriptType nScript )
+{
+ //TODO: move to a header file
+ return ( nScript != SvtScriptType::LATIN &&
+ nScript != SvtScriptType::ASIAN &&
+ nScript != SvtScriptType::COMPLEX );
+}
+
+// Data operations
+
+tools::Long ScColumn::GetNeededSize(
+ SCROW nRow, OutputDevice* pDev, double nPPTX, double nPPTY,
+ const Fraction& rZoomX, const Fraction& rZoomY,
+ bool bWidth, const ScNeededSizeOptions& rOptions,
+ const ScPatternAttr** ppPatternChange, bool bInPrintTwips ) const
+{
+ // If bInPrintTwips is set, the size calculated should be in print twips,
+ // else it should be in pixels.
+
+ // Switch unit to MapTwip instead ? (temporarily and then revert before exit).
+ if (bInPrintTwips)
+ assert(pDev->GetMapMode().GetMapUnit() == MapUnit::MapTwip);
+
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it == maCells.end() || it->type == sc::element_type_empty)
+ // Empty cell, or invalid row.
+ return 0;
+
+ tools::Long nValue = 0;
+ ScRefCellValue aCell = GetCellValue(it, aPos.second);
+ double nPPT = bWidth ? nPPTX : nPPTY;
+
+ auto conditionalScaleFunc = [bInPrintTwips](tools::Long nMeasure, double fScale) {
+ return bInPrintTwips ? nMeasure : static_cast<tools::Long>(nMeasure * fScale);
+ };
+
+ const ScPatternAttr* pPattern = rOptions.pPattern;
+ if (!pPattern)
+ pPattern = pAttrArray->GetPattern( nRow );
+
+ // merged?
+ // Do not merge in conditional formatting
+
+ const ScMergeAttr* pMerge = &pPattern->GetItem(ATTR_MERGE);
+ const ScMergeFlagAttr* pFlag = &pPattern->GetItem(ATTR_MERGE_FLAG);
+
+ if ( bWidth )
+ {
+ if ( pFlag->IsHorOverlapped() )
+ return 0;
+ if ( rOptions.bSkipMerged && pMerge->GetColMerge() > 1 )
+ return 0;
+ }
+ else
+ {
+ if ( pFlag->IsVerOverlapped() )
+ return 0;
+ if ( rOptions.bSkipMerged && pMerge->GetRowMerge() > 1 )
+ return 0;
+ }
+
+ // conditional formatting
+ ScDocument& rDocument = GetDoc();
+ const SfxItemSet* pCondSet = rDocument.GetCondResult( nCol, nRow, nTab );
+
+ //The pPattern may change in GetCondResult
+ if (aCell.meType == CELLTYPE_FORMULA)
+ {
+ pPattern = pAttrArray->GetPattern( nRow );
+ if (ppPatternChange)
+ *ppPatternChange = pPattern;
+ }
+ // line break?
+
+ const SvxHorJustifyItem* pCondItem;
+ SvxCellHorJustify eHorJust;
+ if (pCondSet && (pCondItem = pCondSet->GetItemIfSet(ATTR_HOR_JUSTIFY)) )
+ eHorJust = pCondItem->GetValue();
+ else
+ eHorJust = pPattern->GetItem( ATTR_HOR_JUSTIFY ).GetValue();
+ bool bBreak;
+ const ScLineBreakCell* pLineBreakCell;
+ if ( eHorJust == SvxCellHorJustify::Block )
+ bBreak = true;
+ else if ( pCondSet && (pLineBreakCell = pCondSet->GetItemIfSet(ATTR_LINEBREAK)) )
+ bBreak = pLineBreakCell->GetValue();
+ else
+ bBreak = pPattern->GetItem(ATTR_LINEBREAK).GetValue();
+
+ SvNumberFormatter* pFormatter = rDocument.GetFormatTable();
+ sal_uInt32 nFormat = pPattern->GetNumberFormat( pFormatter, pCondSet );
+
+ // get "cell is value" flag
+ // Must be synchronized with ScOutputData::LayoutStrings()
+ bool bCellIsValue = (aCell.meType == CELLTYPE_VALUE);
+ if (aCell.meType == CELLTYPE_FORMULA)
+ {
+ ScFormulaCell* pFCell = aCell.mpFormula;
+ bCellIsValue = pFCell->IsRunning() || pFCell->IsValue();
+ }
+
+ // #i111387#, tdf#121040: disable automatic line breaks for all number formats
+ if (bBreak && bCellIsValue && (pFormatter->GetType(nFormat) == SvNumFormatType::NUMBER))
+ {
+ // If a formula cell needs to be interpreted during aCell.hasNumeric()
+ // to determine the type, the pattern may get invalidated because the
+ // result may set a number format. In which case there's also the
+ // General format not set anymore...
+ bool bMayInvalidatePattern = (aCell.meType == CELLTYPE_FORMULA);
+ const ScPatternAttr* pOldPattern = pPattern;
+ bool bNumeric = aCell.hasNumeric();
+ if (bMayInvalidatePattern)
+ {
+ pPattern = pAttrArray->GetPattern( nRow );
+ if (ppPatternChange)
+ *ppPatternChange = pPattern; // XXX caller may have to check for change!
+ }
+ if (bNumeric)
+ {
+ if (!bMayInvalidatePattern || pPattern == pOldPattern)
+ bBreak = false;
+ else
+ {
+ nFormat = pPattern->GetNumberFormat( pFormatter, pCondSet );
+ if (pFormatter->GetType(nFormat) == SvNumFormatType::NUMBER)
+ bBreak = false;
+ }
+ }
+ }
+
+ // get other attributes from pattern and conditional formatting
+
+ SvxCellOrientation eOrient = pPattern->GetCellOrientation( pCondSet );
+ bool bAsianVertical = ( eOrient == SvxCellOrientation::Stacked &&
+ pPattern->GetItem( ATTR_VERTICAL_ASIAN, pCondSet ).GetValue() );
+ if ( bAsianVertical )
+ bBreak = false;
+
+ if ( bWidth && bBreak ) // after determining bAsianVertical (bBreak may be reset)
+ return 0;
+
+ Degree100 nRotate(0);
+ SvxRotateMode eRotMode = SVX_ROTATE_MODE_STANDARD;
+ if ( eOrient == SvxCellOrientation::Standard )
+ {
+ const ScRotateValueItem* pRotateValueItem;
+ if (pCondSet &&
+ (pRotateValueItem = pCondSet->GetItemIfSet(ATTR_ROTATE_VALUE)) )
+ nRotate = pRotateValueItem->GetValue();
+ else
+ nRotate = pPattern->GetItem(ATTR_ROTATE_VALUE).GetValue();
+ if ( nRotate )
+ {
+ const SvxRotateModeItem* pRotateModeItem;
+ if (pCondSet &&
+ (pRotateModeItem = pCondSet->GetItemIfSet(ATTR_ROTATE_MODE)) )
+ eRotMode = pRotateModeItem->GetValue();
+ else
+ eRotMode = pPattern->GetItem(ATTR_ROTATE_MODE).GetValue();
+
+ if ( nRotate == 18000_deg100 )
+ eRotMode = SVX_ROTATE_MODE_STANDARD; // no overflow
+ }
+ }
+
+ if ( eHorJust == SvxCellHorJustify::Repeat )
+ {
+ // ignore orientation/rotation if "repeat" is active
+ eOrient = SvxCellOrientation::Standard;
+ nRotate = 0_deg100;
+ bAsianVertical = false;
+ }
+
+ const SvxMarginItem* pMargin;
+ if (pCondSet &&
+ (pMargin = pCondSet->GetItemIfSet(ATTR_MARGIN)) )
+ ;
+ else
+ pMargin = &pPattern->GetItem(ATTR_MARGIN);
+ sal_uInt16 nIndent = 0;
+ if ( eHorJust == SvxCellHorJustify::Left )
+ {
+ const ScIndentItem* pIndentItem;
+ if (pCondSet &&
+ (pIndentItem = pCondSet->GetItemIfSet(ATTR_INDENT)) )
+ nIndent = pIndentItem->GetValue();
+ else
+ nIndent = pPattern->GetItem(ATTR_INDENT).GetValue();
+ }
+
+ SvtScriptType nScript = rDocument.GetScriptType(nCol, nRow, nTab);
+ if (nScript == SvtScriptType::NONE) nScript = ScGlobal::GetDefaultScriptType();
+
+ // also call SetFont for edit cells, because bGetFont may be set only once
+ // bGetFont is set also if script type changes
+ if (rOptions.bGetFont)
+ {
+ Fraction aFontZoom = ( eOrient == SvxCellOrientation::Standard ) ? rZoomX : rZoomY;
+ vcl::Font aFont;
+ // font color doesn't matter here
+ pPattern->GetFont( aFont, SC_AUTOCOL_BLACK, pDev, &aFontZoom, pCondSet, nScript );
+ pDev->SetFont(aFont);
+ }
+
+ bool bAddMargin = true;
+ CellType eCellType = aCell.meType;
+
+ bool bEditEngine = (eCellType == CELLTYPE_EDIT ||
+ eOrient == SvxCellOrientation::Stacked ||
+ IsAmbiguousScript(nScript) ||
+ ((eCellType == CELLTYPE_FORMULA) && aCell.mpFormula->IsMultilineResult()));
+
+ if (!bEditEngine) // direct output
+ {
+ const Color* pColor;
+ OUString aValStr = ScCellFormat::GetString(
+ aCell, nFormat, &pColor, *pFormatter, rDocument, true, rOptions.bFormula);
+
+ if (!aValStr.isEmpty())
+ {
+ // SetFont is moved up
+
+ Size aSize( pDev->GetTextWidth( aValStr ), pDev->GetTextHeight() );
+ if ( eOrient != SvxCellOrientation::Standard )
+ {
+ tools::Long nTemp = aSize.Width();
+ aSize.setWidth( aSize.Height() );
+ aSize.setHeight( nTemp );
+ }
+ else if ( nRotate )
+ {
+ //TODO: take different X/Y scaling into consideration
+
+ double nRealOrient = toRadians(nRotate);
+ double nCosAbs = fabs( cos( nRealOrient ) );
+ double nSinAbs = fabs( sin( nRealOrient ) );
+ tools::Long nHeight = static_cast<tools::Long>( aSize.Height() * nCosAbs + aSize.Width() * nSinAbs );
+ tools::Long nWidth;
+ if ( eRotMode == SVX_ROTATE_MODE_STANDARD )
+ nWidth = static_cast<tools::Long>( aSize.Width() * nCosAbs + aSize.Height() * nSinAbs );
+ else if ( rOptions.bTotalSize )
+ {
+ nWidth = conditionalScaleFunc(rDocument.GetColWidth( nCol,nTab ), nPPT);
+ bAddMargin = false;
+ // only to the right:
+ //TODO: differ on direction up/down (only Text/whole height)
+ if ( pPattern->GetRotateDir( pCondSet ) == ScRotateDir::Right )
+ nWidth += static_cast<tools::Long>( rDocument.GetRowHeight( nRow,nTab ) *
+ (bInPrintTwips ? 1.0 : nPPT) * nCosAbs / nSinAbs );
+ }
+ else
+ nWidth = static_cast<tools::Long>( aSize.Height() / nSinAbs ); //TODO: limit?
+
+ if ( bBreak && !rOptions.bTotalSize )
+ {
+ // limit size for line break
+ tools::Long nCmp = pDev->GetFont().GetFontSize().Height() * SC_ROT_BREAK_FACTOR;
+ if ( nHeight > nCmp )
+ nHeight = nCmp;
+ }
+
+ aSize = Size( nWidth, nHeight );
+ }
+ nValue = bWidth ? aSize.Width() : aSize.Height();
+
+ if ( bAddMargin )
+ {
+ if (bWidth)
+ {
+ nValue += conditionalScaleFunc(pMargin->GetLeftMargin(), nPPT) +
+ conditionalScaleFunc(pMargin->GetRightMargin(), nPPT);
+ if ( nIndent )
+ nValue += conditionalScaleFunc(nIndent, nPPT);
+ }
+ else
+ nValue += conditionalScaleFunc(pMargin->GetTopMargin(), nPPT) +
+ conditionalScaleFunc(pMargin->GetBottomMargin(), nPPT);
+ }
+
+ // linebreak done ?
+
+ if ( bBreak && !bWidth )
+ {
+ // test with EditEngine the safety at 90%
+ // (due to rounding errors and because EditEngine formats partially differently)
+
+ tools::Long nDocSize = conditionalScaleFunc((rDocument.GetColWidth( nCol,nTab ) -
+ pMargin->GetLeftMargin() - pMargin->GetRightMargin() -
+ nIndent), nPPTX);
+ nDocSize = (nDocSize * 9) / 10; // for safety
+ if ( aSize.Width() > nDocSize )
+ bEditEngine = true;
+ }
+ }
+ }
+
+ if (bEditEngine)
+ {
+ // the font is not reset each time with !bEditEngine
+ vcl::Font aOldFont = pDev->GetFont();
+
+ MapMode aHMMMode( MapUnit::Map100thMM, Point(), rZoomX, rZoomY );
+
+ // save in document ?
+ std::unique_ptr<ScFieldEditEngine> pEngine = rDocument.CreateFieldEditEngine();
+
+ const bool bPrevUpdateLayout = pEngine->SetUpdateLayout( false );
+ bool bTextWysiwyg = ( pDev->GetOutDevType() == OUTDEV_PRINTER );
+ EEControlBits nCtrl = pEngine->GetControlWord();
+ if ( bTextWysiwyg )
+ nCtrl |= EEControlBits::FORMAT100;
+ else
+ nCtrl &= ~EEControlBits::FORMAT100;
+ pEngine->SetControlWord( nCtrl );
+ MapMode aOld = pDev->GetMapMode();
+ pDev->SetMapMode( aHMMMode );
+ pEngine->SetRefDevice( pDev );
+ rDocument.ApplyAsianEditSettings( *pEngine );
+ SfxItemSet aSet( pEngine->GetEmptyItemSet() );
+ if ( ScStyleSheet* pPreviewStyle = rDocument.GetPreviewCellStyle( nCol, nRow, nTab ) )
+ {
+ ScPatternAttr aPreviewPattern( *pPattern );
+ aPreviewPattern.SetStyleSheet(pPreviewStyle);
+ aPreviewPattern.FillEditItemSet( &aSet, pCondSet );
+ }
+ else
+ {
+ SfxItemSet* pFontSet = rDocument.GetPreviewFont( nCol, nRow, nTab );
+ pPattern->FillEditItemSet( &aSet, pFontSet ? pFontSet : pCondSet );
+ }
+// no longer needed, are set with the text (is faster)
+// pEngine->SetDefaults( pSet );
+
+ if ( aSet.Get(EE_PARA_HYPHENATE).GetValue() ) {
+
+ css::uno::Reference<css::linguistic2::XHyphenator> xXHyphenator( LinguMgr::GetHyphenator() );
+ pEngine->SetHyphenator( xXHyphenator );
+ }
+
+ Size aPaper( 1000000, 1000000 );
+ if ( eOrient==SvxCellOrientation::Stacked && !bAsianVertical )
+ aPaper.setWidth( 1 );
+ else if (bBreak)
+ {
+ double fWidthFactor = bInPrintTwips ? 1.0 : nPPTX;
+ if ( bTextWysiwyg )
+ {
+ // if text is formatted for printer, don't use PixelToLogic,
+ // to ensure the exact same paper width (and same line breaks) as in
+ // ScEditUtil::GetEditArea, used for output.
+
+ fWidthFactor = o3tl::convert(1.0, o3tl::Length::twip, o3tl::Length::mm100);
+ }
+
+ // use original width for hidden columns:
+ tools::Long nDocWidth = static_cast<tools::Long>( rDocument.GetOriginalWidth(nCol,nTab) * fWidthFactor );
+ SCCOL nColMerge = pMerge->GetColMerge();
+ if (nColMerge > 1)
+ for (SCCOL nColAdd=1; nColAdd<nColMerge; nColAdd++)
+ nDocWidth += static_cast<tools::Long>( rDocument.GetColWidth(nCol+nColAdd,nTab) * fWidthFactor );
+ nDocWidth -= static_cast<tools::Long>( pMargin->GetLeftMargin() * fWidthFactor )
+ + static_cast<tools::Long>( pMargin->GetRightMargin() * fWidthFactor )
+ + 1; // output size is width-1 pixel (due to gridline)
+ if ( nIndent )
+ nDocWidth -= static_cast<tools::Long>( nIndent * fWidthFactor );
+
+ // space for AutoFilter button: 20 * nZoom/100
+ constexpr tools::Long nFilterButtonWidthPix = 20; // Autofilter pixel width at 100% zoom.
+ if ( pFlag->HasAutoFilter() && !bTextWysiwyg )
+ nDocWidth -= bInPrintTwips ? o3tl::convert(nFilterButtonWidthPix, o3tl::Length::px,
+ o3tl::Length::twip)
+ : tools::Long(rZoomX * nFilterButtonWidthPix);
+
+ aPaper.setWidth( nDocWidth );
+
+ if ( !bTextWysiwyg )
+ {
+ aPaper = bInPrintTwips ?
+ o3tl::convert(aPaper, o3tl::Length::twip, o3tl::Length::mm100) :
+ pDev->PixelToLogic(aPaper, aHMMMode);
+ }
+ }
+ pEngine->SetPaperSize(aPaper);
+
+ if (aCell.meType == CELLTYPE_EDIT)
+ {
+ pEngine->SetTextNewDefaults(*aCell.mpEditText, std::move(aSet));
+ }
+ else
+ {
+ const Color* pColor;
+ OUString aString = ScCellFormat::GetString(
+ aCell, nFormat, &pColor, *pFormatter, rDocument, true,
+ rOptions.bFormula);
+
+ if (!aString.isEmpty())
+ pEngine->SetTextNewDefaults(aString, std::move(aSet));
+ else
+ pEngine->SetDefaults(std::move(aSet));
+ }
+
+ bool bEngineVertical = pEngine->IsEffectivelyVertical();
+ pEngine->SetVertical( bAsianVertical );
+ pEngine->SetUpdateLayout( bPrevUpdateLayout );
+
+ bool bEdWidth = bWidth;
+ if ( eOrient != SvxCellOrientation::Standard && eOrient != SvxCellOrientation::Stacked )
+ bEdWidth = !bEdWidth;
+ if ( nRotate )
+ {
+ //TODO: take different X/Y scaling into consideration
+
+ Size aSize( pEngine->CalcTextWidth(), pEngine->GetTextHeight() );
+ double nRealOrient = toRadians(nRotate);
+ double nCosAbs = fabs( cos( nRealOrient ) );
+ double nSinAbs = fabs( sin( nRealOrient ) );
+ tools::Long nHeight = static_cast<tools::Long>( aSize.Height() * nCosAbs + aSize.Width() * nSinAbs );
+ tools::Long nWidth;
+ if ( eRotMode == SVX_ROTATE_MODE_STANDARD )
+ nWidth = static_cast<tools::Long>( aSize.Width() * nCosAbs + aSize.Height() * nSinAbs );
+ else if ( rOptions.bTotalSize )
+ {
+ nWidth = conditionalScaleFunc(rDocument.GetColWidth( nCol,nTab ), nPPT);
+ bAddMargin = false;
+ if ( pPattern->GetRotateDir( pCondSet ) == ScRotateDir::Right )
+ nWidth += static_cast<tools::Long>( rDocument.GetRowHeight( nRow,nTab ) *
+ (bInPrintTwips ? 1.0 : nPPT) * nCosAbs / nSinAbs );
+ }
+ else
+ nWidth = static_cast<tools::Long>( aSize.Height() / nSinAbs ); //TODO: limit?
+ aSize = Size( nWidth, nHeight );
+
+ Size aTextSize = bInPrintTwips ?
+ o3tl::toTwips(aSize, o3tl::Length::mm100) :
+ pDev->LogicToPixel(aSize, aHMMMode);
+
+ if ( bEdWidth )
+ nValue = aTextSize.Width();
+ else
+ {
+ nValue = aTextSize.Height();
+
+ if ( bBreak && !rOptions.bTotalSize )
+ {
+ // limit size for line break
+ tools::Long nCmp = aOldFont.GetFontSize().Height() * SC_ROT_BREAK_FACTOR;
+ if ( nValue > nCmp )
+ nValue = nCmp;
+ }
+ }
+ }
+ else if ( bEdWidth )
+ {
+ if (bBreak)
+ nValue = 0;
+ else
+ {
+ sal_uInt32 aTextSize(pEngine->CalcTextWidth());
+ nValue = bInPrintTwips ?
+ o3tl::toTwips(aTextSize, o3tl::Length::mm100) :
+ pDev->LogicToPixel(Size(aTextSize, 0), aHMMMode).Width();
+ }
+ }
+ else // height
+ {
+ sal_uInt32 aTextSize(pEngine->GetTextHeight());
+ nValue = bInPrintTwips ?
+ o3tl::toTwips(aTextSize, o3tl::Length::mm100) :
+ pDev->LogicToPixel(Size(0, aTextSize), aHMMMode).Height();
+
+ // With non-100% zoom and several lines or paragraphs, don't shrink below the result with FORMAT100 set
+ if ( !bTextWysiwyg && ( rZoomY.GetNumerator() != 1 || rZoomY.GetDenominator() != 1 ) &&
+ ( pEngine->GetParagraphCount() > 1 || ( bBreak && pEngine->GetLineCount(0) > 1 ) ) )
+ {
+ pEngine->SetControlWord( nCtrl | EEControlBits::FORMAT100 );
+ pEngine->QuickFormatDoc( true );
+ aTextSize = pEngine->GetTextHeight();
+ tools::Long nSecondValue = bInPrintTwips ?
+ o3tl::toTwips(aTextSize, o3tl::Length::mm100) :
+ pDev->LogicToPixel(Size(0, aTextSize), aHMMMode).Height();
+ if ( nSecondValue > nValue )
+ nValue = nSecondValue;
+ }
+ }
+
+ if ( nValue && bAddMargin )
+ {
+ if (bWidth)
+ {
+ nValue += conditionalScaleFunc(pMargin->GetLeftMargin(), nPPT) +
+ conditionalScaleFunc(pMargin->GetRightMargin(), nPPT);
+ if (nIndent)
+ nValue += conditionalScaleFunc(nIndent, nPPT);
+ }
+ else
+ {
+ nValue += conditionalScaleFunc(pMargin->GetTopMargin(), nPPT) +
+ conditionalScaleFunc(pMargin->GetBottomMargin(), nPPT);
+
+ if ( bAsianVertical && pDev->GetOutDevType() != OUTDEV_PRINTER )
+ {
+ // add 1pt extra (default margin value) for line breaks with SetVertical
+ constexpr tools::Long nDefaultMarginInPoints = 1;
+ nValue += conditionalScaleFunc(
+ o3tl::convert(nDefaultMarginInPoints, o3tl::Length::pt, o3tl::Length::twip),
+ nPPT);
+ }
+ }
+ }
+
+ // EditEngine is cached and re-used, so the old vertical flag must be restored
+ pEngine->SetVertical( bEngineVertical );
+
+ rDocument.DisposeFieldEditEngine(pEngine);
+
+ pDev->SetMapMode( aOld );
+ pDev->SetFont( aOldFont );
+ }
+
+ if (bWidth)
+ {
+ // place for Autofilter Button
+ // 20 * nZoom/100
+ // Conditional formatting is not interesting here
+ constexpr tools::Long nFilterButtonWidthPix = 20; // Autofilter pixel width at 100% zoom.
+ ScMF nFlags = pPattern->GetItem(ATTR_MERGE_FLAG).GetValue();
+ if (nFlags & ScMF::Auto)
+ nValue += bInPrintTwips ? o3tl::convert(nFilterButtonWidthPix, o3tl::Length::px,
+ o3tl::Length::twip)
+ : tools::Long(rZoomX * nFilterButtonWidthPix);
+ }
+
+ return nValue;
+}
+
+namespace {
+
+class MaxStrLenFinder
+{
+ ScDocument& mrDoc;
+ sal_uInt32 mnFormat;
+ OUString maMaxLenStr;
+ sal_Int32 mnMaxLen;
+
+ // tdf#59820 - search for the longest substring in a multiline string
+ void checkLineBreak(const OUString& aStrVal)
+ {
+ sal_Int32 nFromIndex = 0;
+ sal_Int32 nToIndex = aStrVal.indexOf('\n', nFromIndex);
+ // if there is no line break, just take the length of the entire string
+ if (nToIndex == -1)
+ {
+ mnMaxLen = aStrVal.getLength();
+ maMaxLenStr = aStrVal;
+ }
+ else
+ {
+ sal_Int32 nMaxLen = 0;
+ // search for the longest substring in the multiline string
+ while (nToIndex != -1)
+ {
+ if (nMaxLen < nToIndex - nFromIndex)
+ {
+ nMaxLen = nToIndex - nFromIndex;
+ }
+ nFromIndex = nToIndex + 1;
+ nToIndex = aStrVal.indexOf('\n', nFromIndex);
+ }
+ // take into consideration the last part of multiline string
+ nToIndex = aStrVal.getLength() - nFromIndex;
+ if (nMaxLen < nToIndex)
+ {
+ nMaxLen = nToIndex;
+ }
+ // assign new maximum including its substring
+ if (mnMaxLen < nMaxLen)
+ {
+ mnMaxLen = nMaxLen;
+ maMaxLenStr = aStrVal.subView(nFromIndex);
+ }
+ }
+ }
+
+ void checkLength(const ScRefCellValue& rCell)
+ {
+ const Color* pColor;
+ OUString aValStr = ScCellFormat::GetString(
+ rCell, mnFormat, &pColor, *mrDoc.GetFormatTable(), mrDoc);
+
+ if (aValStr.getLength() <= mnMaxLen)
+ return;
+
+ switch (rCell.meType)
+ {
+ case CELLTYPE_NONE:
+ case CELLTYPE_VALUE:
+ mnMaxLen = aValStr.getLength();
+ maMaxLenStr = aValStr;
+ break;
+ case CELLTYPE_EDIT:
+ case CELLTYPE_STRING:
+ case CELLTYPE_FORMULA:
+ default:
+ checkLineBreak(aValStr);
+ }
+ }
+
+public:
+ MaxStrLenFinder(ScDocument& rDoc, sal_uInt32 nFormat) :
+ mrDoc(rDoc), mnFormat(nFormat), mnMaxLen(0) {}
+
+ void operator() (size_t /*nRow*/, double f)
+ {
+ ScRefCellValue aCell(f);
+ checkLength(aCell);
+ }
+
+ void operator() (size_t /*nRow*/, const svl::SharedString& rSS)
+ {
+ if (rSS.getLength() > mnMaxLen)
+ {
+ checkLineBreak(rSS.getString());
+ }
+ }
+
+ void operator() (size_t /*nRow*/, const EditTextObject* p)
+ {
+ ScRefCellValue aCell(p);
+ checkLength(aCell);
+ }
+
+ void operator() (size_t /*nRow*/, const ScFormulaCell* p)
+ {
+ ScRefCellValue aCell(const_cast<ScFormulaCell*>(p));
+ checkLength(aCell);
+ }
+
+ const OUString& getMaxLenStr() const { return maMaxLenStr; }
+};
+
+}
+
+sal_uInt16 ScColumn::GetOptimalColWidth(
+ OutputDevice* pDev, double nPPTX, double nPPTY, const Fraction& rZoomX, const Fraction& rZoomY,
+ bool bFormula, sal_uInt16 nOldWidth, const ScMarkData* pMarkData, const ScColWidthParam* pParam) const
+{
+ if (maCells.block_size() == 1 && maCells.begin()->type == sc::element_type_empty)
+ // All cells are empty.
+ return nOldWidth;
+
+ sc::SingleColumnSpanSet aSpanSet(GetDoc().GetSheetLimits());
+ sc::SingleColumnSpanSet::SpansType aMarkedSpans;
+ if (pMarkData && (pMarkData->IsMarked() || pMarkData->IsMultiMarked()))
+ {
+ aSpanSet.scan(*pMarkData, nTab, nCol);
+ aSpanSet.getSpans(aMarkedSpans);
+ }
+ else
+ // "Select" the entire column if no selection exists.
+ aMarkedSpans.emplace_back(0, GetDoc().MaxRow());
+
+ sal_uInt16 nWidth = static_cast<sal_uInt16>(nOldWidth*nPPTX);
+ bool bFound = false;
+ ScDocument& rDocument = GetDoc();
+
+ if ( pParam && pParam->mbSimpleText )
+ { // all the same except for number format
+ SCROW nRow = 0;
+ const ScPatternAttr* pPattern = GetPattern( nRow );
+ vcl::Font aFont;
+ // font color doesn't matter here
+ pPattern->GetFont( aFont, SC_AUTOCOL_BLACK, pDev, &rZoomX );
+ pDev->SetFont( aFont );
+ const SvxMarginItem* pMargin = &pPattern->GetItem(ATTR_MARGIN);
+ tools::Long nMargin = static_cast<tools::Long>( pMargin->GetLeftMargin() * nPPTX ) +
+ static_cast<tools::Long>( pMargin->GetRightMargin() * nPPTX );
+
+ // Try to find the row that has the longest string, and measure the width of that string.
+ SvNumberFormatter* pFormatter = rDocument.GetFormatTable();
+ sal_uInt32 nFormat = pPattern->GetNumberFormat( pFormatter );
+ while ((nFormat % SV_COUNTRY_LANGUAGE_OFFSET) == 0 && nRow <= 2)
+ {
+ // This is often used with CSV import or other data having a header
+ // row; if there is no specific format set try next row for actual
+ // data format.
+ // Or again in case there was a leading sep=";" row or two header
+ // rows..
+ const ScPatternAttr* pNextPattern = GetPattern( ++nRow );
+ if (pNextPattern != pPattern)
+ nFormat = pNextPattern->GetNumberFormat( pFormatter );
+ }
+ OUString aLongStr;
+ const Color* pColor;
+ if (pParam->mnMaxTextRow >= 0)
+ {
+ ScRefCellValue aCell = GetCellValue(pParam->mnMaxTextRow);
+ aLongStr = ScCellFormat::GetString(
+ aCell, nFormat, &pColor, *pFormatter, rDocument);
+ }
+ else
+ {
+ // Go though all non-empty cells within selection.
+ MaxStrLenFinder aFunc(rDocument, nFormat);
+ sc::CellStoreType::const_iterator itPos = maCells.begin();
+ for (const auto& rMarkedSpan : aMarkedSpans)
+ itPos = sc::ParseAllNonEmpty(itPos, maCells, rMarkedSpan.mnRow1, rMarkedSpan.mnRow2, aFunc);
+
+ aLongStr = aFunc.getMaxLenStr();
+ }
+
+ if (!aLongStr.isEmpty())
+ {
+ nWidth = pDev->GetTextWidth(aLongStr) + static_cast<sal_uInt16>(nMargin);
+ bFound = true;
+ }
+ }
+ else
+ {
+ ScNeededSizeOptions aOptions;
+ aOptions.bFormula = bFormula;
+ const ScPatternAttr* pOldPattern = nullptr;
+
+ // Go though all non-empty cells within selection.
+ sc::CellStoreType::const_iterator itPos = maCells.begin();
+ for (const auto& rMarkedSpan : aMarkedSpans)
+ {
+ SCROW nRow1 = rMarkedSpan.mnRow1, nRow2 = rMarkedSpan.mnRow2;
+ SCROW nRow = nRow1;
+ while (nRow <= nRow2)
+ {
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(itPos, nRow);
+ itPos = aPos.first;
+ if (itPos->type == sc::element_type_empty)
+ {
+ // Skip empty cells.
+ nRow += itPos->size - aPos.second;
+ continue;
+ }
+
+ for (size_t nOffset = aPos.second; nOffset < itPos->size; ++nOffset, ++nRow)
+ {
+ SvtScriptType nScript = rDocument.GetScriptType(nCol, nRow, nTab);
+ if (nScript == SvtScriptType::NONE)
+ nScript = ScGlobal::GetDefaultScriptType();
+
+ const ScPatternAttr* pPattern = GetPattern(nRow);
+ aOptions.pPattern = pPattern;
+ aOptions.bGetFont = (pPattern != pOldPattern || nScript != SvtScriptType::NONE);
+ pOldPattern = pPattern;
+ sal_uInt16 nThis = static_cast<sal_uInt16>(GetNeededSize(
+ nRow, pDev, nPPTX, nPPTY, rZoomX, rZoomY, true, aOptions, &pOldPattern));
+ if (nThis && (nThis > nWidth || !bFound))
+ {
+ nWidth = nThis;
+ bFound = true;
+ }
+ }
+ }
+ }
+ }
+
+ if (bFound)
+ {
+ nWidth += 2;
+ sal_uInt16 nTwips = static_cast<sal_uInt16>(
+ std::min(nWidth / nPPTX, std::numeric_limits<sal_uInt16>::max() / 2.0));
+ return nTwips;
+ }
+ else
+ return nOldWidth;
+}
+
+static sal_uInt16 lcl_GetAttribHeight( const ScPatternAttr& rPattern, sal_uInt16 nFontHeightId )
+{
+ const SvxFontHeightItem& rFontHeight =
+ static_cast<const SvxFontHeightItem&>(rPattern.GetItem(nFontHeightId));
+
+ sal_uInt16 nHeight = rFontHeight.GetHeight();
+ nHeight *= 1.18;
+
+ if ( rPattern.GetItem(ATTR_FONT_EMPHASISMARK).GetEmphasisMark() != FontEmphasisMark::NONE )
+ {
+ // add height for emphasis marks
+ //TODO: font metrics should be used instead
+ nHeight += nHeight / 4;
+ }
+
+ const SvxMarginItem& rMargin = rPattern.GetItem(ATTR_MARGIN);
+
+ nHeight += rMargin.GetTopMargin() + rMargin.GetBottomMargin();
+
+ if (nHeight > STD_ROWHEIGHT_DIFF)
+ nHeight -= STD_ROWHEIGHT_DIFF;
+
+ if (nHeight < ScGlobal::nStdRowHeight)
+ nHeight = ScGlobal::nStdRowHeight;
+
+ return nHeight;
+}
+
+// pHeight in Twips
+// optimize nMinHeight, nMinStart : with nRow >= nMinStart is at least nMinHeight
+// (is only evaluated with bStdAllowed)
+
+void ScColumn::GetOptimalHeight(
+ sc::RowHeightContext& rCxt, SCROW nStartRow, SCROW nEndRow, sal_uInt16 nMinHeight, SCROW nMinStart )
+{
+ ScDocument& rDocument = GetDoc();
+ RowHeightsArray& rHeights = rCxt.getHeightArray();
+ ScAttrIterator aIter( pAttrArray.get(), nStartRow, nEndRow, rDocument.GetDefPattern() );
+
+ SCROW nStart = -1;
+ SCROW nEnd = -1;
+ SCROW nEditPos = 0;
+ SCROW nNextEnd = 0;
+
+ // with conditional formatting, always consider the individual cells
+
+ const ScPatternAttr* pPattern = aIter.Next(nStart,nEnd);
+ while ( pPattern )
+ {
+ const ScMergeAttr* pMerge = &pPattern->GetItem(ATTR_MERGE);
+ const ScMergeFlagAttr* pFlag = &pPattern->GetItem(ATTR_MERGE_FLAG);
+ if ( pMerge->GetRowMerge() > 1 || pFlag->IsOverlapped() )
+ {
+ // do nothing - vertically with merged and overlapping,
+ // horizontally only with overlapped (invisible) -
+ // only one horizontal merged is always considered
+ }
+ else
+ {
+ bool bStdAllowed = (pPattern->GetCellOrientation() == SvxCellOrientation::Standard);
+ bool bStdOnly = false;
+ if (bStdAllowed)
+ {
+ bool bBreak = pPattern->GetItem(ATTR_LINEBREAK).GetValue() ||
+ (pPattern->GetItem( ATTR_HOR_JUSTIFY ).GetValue() ==
+ SvxCellHorJustify::Block);
+ bStdOnly = !bBreak;
+
+ // conditional formatting: loop all cells
+ if (bStdOnly &&
+ !pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData().empty())
+ {
+ bStdOnly = false;
+ }
+
+ // rotated text: loop all cells
+ if ( bStdOnly && pPattern->GetItem(ATTR_ROTATE_VALUE).GetValue() )
+ bStdOnly = false;
+ }
+
+ if (bStdOnly)
+ {
+ bool bHasEditCells = HasEditCells(nStart,nEnd,nEditPos);
+ // Call to HasEditCells() may change pattern due to
+ // calculation, => sync always.
+ // We don't know which row changed first, but as pPattern
+ // covered nStart to nEnd we can pick nStart. Worst case we
+ // have to repeat that for every row in range if every row
+ // changed.
+ pPattern = aIter.Resync( nStart, nStart, nEnd);
+ if (bHasEditCells && nEnd < nEditPos)
+ bHasEditCells = false; // run into that again
+ if (bHasEditCells) // includes mixed script types
+ {
+ if (nEditPos == nStart)
+ {
+ bStdOnly = false;
+ if (nEnd > nEditPos)
+ nNextEnd = nEnd;
+ nEnd = nEditPos; // calculate single
+ bStdAllowed = false; // will be computed in any case per cell
+ }
+ else
+ {
+ nNextEnd = nEnd;
+ nEnd = nEditPos - 1; // standard - part
+ }
+ }
+ }
+
+ sc::SingleColumnSpanSet aSpanSet(GetDoc().GetSheetLimits());
+ aSpanSet.scan(*this, nStart, nEnd);
+ sc::SingleColumnSpanSet::SpansType aSpans;
+ aSpanSet.getSpans(aSpans);
+
+ if (bStdAllowed)
+ {
+ sal_uInt16 nLatHeight = 0;
+ sal_uInt16 nCjkHeight = 0;
+ sal_uInt16 nCtlHeight = 0;
+ sal_uInt16 nDefHeight;
+ SvtScriptType nDefScript = ScGlobal::GetDefaultScriptType();
+ if ( nDefScript == SvtScriptType::ASIAN )
+ nDefHeight = nCjkHeight = lcl_GetAttribHeight( *pPattern, ATTR_CJK_FONT_HEIGHT );
+ else if ( nDefScript == SvtScriptType::COMPLEX )
+ nDefHeight = nCtlHeight = lcl_GetAttribHeight( *pPattern, ATTR_CTL_FONT_HEIGHT );
+ else
+ nDefHeight = nLatHeight = lcl_GetAttribHeight( *pPattern, ATTR_FONT_HEIGHT );
+
+ // if everything below is already larger, the loop doesn't have to
+ // be run again
+ SCROW nStdEnd = nEnd;
+ if ( nDefHeight <= nMinHeight && nStdEnd >= nMinStart )
+ nStdEnd = (nMinStart>0) ? nMinStart-1 : 0;
+
+ if (nStart <= nStdEnd)
+ {
+ SCROW nRow = nStart;
+ for (;;)
+ {
+ size_t nIndex;
+ SCROW nRangeEnd;
+ sal_uInt16 nRangeHeight = rHeights.GetValue(nRow, nIndex, nRangeEnd);
+ if (nRangeHeight < nDefHeight)
+ rHeights.SetValue(nRow, std::min(nRangeEnd, nStdEnd), nDefHeight);
+ nRow = nRangeEnd + 1;
+ if (nRow > nStdEnd)
+ break;
+ }
+ }
+
+ if ( bStdOnly )
+ {
+ // if cells are not handled individually below,
+ // check for cells with different script type
+ sc::CellTextAttrStoreType::iterator itAttr = maCellTextAttrs.begin();
+ sc::CellStoreType::iterator itCells = maCells.begin();
+ for (const auto& rSpan : aSpans)
+ {
+ for (SCROW nRow = rSpan.mnRow1; nRow <= rSpan.mnRow2; ++nRow)
+ {
+ SvtScriptType nScript = GetRangeScriptType(itAttr, nRow, nRow, itCells);
+ if (nScript == nDefScript)
+ continue;
+
+ if ( nScript == SvtScriptType::ASIAN )
+ {
+ if ( nCjkHeight == 0 )
+ nCjkHeight = lcl_GetAttribHeight( *pPattern, ATTR_CJK_FONT_HEIGHT );
+ if (nCjkHeight > rHeights.GetValue(nRow))
+ rHeights.SetValue(nRow, nRow, nCjkHeight);
+ }
+ else if ( nScript == SvtScriptType::COMPLEX )
+ {
+ if ( nCtlHeight == 0 )
+ nCtlHeight = lcl_GetAttribHeight( *pPattern, ATTR_CTL_FONT_HEIGHT );
+ if (nCtlHeight > rHeights.GetValue(nRow))
+ rHeights.SetValue(nRow, nRow, nCtlHeight);
+ }
+ else
+ {
+ if ( nLatHeight == 0 )
+ nLatHeight = lcl_GetAttribHeight( *pPattern, ATTR_FONT_HEIGHT );
+ if (nLatHeight > rHeights.GetValue(nRow))
+ rHeights.SetValue(nRow, nRow, nLatHeight);
+ }
+ }
+ }
+ }
+ }
+
+ if (!bStdOnly) // search covered cells
+ {
+ ScNeededSizeOptions aOptions;
+
+ for (const auto& rSpan : aSpans)
+ {
+ for (SCROW nRow = rSpan.mnRow1; nRow <= rSpan.mnRow2; ++nRow)
+ {
+ // only calculate the cell height when it's used later (#37928#)
+
+ if (rCxt.isForceAutoSize() || !(rDocument.GetRowFlags(nRow, nTab) & CRFlags::ManualSize) )
+ {
+ aOptions.pPattern = pPattern;
+ const ScPatternAttr* pOldPattern = pPattern;
+ sal_uInt16 nHeight = static_cast<sal_uInt16>(
+ std::min(
+ GetNeededSize( nRow, rCxt.getOutputDevice(), rCxt.getPPTX(), rCxt.getPPTY(),
+ rCxt.getZoomX(), rCxt.getZoomY(), false, aOptions,
+ &pPattern) / rCxt.getPPTY(),
+ double(std::numeric_limits<sal_uInt16>::max())));
+ if (nHeight > rHeights.GetValue(nRow))
+ rHeights.SetValue(nRow, nRow, nHeight);
+ // Pattern changed due to calculation? => sync.
+ if (pPattern != pOldPattern)
+ {
+ pPattern = aIter.Resync( nRow, nStart, nEnd);
+ nNextEnd = 0;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (nNextEnd > 0)
+ {
+ nStart = nEnd + 1;
+ nEnd = nNextEnd;
+ nNextEnd = 0;
+ }
+ else
+ pPattern = aIter.Next(nStart,nEnd);
+ }
+}
+
+bool ScColumn::GetNextSpellingCell(SCROW& nRow, bool bInSel, const ScMarkData& rData) const
+{
+ ScDocument& rDocument = GetDoc();
+ sc::CellStoreType::const_iterator it = maCells.position(nRow).first;
+ mdds::mtv::element_t eType = it->type;
+ if (!bInSel && it != maCells.end() && eType != sc::element_type_empty)
+ {
+ if ( (eType == sc::element_type_string || eType == sc::element_type_edittext) &&
+ !(HasAttrib( nRow, nRow, HasAttrFlags::Protected) &&
+ rDocument.IsTabProtected(nTab)) )
+ return true;
+ }
+ if (bInSel)
+ {
+ SCROW lastDataPos = GetLastDataPos();
+ for (;;)
+ {
+ nRow = rData.GetNextMarked(nCol, nRow, false);
+ if (!rDocument.ValidRow(nRow) || nRow > lastDataPos )
+ {
+ nRow = GetDoc().MaxRow()+1;
+ return false;
+ }
+ else
+ {
+ it = maCells.position(it, nRow).first;
+ eType = it->type;
+ if ( (eType == sc::element_type_string || eType == sc::element_type_edittext) &&
+ !(HasAttrib( nRow, nRow, HasAttrFlags::Protected) &&
+ rDocument.IsTabProtected(nTab)) )
+ return true;
+ else
+ nRow++;
+ }
+ }
+ }
+ else
+ {
+ while (GetNextDataPos(nRow))
+ {
+ it = maCells.position(it, nRow).first;
+ eType = it->type;
+ if ( (eType == sc::element_type_string || eType == sc::element_type_edittext) &&
+ !(HasAttrib( nRow, nRow, HasAttrFlags::Protected) &&
+ rDocument.IsTabProtected(nTab)) )
+ return true;
+ else
+ nRow++;
+ }
+ nRow = GetDoc().MaxRow()+1;
+ return false;
+ }
+}
+
+namespace {
+
+class StrEntries
+{
+ sc::CellStoreType& mrCells;
+
+protected:
+ struct StrEntry
+ {
+ SCROW mnRow;
+ OUString maStr;
+
+ StrEntry(SCROW nRow, const OUString& rStr) : mnRow(nRow), maStr(rStr) {}
+ };
+
+ std::vector<StrEntry> maStrEntries;
+ ScDocument* mpDoc;
+
+ StrEntries(sc::CellStoreType& rCells, ScDocument* pDoc) : mrCells(rCells), mpDoc(pDoc) {}
+
+public:
+ void commitStrings()
+ {
+ svl::SharedStringPool& rPool = mpDoc->GetSharedStringPool();
+ sc::CellStoreType::iterator it = mrCells.begin();
+ for (const auto& rStrEntry : maStrEntries)
+ it = mrCells.set(it, rStrEntry.mnRow, rPool.intern(rStrEntry.maStr));
+ }
+};
+
+class RemoveEditAttribsHandler : public StrEntries
+{
+ std::unique_ptr<ScFieldEditEngine> mpEngine;
+
+public:
+ RemoveEditAttribsHandler(sc::CellStoreType& rCells, ScDocument* pDoc) : StrEntries(rCells, pDoc) {}
+
+ void operator() (size_t nRow, EditTextObject*& pObj)
+ {
+ // For the test on hard formatting (ScEditAttrTester), are the defaults in the
+ // EditEngine of no importance. When the tester would later recognise the same
+ // attributes in default and hard formatting and has to remove them, the correct
+ // defaults must be set in the EditEngine for each cell.
+
+ // test for attributes
+ if (!mpEngine)
+ {
+ mpEngine.reset(new ScFieldEditEngine(mpDoc, mpDoc->GetEditPool()));
+ // EEControlBits::ONLINESPELLING if there are errors already
+ mpEngine->SetControlWord(mpEngine->GetControlWord() | EEControlBits::ONLINESPELLING);
+ mpDoc->ApplyAsianEditSettings(*mpEngine);
+ }
+ mpEngine->SetTextCurrentDefaults(*pObj);
+ sal_Int32 nParCount = mpEngine->GetParagraphCount();
+ for (sal_Int32 nPar=0; nPar<nParCount; nPar++)
+ {
+ mpEngine->RemoveCharAttribs(nPar);
+ const SfxItemSet& rOld = mpEngine->GetParaAttribs(nPar);
+ if ( rOld.Count() )
+ {
+ SfxItemSet aNew( *rOld.GetPool(), rOld.GetRanges() ); // empty
+ mpEngine->SetParaAttribs( nPar, aNew );
+ }
+ }
+ // change URL field to text (not possible otherwise, thus pType=0)
+ mpEngine->RemoveFields();
+
+ bool bSpellErrors = mpEngine->HasOnlineSpellErrors();
+ bool bNeedObject = bSpellErrors || nParCount>1; // keep errors/paragraphs
+ // ScEditAttrTester is not needed anymore, arrays are gone
+
+ if (bNeedObject) // remains edit cell
+ {
+ EEControlBits nCtrl = mpEngine->GetControlWord();
+ EEControlBits nWantBig = bSpellErrors ? EEControlBits::ALLOWBIGOBJS : EEControlBits::NONE;
+ if ( ( nCtrl & EEControlBits::ALLOWBIGOBJS ) != nWantBig )
+ mpEngine->SetControlWord( (nCtrl & ~EEControlBits::ALLOWBIGOBJS) | nWantBig );
+
+ // Overwrite the existing object.
+ delete pObj;
+ pObj = mpEngine->CreateTextObject().release();
+ }
+ else // create String
+ {
+ // Store the string replacement for later commits.
+ OUString aText = ScEditUtil::GetSpaceDelimitedString(*mpEngine);
+ maStrEntries.emplace_back(nRow, aText);
+ }
+ }
+};
+
+class TestTabRefAbsHandler
+{
+ SCTAB mnTab;
+ bool mbTestResult;
+public:
+ explicit TestTabRefAbsHandler(SCTAB nTab) : mnTab(nTab), mbTestResult(false) {}
+
+ void operator() (size_t /*nRow*/, const ScFormulaCell* pCell)
+ {
+ if (const_cast<ScFormulaCell*>(pCell)->TestTabRefAbs(mnTab))
+ mbTestResult = true;
+ }
+
+ bool getTestResult() const { return mbTestResult; }
+};
+
+}
+
+void ScColumn::RemoveEditAttribs( sc::ColumnBlockPosition& rBlockPos, SCROW nStartRow, SCROW nEndRow )
+{
+ RemoveEditAttribsHandler aFunc(maCells, &GetDoc());
+
+ rBlockPos.miCellPos = sc::ProcessEditText(
+ rBlockPos.miCellPos, maCells, nStartRow, nEndRow, aFunc);
+
+ aFunc.commitStrings();
+}
+
+bool ScColumn::TestTabRefAbs(SCTAB nTable) const
+{
+ TestTabRefAbsHandler aFunc(nTable);
+ sc::ParseFormula(maCells, aFunc);
+ return aFunc.getTestResult();
+}
+
+bool ScColumn::IsEmptyData() const
+{
+ return maCells.block_size() == 1 && maCells.begin()->type == sc::element_type_empty;
+}
+
+namespace {
+
+class CellCounter
+{
+ size_t mnCount;
+public:
+ CellCounter() : mnCount(0) {}
+
+ void operator() (
+ const sc::CellStoreType::value_type& node, size_t /*nOffset*/, size_t nDataSize)
+ {
+ if (node.type == sc::element_type_empty)
+ return;
+
+ mnCount += nDataSize;
+ }
+
+ size_t getCount() const { return mnCount; }
+};
+
+}
+
+SCSIZE ScColumn::VisibleCount( SCROW nStartRow, SCROW nEndRow ) const
+{
+ CellCounter aFunc;
+ sc::ParseBlock(maCells.begin(), maCells, aFunc, nStartRow, nEndRow);
+ return aFunc.getCount();
+}
+
+bool ScColumn::HasVisibleDataAt(SCROW nRow) const
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it == maCells.end())
+ // Likely invalid row number.
+ return false;
+
+ return it->type != sc::element_type_empty;
+}
+
+bool ScColumn::IsEmptyData(SCROW nStartRow, SCROW nEndRow) const
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nStartRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it == maCells.end())
+ // Invalid row number.
+ return false;
+
+ if (it->type != sc::element_type_empty)
+ // Non-empty cell at the start position.
+ return false;
+
+ // start position of next block which is not empty.
+ SCROW nNextRow = nStartRow + it->size - aPos.second;
+ return nEndRow < nNextRow;
+}
+
+bool ScColumn::IsNotesEmptyBlock(SCROW nStartRow, SCROW nEndRow) const
+{
+ std::pair<sc::CellNoteStoreType::const_iterator,size_t> aPos = maCellNotes.position(nStartRow);
+ sc::CellNoteStoreType::const_iterator it = aPos.first;
+ if (it == maCellNotes.end())
+ // Invalid row number.
+ return false;
+
+ if (it->type != sc::element_type_empty)
+ // Non-empty cell at the start position.
+ return false;
+
+ // start position of next block which is not empty.
+ SCROW nNextRow = nStartRow + it->size - aPos.second;
+ return nEndRow < nNextRow;
+}
+
+SCSIZE ScColumn::GetEmptyLinesInBlock( SCROW nStartRow, SCROW nEndRow, ScDirection eDir ) const
+{
+ // Given a range of rows, find a top or bottom empty segment.
+ switch (eDir)
+ {
+ case DIR_TOP:
+ {
+ // Determine the length of empty head segment.
+ size_t nLength = nEndRow - nStartRow + 1;
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nStartRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it->type != sc::element_type_empty)
+ // First row is already not empty.
+ return 0;
+
+ // length of this empty block minus the offset.
+ size_t nThisLen = it->size - aPos.second;
+ return std::min(nThisLen, nLength);
+ }
+ break;
+ case DIR_BOTTOM:
+ {
+ // Determine the length of empty tail segment.
+ size_t nLength = nEndRow - nStartRow + 1;
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nEndRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it->type != sc::element_type_empty)
+ // end row is already not empty.
+ return 0;
+
+ // length of this empty block from the tip to the end row position.
+ size_t nThisLen = aPos.second + 1;
+ return std::min(nThisLen, nLength);
+ }
+ break;
+ default:
+ ;
+ }
+
+ return 0;
+}
+
+SCROW ScColumn::GetFirstDataPos() const
+{
+ if (IsEmptyData())
+ return 0;
+
+ sc::CellStoreType::const_iterator it = maCells.begin();
+ if (it->type != sc::element_type_empty)
+ return 0;
+
+ return it->size;
+}
+
+SCROW ScColumn::GetLastDataPos() const
+{
+ if (IsEmptyData())
+ return 0;
+
+ sc::CellStoreType::const_reverse_iterator it = maCells.rbegin();
+ if (it->type != sc::element_type_empty)
+ return GetDoc().MaxRow();
+
+ return GetDoc().MaxRow() - static_cast<SCROW>(it->size);
+}
+
+SCROW ScColumn::GetLastDataPos( SCROW nLastRow, ScDataAreaExtras* pDataAreaExtras ) const
+{
+ nLastRow = std::min( nLastRow, GetDoc().MaxRow());
+
+ if (pDataAreaExtras && pDataAreaExtras->mnEndRow < nLastRow)
+ {
+ // Check in order of likeliness.
+ if ( (pDataAreaExtras->mbCellFormats && HasVisibleAttrIn(nLastRow, nLastRow)) ||
+ (pDataAreaExtras->mbCellNotes && !IsNotesEmptyBlock(nLastRow, nLastRow)) ||
+ (pDataAreaExtras->mbCellDrawObjects && !IsDrawObjectsEmptyBlock(nLastRow, nLastRow)))
+ pDataAreaExtras->mnEndRow = nLastRow;
+ }
+
+ sc::CellStoreType::const_position_type aPos = maCells.position(nLastRow);
+
+ if (aPos.first->type != sc::element_type_empty)
+ return nLastRow;
+
+ if (aPos.first == maCells.begin())
+ // This is the first block, and is empty.
+ return 0;
+
+ return static_cast<SCROW>(aPos.first->position - 1);
+}
+
+bool ScColumn::GetPrevDataPos(SCROW& rRow) const
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(rRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it == maCells.end())
+ return false;
+
+ if (it->type == sc::element_type_empty)
+ {
+ if (it == maCells.begin())
+ // No more previous non-empty cell.
+ return false;
+
+ rRow -= aPos.second + 1; // Last row position of the previous block.
+ return true;
+ }
+
+ // This block is not empty.
+ if (aPos.second)
+ {
+ // There are preceding cells in this block. Simply move back one cell.
+ --rRow;
+ return true;
+ }
+
+ // This is the first cell in a non-empty block. Move back to the previous block.
+ if (it == maCells.begin())
+ // No more preceding block.
+ return false;
+
+ --rRow; // Move to the last cell of the previous block.
+ --it;
+ if (it->type == sc::element_type_empty)
+ {
+ // This block is empty.
+ if (it == maCells.begin())
+ // No more preceding blocks.
+ return false;
+
+ // Skip the whole empty block segment.
+ rRow -= it->size;
+ }
+
+ return true;
+}
+
+bool ScColumn::GetNextDataPos(SCROW& rRow) const // greater than rRow
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(rRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it == maCells.end())
+ return false;
+
+ if (it->type == sc::element_type_empty)
+ {
+ // This block is empty. Skip ahead to the next block (if exists).
+ rRow += it->size - aPos.second;
+ ++it;
+ if (it == maCells.end())
+ // No more next block.
+ return false;
+
+ // Next block exists, and is non-empty.
+ return true;
+ }
+
+ if (aPos.second < it->size - 1)
+ {
+ // There are still cells following the current position.
+ ++rRow;
+ return true;
+ }
+
+ // This is the last cell in the block. Move ahead to the next block.
+ rRow += it->size - aPos.second; // First cell in the next block.
+ ++it;
+ if (it == maCells.end())
+ // No more next block.
+ return false;
+
+ if (it->type == sc::element_type_empty)
+ {
+ // Next block is empty. Move to the next block.
+ rRow += it->size;
+ ++it;
+ if (it == maCells.end())
+ return false;
+ }
+
+ return true;
+}
+
+bool ScColumn::TrimEmptyBlocks(SCROW& rRowStart, SCROW& rRowEnd) const
+{
+ assert(rRowStart <= rRowEnd);
+ SCROW nRowStartNew = rRowStart, nRowEndNew = rRowEnd;
+
+ // Trim down rRowStart first
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(rRowStart);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it == maCells.end())
+ return false;
+
+ if (it->type == sc::element_type_empty)
+ {
+ // This block is empty. Skip ahead to the next block (if exists).
+ nRowStartNew += it->size - aPos.second;
+ if (nRowStartNew > rRowEnd)
+ return false;
+ ++it;
+ if (it == maCells.end())
+ // No more next block.
+ return false;
+ }
+
+ // Trim up rRowEnd next
+ aPos = maCells.position(rRowEnd);
+ it = aPos.first;
+ if (it == maCells.end())
+ {
+ rRowStart = nRowStartNew;
+ return true; // Because trimming of rRowStart is ok
+ }
+
+ if (it->type == sc::element_type_empty)
+ {
+ // rRowEnd cannot be in the first block which is empty !
+ assert(it != maCells.begin());
+ // This block is empty. Skip to the previous block (it exists).
+ nRowEndNew -= aPos.second + 1; // Last row position of the previous block.
+ assert(nRowStartNew <= nRowEndNew);
+ }
+
+ rRowStart = nRowStartNew;
+ rRowEnd = nRowEndNew;
+ return true;
+}
+
+SCROW ScColumn::FindNextVisibleRow(SCROW nRow, bool bForward) const
+{
+ if(bForward)
+ {
+ nRow++;
+ SCROW nEndRow = 0;
+ bool bHidden = GetDoc().RowHidden(nRow, nTab, nullptr, &nEndRow);
+ if(bHidden)
+ return std::min<SCROW>(GetDoc().MaxRow(), nEndRow + 1);
+ else
+ return nRow;
+ }
+ else
+ {
+ nRow--;
+ SCROW nStartRow = GetDoc().MaxRow();
+ bool bHidden = GetDoc().RowHidden(nRow, nTab, &nStartRow);
+ if(bHidden)
+ return std::max<SCROW>(0, nStartRow - 1);
+ else
+ return nRow;
+ }
+}
+
+SCROW ScColumn::FindNextVisibleRowWithContent(
+ sc::CellStoreType::const_iterator& itPos, SCROW nRow, bool bForward) const
+{
+ ScDocument& rDocument = GetDoc();
+ if (bForward)
+ {
+ do
+ {
+ nRow++;
+ SCROW nEndRow = 0;
+ bool bHidden = rDocument.RowHidden(nRow, nTab, nullptr, &nEndRow);
+ if (bHidden)
+ {
+ nRow = nEndRow + 1;
+ if(nRow >= GetDoc().MaxRow())
+ return GetDoc().MaxRow();
+ }
+
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(itPos, nRow);
+ itPos = aPos.first;
+ if (itPos == maCells.end())
+ // Invalid row.
+ return GetDoc().MaxRow();
+
+ if (itPos->type != sc::element_type_empty)
+ return nRow;
+
+ // Move to the last cell of the current empty block.
+ nRow += itPos->size - aPos.second - 1;
+ }
+ while (nRow < GetDoc().MaxRow());
+
+ return GetDoc().MaxRow();
+ }
+
+ do
+ {
+ nRow--;
+ SCROW nStartRow = GetDoc().MaxRow();
+ bool bHidden = rDocument.RowHidden(nRow, nTab, &nStartRow);
+ if (bHidden)
+ {
+ nRow = nStartRow - 1;
+ if(nRow <= 0)
+ return 0;
+ }
+
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(itPos, nRow);
+ itPos = aPos.first;
+ if (itPos == maCells.end())
+ // Invalid row.
+ return 0;
+
+ if (itPos->type != sc::element_type_empty)
+ return nRow;
+
+ // Move to the first cell of the current empty block.
+ nRow -= aPos.second;
+ }
+ while (nRow > 0);
+
+ return 0;
+}
+
+void ScColumn::CellStorageModified()
+{
+ // Remove cached values. Given how often this function is called and how (not that) often
+ // the cached values are used, it should be more efficient to just discard everything
+ // instead of trying to figure out each time exactly what to discard.
+ GetDoc().DiscardFormulaGroupContext();
+
+ // TODO: Update column's "last updated" timestamp here.
+
+ assert(sal::static_int_cast<SCROW>(maCells.size()) == GetDoc().GetMaxRowCount()
+ && "Size of the cell array is incorrect." );
+
+ assert(sal::static_int_cast<SCROW>(maCellTextAttrs.size()) == GetDoc().GetMaxRowCount()
+ && "Size of the cell text attribute array is incorrect.");
+
+ assert(sal::static_int_cast<SCROW>(maBroadcasters.size()) == GetDoc().GetMaxRowCount()
+ && "Size of the broadcaster array is incorrect.");
+
+#if DEBUG_COLUMN_STORAGE
+ // Make sure that these two containers are synchronized wrt empty segments.
+ auto lIsEmptyType = [](const auto& rElement) { return rElement.type == sc::element_type_empty; };
+ // Move to the first empty blocks.
+ auto itCell = std::find_if(maCells.begin(), maCells.end(), lIsEmptyType);
+ auto itAttr = std::find_if(maCellTextAttrs.begin(), maCellTextAttrs.end(), lIsEmptyType);
+
+ while (itCell != maCells.end())
+ {
+ if (itCell->position != itAttr->position || itCell->size != itAttr->size)
+ {
+ cout << "ScColumn::CellStorageModified: Cell array and cell text attribute array are out of sync." << endl;
+ cout << "-- cell array" << endl;
+ maCells.dump_blocks(cout);
+ cout << "-- attribute array" << endl;
+ maCellTextAttrs.dump_blocks(cout);
+ cout.flush();
+ abort();
+ }
+
+ // Move to the next empty blocks.
+ ++itCell;
+ itCell = std::find_if(itCell, maCells.end(), lIsEmptyType);
+
+ ++itAttr;
+ itAttr = std::find_if(itAttr, maCellTextAttrs.end(), lIsEmptyType);
+ }
+#endif
+}
+
+#if DUMP_COLUMN_STORAGE
+
+namespace {
+
+#define DUMP_FORMULA_RESULTS 0
+
+struct ColumnStorageDumper
+{
+ const ScDocument& mrDoc;
+
+ ColumnStorageDumper( const ScDocument& rDoc ) : mrDoc(rDoc) {}
+
+ void operator() (const sc::CellStoreType::value_type& rNode) const
+ {
+ switch (rNode.type)
+ {
+ case sc::element_type_numeric:
+ cout << " * numeric block (pos=" << rNode.position << ", length=" << rNode.size << ")" << endl;
+ break;
+ case sc::element_type_string:
+ cout << " * string block (pos=" << rNode.position << ", length=" << rNode.size << ")" << endl;
+ break;
+ case sc::element_type_edittext:
+ cout << " * edit-text block (pos=" << rNode.position << ", length=" << rNode.size << ")" << endl;
+ break;
+ case sc::element_type_formula:
+ dumpFormulaBlock(rNode);
+ break;
+ case sc::element_type_empty:
+ cout << " * empty block (pos=" << rNode.position << ", length=" << rNode.size << ")" << endl;
+ break;
+ default:
+ cout << " * unknown block" << endl;
+ }
+ }
+
+ void dumpFormulaBlock(const sc::CellStoreType::value_type& rNode) const
+ {
+ cout << " * formula block (pos=" << rNode.position << ", length=" << rNode.size << ")" << endl;
+ sc::formula_block::const_iterator it = sc::formula_block::begin(*rNode.data);
+ sc::formula_block::const_iterator itEnd = sc::formula_block::end(*rNode.data);
+
+ for (; it != itEnd; ++it)
+ {
+ const ScFormulaCell* pCell = *it;
+ if (!pCell->IsShared())
+ {
+ cout << " * row " << pCell->aPos.Row() << " not shared" << endl;
+ printFormula(pCell);
+ printResult(pCell);
+ continue;
+ }
+
+ if (pCell->GetSharedTopRow() != pCell->aPos.Row())
+ {
+ cout << " * row " << pCell->aPos.Row() << " shared with top row "
+ << pCell->GetSharedTopRow() << " with length " << pCell->GetSharedLength()
+ << endl;
+ continue;
+ }
+
+ SCROW nLen = pCell->GetSharedLength();
+ cout << " * group: start=" << pCell->aPos.Row() << ", length=" << nLen << endl;
+ printFormula(pCell);
+ printResult(pCell);
+
+ if (nLen > 1)
+ {
+ for (SCROW i = 0; i < nLen-1; ++i, ++it)
+ {
+ pCell = *it;
+ printResult(pCell);
+ }
+ }
+ }
+ }
+
+ void printFormula(const ScFormulaCell* pCell) const
+ {
+ sc::TokenStringContext aCxt(mrDoc, mrDoc.GetGrammar());
+ OUString aFormula = pCell->GetCode()->CreateString(aCxt, pCell->aPos);
+ cout << " * formula: " << aFormula << endl;
+ }
+
+#if DUMP_FORMULA_RESULTS
+ void printResult(const ScFormulaCell* pCell) const
+ {
+ sc::FormulaResultValue aRes = pCell->GetResult();
+ cout << " * result: ";
+ switch (aRes.meType)
+ {
+ case sc::FormulaResultValue::Value:
+ cout << aRes.mfValue << " (type: value)";
+ break;
+ case sc::FormulaResultValue::String:
+ cout << "'" << aRes.maString.getString() << "' (type: string)";
+ break;
+ case sc::FormulaResultValue::Error:
+ cout << "error (" << static_cast<int>(aRes.mnError) << ")";
+ break;
+ case sc::FormulaResultValue::Invalid:
+ cout << "invalid";
+ break;
+ }
+
+ cout << endl;
+ }
+#else
+ void printResult(const ScFormulaCell*) const
+ {
+ (void) this; /* loplugin:staticmethods */
+ }
+#endif
+};
+
+}
+
+void ScColumn::DumpColumnStorage() const
+{
+ cout << "-- table: " << nTab << "; column: " << nCol << endl;
+ std::for_each(maCells.begin(), maCells.end(), ColumnStorageDumper(GetDoc()));
+ cout << "--" << endl;
+}
+#endif
+
+void ScColumn::CopyCellTextAttrsToDocument(SCROW nRow1, SCROW nRow2, ScColumn& rDestCol) const
+{
+ rDestCol.maCellTextAttrs.set_empty(nRow1, nRow2); // Empty the destination range first.
+
+ sc::CellTextAttrStoreType::const_iterator itBlk = maCellTextAttrs.begin(), itBlkEnd = maCellTextAttrs.end();
+
+ // Locate the top row position.
+ size_t nBlockStart = 0, nRowPos = static_cast<size_t>(nRow1);
+ itBlk = std::find_if(itBlk, itBlkEnd, [&nRowPos, &nBlockStart](const auto& rAttr) {
+ return nBlockStart <= nRowPos && nRowPos < nBlockStart + rAttr.size; });
+
+ if (itBlk == itBlkEnd)
+ // Specified range not found. Bail out.
+ return;
+
+ size_t nBlockEnd;
+ size_t nOffsetInBlock = nRowPos - nBlockStart;
+
+ nRowPos = static_cast<size_t>(nRow2); // End row position.
+
+ // Keep copying until we hit the end row position.
+ sc::celltextattr_block::const_iterator itData, itDataEnd;
+ for (; itBlk != itBlkEnd; ++itBlk, nBlockStart = nBlockEnd, nOffsetInBlock = 0)
+ {
+ nBlockEnd = nBlockStart + itBlk->size;
+ if (!itBlk->data)
+ {
+ // Empty block.
+ if (nBlockStart <= nRowPos && nRowPos < nBlockEnd)
+ // This block contains the end row.
+ rDestCol.maCellTextAttrs.set_empty(nBlockStart + nOffsetInBlock, nRowPos);
+ else
+ rDestCol.maCellTextAttrs.set_empty(nBlockStart + nOffsetInBlock, nBlockEnd-1);
+
+ continue;
+ }
+
+ // Non-empty block.
+ itData = sc::celltextattr_block::begin(*itBlk->data);
+ itDataEnd = sc::celltextattr_block::end(*itBlk->data);
+ std::advance(itData, nOffsetInBlock);
+
+ if (nBlockStart <= nRowPos && nRowPos < nBlockEnd)
+ {
+ // This block contains the end row. Only copy partially.
+ size_t nOffset = nRowPos - nBlockStart + 1;
+ itDataEnd = sc::celltextattr_block::begin(*itBlk->data);
+ std::advance(itDataEnd, nOffset);
+
+ rDestCol.maCellTextAttrs.set(nBlockStart + nOffsetInBlock, itData, itDataEnd);
+ break;
+ }
+
+ rDestCol.maCellTextAttrs.set(nBlockStart + nOffsetInBlock, itData, itDataEnd);
+ }
+}
+
+namespace {
+
+class CopyCellNotesHandler
+{
+ ScColumn& mrDestCol;
+ sc::CellNoteStoreType& mrDestNotes;
+ sc::CellNoteStoreType::iterator miPos;
+ SCTAB mnSrcTab;
+ SCCOL mnSrcCol;
+ SCTAB mnDestTab;
+ SCCOL mnDestCol;
+ SCROW mnDestOffset; /// Add this to the source row position to get the destination row.
+ bool mbCloneCaption;
+
+public:
+ CopyCellNotesHandler( const ScColumn& rSrcCol, ScColumn& rDestCol, SCROW nDestOffset, bool bCloneCaption ) :
+ mrDestCol(rDestCol),
+ mrDestNotes(rDestCol.GetCellNoteStore()),
+ miPos(mrDestNotes.begin()),
+ mnSrcTab(rSrcCol.GetTab()),
+ mnSrcCol(rSrcCol.GetCol()),
+ mnDestTab(rDestCol.GetTab()),
+ mnDestCol(rDestCol.GetCol()),
+ mnDestOffset(nDestOffset),
+ mbCloneCaption(bCloneCaption) {}
+
+ void operator() ( size_t nRow, const ScPostIt* p )
+ {
+ SCROW nDestRow = nRow + mnDestOffset;
+ ScAddress aSrcPos(mnSrcCol, nRow, mnSrcTab);
+ ScAddress aDestPos(mnDestCol, nDestRow, mnDestTab);
+ ScPostIt* pNew = p->Clone(aSrcPos, mrDestCol.GetDoc(), aDestPos, mbCloneCaption).release();
+ miPos = mrDestNotes.set(miPos, nDestRow, pNew);
+ // Notify our LOK clients also
+ ScDocShell::LOKCommentNotify(LOKCommentNotificationType::Add, &mrDestCol.GetDoc(), aDestPos, pNew);
+ }
+};
+
+}
+
+void ScColumn::CopyCellNotesToDocument(
+ SCROW nRow1, SCROW nRow2, ScColumn& rDestCol, bool bCloneCaption, SCROW nRowOffsetDest ) const
+{
+ if (IsNotesEmptyBlock(nRow1, nRow2))
+ // The column has no cell notes to copy between specified rows.
+ return;
+
+ ScDrawLayer *pDrawLayer = rDestCol.GetDoc().GetDrawLayer();
+ bool bWasLocked = bool();
+ if (pDrawLayer)
+ {
+ // Avoid O(n^2) by temporary locking SdrModel which disables broadcasting.
+ // Each cell note adds undo listener, and all of them would be woken up in ScPostIt::CreateCaption.
+ bWasLocked = pDrawLayer->isLocked();
+ pDrawLayer->setLock(true);
+ }
+ CopyCellNotesHandler aFunc(*this, rDestCol, nRowOffsetDest, bCloneCaption);
+ sc::ParseNote(maCellNotes.begin(), maCellNotes, nRow1, nRow2, aFunc);
+ if (pDrawLayer)
+ pDrawLayer->setLock(bWasLocked);
+}
+
+void ScColumn::DuplicateNotes(SCROW nStartRow, size_t nDataSize, ScColumn& rDestCol, sc::ColumnBlockPosition& maDestBlockPos,
+ bool bCloneCaption, SCROW nRowOffsetDest ) const
+{
+ CopyCellNotesToDocument(nStartRow, nStartRow + nDataSize -1, rDestCol, bCloneCaption, nRowOffsetDest);
+ maDestBlockPos.miCellNotePos = rDestCol.maCellNotes.begin();
+}
+
+SvtBroadcaster* ScColumn::GetBroadcaster(SCROW nRow)
+{
+ return maBroadcasters.get<SvtBroadcaster*>(nRow);
+}
+
+const SvtBroadcaster* ScColumn::GetBroadcaster(SCROW nRow) const
+{
+ return maBroadcasters.get<SvtBroadcaster*>(nRow);
+}
+
+void ScColumn::DeleteBroadcasters( sc::ColumnBlockPosition& rBlockPos, SCROW nRow1, SCROW nRow2 )
+{
+ rBlockPos.miBroadcasterPos =
+ maBroadcasters.set_empty(rBlockPos.miBroadcasterPos, nRow1, nRow2);
+}
+
+void ScColumn::PrepareBroadcastersForDestruction()
+{
+ for (auto& rBroadcaster : maBroadcasters)
+ {
+ if (rBroadcaster.type == sc::element_type_broadcaster)
+ {
+ sc::broadcaster_block::iterator it = sc::broadcaster_block::begin(*rBroadcaster.data);
+ sc::broadcaster_block::iterator itEnd = sc::broadcaster_block::end(*rBroadcaster.data);
+ for (; it != itEnd; ++it)
+ (*it)->PrepareForDestruction();
+ }
+ }
+}
+
+namespace
+{
+struct BroadcasterNoListenersPredicate
+{
+ bool operator()( size_t, const SvtBroadcaster* broadcaster )
+ {
+ return !broadcaster->HasListeners();
+ }
+};
+
+}
+
+void ScColumn::DeleteEmptyBroadcasters()
+{
+ if(!mbEmptyBroadcastersPending)
+ return;
+ // Clean up after ScDocument::EnableDelayDeletingBroadcasters().
+ BroadcasterNoListenersPredicate predicate;
+ sc::SetElementsToEmpty1<sc::broadcaster_block>( maBroadcasters, predicate );
+ mbEmptyBroadcastersPending = false;
+}
+
+// Sparklines
+
+namespace
+{
+
+class DeletingSparklinesHandler
+{
+ ScDocument& m_rDocument;
+ SCTAB m_nTab;
+
+public:
+ DeletingSparklinesHandler(ScDocument& rDocument, SCTAB nTab)
+ : m_rDocument(rDocument)
+ , m_nTab(nTab)
+ {}
+
+ void operator() (size_t /*nRow*/, const sc::SparklineCell* pCell)
+ {
+ auto* pList = m_rDocument.GetSparklineList(m_nTab);
+ pList->removeSparkline(pCell->getSparkline());
+ }
+};
+
+} // end anonymous ns
+
+sc::SparklineCell* ScColumn::GetSparklineCell(SCROW nRow)
+{
+ return maSparklines.get<sc::SparklineCell*>(nRow);
+}
+
+void ScColumn::CreateSparklineCell(SCROW nRow, std::shared_ptr<sc::Sparkline> const& pSparkline)
+{
+ auto* pList = GetDoc().GetSparklineList(GetTab());
+ pList->addSparkline(pSparkline);
+ maSparklines.set(nRow, new sc::SparklineCell(pSparkline));
+}
+
+void ScColumn::DeleteSparklineCells(sc::ColumnBlockPosition& rBlockPos, SCROW nRow1, SCROW nRow2)
+{
+ DeletingSparklinesHandler aFunction(GetDoc(), nTab);
+ sc::ParseSparkline(maSparklines.begin(), maSparklines, nRow1, nRow2, aFunction);
+
+ rBlockPos.miSparklinePos = maSparklines.set_empty(rBlockPos.miSparklinePos, nRow1, nRow2);
+}
+
+bool ScColumn::DeleteSparkline(SCROW nRow)
+{
+ if (!GetDoc().ValidRow(nRow))
+ return false;
+
+ DeletingSparklinesHandler aFunction(GetDoc(), nTab);
+ sc::ParseSparkline(maSparklines.begin(), maSparklines, nRow, nRow, aFunction);
+
+ maSparklines.set_empty(nRow, nRow);
+ return true;
+}
+
+bool ScColumn::IsSparklinesEmptyBlock(SCROW nStartRow, SCROW nEndRow) const
+{
+ std::pair<sc::SparklineStoreType::const_iterator,size_t> aPos = maSparklines.position(nStartRow);
+ sc::SparklineStoreType::const_iterator it = aPos.first;
+ if (it == maSparklines.end())
+ return false;
+
+ if (it->type != sc::element_type_empty)
+ return false;
+
+ // start position of next block which is not empty.
+ SCROW nNextRow = nStartRow + it->size - aPos.second;
+ return nEndRow < nNextRow;
+}
+
+namespace
+{
+
+class CopySparklinesHandler
+{
+ ScColumn& mrDestColumn;
+ sc::SparklineStoreType& mrDestSparkline;
+ sc::SparklineStoreType::iterator miDestPosition;
+ SCROW mnDestOffset;
+
+public:
+ CopySparklinesHandler(ScColumn& rDestColumn, SCROW nDestOffset)
+ : mrDestColumn(rDestColumn)
+ , mrDestSparkline(mrDestColumn.GetSparklineStore())
+ , miDestPosition(mrDestSparkline.begin())
+ , mnDestOffset(nDestOffset)
+ {}
+
+ void operator() (size_t nRow, const sc::SparklineCell* pCell)
+ {
+ SCROW nDestRow = nRow + mnDestOffset;
+
+ auto const& pSparkline = pCell->getSparkline();
+ auto const& pGroup = pCell->getSparklineGroup();
+
+ auto& rDestDoc = mrDestColumn.GetDoc();
+ auto pDestinationGroup = rDestDoc.SearchSparklineGroup(pGroup->getID());
+ if (!pDestinationGroup)
+ pDestinationGroup = std::make_shared<sc::SparklineGroup>(*pGroup); // Copy the group
+ auto pNewSparkline = std::make_shared<sc::Sparkline>(mrDestColumn.GetCol(), nDestRow, pDestinationGroup);
+ pNewSparkline->setInputRange(pSparkline->getInputRange());
+
+ auto* pList = rDestDoc.GetSparklineList(mrDestColumn.GetTab());
+ pList->addSparkline(pNewSparkline);
+
+ miDestPosition = mrDestSparkline.set(miDestPosition, nDestRow, new sc::SparklineCell(pNewSparkline));
+ }
+};
+
+}
+
+void ScColumn::CopyCellSparklinesToDocument(SCROW nRow1, SCROW nRow2, ScColumn& rDestCol, SCROW nRowOffsetDest) const
+{
+ if (IsSparklinesEmptyBlock(nRow1, nRow2))
+ // The column has no cell sparklines to copy between specified rows.
+ return;
+
+ CopySparklinesHandler aFunctor(rDestCol, nRowOffsetDest);
+ sc::ParseSparkline(maSparklines.begin(), maSparklines, nRow1, nRow2, aFunctor);
+}
+
+void ScColumn::DuplicateSparklines(SCROW nStartRow, size_t nDataSize, ScColumn& rDestCol,
+ sc::ColumnBlockPosition& rDestBlockPos, SCROW nRowOffsetDest) const
+{
+ CopyCellSparklinesToDocument(nStartRow, nStartRow + nDataSize - 1, rDestCol, nRowOffsetDest);
+ rDestBlockPos.miSparklinePos = rDestCol.maSparklines.begin();
+}
+
+bool ScColumn::HasSparklines() const
+{
+ if (maSparklines.block_size() == 1 && maSparklines.begin()->type == sc::element_type_empty)
+ return false; // all elements are empty
+ return true; // otherwise some must be sparklines
+}
+
+SCROW ScColumn::GetSparklinesMaxRow() const
+{
+ SCROW maxRow = 0;
+ for (const auto& rSparkline : maSparklines)
+ {
+ if (rSparkline.type == sc::element_type_sparkline)
+ maxRow = rSparkline.position + rSparkline.size - 1;
+ }
+ return maxRow;
+}
+
+SCROW ScColumn::GetSparklinesMinRow() const
+{
+ SCROW minRow = 0;
+ sc::SparklineStoreType::const_iterator it = std::find_if(maSparklines.begin(), maSparklines.end(),
+ [](const auto& rSparkline)
+ {
+ return rSparkline.type == sc::element_type_sparkline;
+ });
+ if (it != maSparklines.end())
+ minRow = it->position;
+ return minRow;
+}
+
+// Notes
+
+ScPostIt* ScColumn::GetCellNote(SCROW nRow)
+{
+ return maCellNotes.get<ScPostIt*>(nRow);
+}
+
+const ScPostIt* ScColumn::GetCellNote(SCROW nRow) const
+{
+ return maCellNotes.get<ScPostIt*>(nRow);
+}
+
+const ScPostIt* ScColumn::GetCellNote( sc::ColumnBlockConstPosition& rBlockPos, SCROW nRow ) const
+{
+ sc::CellNoteStoreType::const_position_type aPos = maCellNotes.position(rBlockPos.miCellNotePos, nRow);
+ rBlockPos.miCellNotePos = aPos.first;
+
+ if (aPos.first->type != sc::element_type_cellnote)
+ return nullptr;
+
+ return sc::cellnote_block::at(*aPos.first->data, aPos.second);
+}
+
+ScPostIt* ScColumn::GetCellNote( sc::ColumnBlockConstPosition& rBlockPos, SCROW nRow )
+{
+ return const_cast<ScPostIt*>(const_cast<const ScColumn*>(this)->GetCellNote( rBlockPos, nRow ));
+}
+
+void ScColumn::SetCellNote(SCROW nRow, std::unique_ptr<ScPostIt> pNote)
+{
+ //pNote->UpdateCaptionPos(ScAddress(nCol, nRow, nTab)); // TODO notes useful ? slow import with many notes
+ maCellNotes.set(nRow, pNote.release());
+}
+
+namespace {
+ class CellNoteHandler
+ {
+ const ScDocument* m_pDocument;
+ const ScAddress m_aAddress; // 'incomplete' address consisting of tab, column
+ const bool m_bForgetCaptionOwnership;
+
+ public:
+ CellNoteHandler(const ScDocument* pDocument, const ScAddress& rPos, bool bForgetCaptionOwnership) :
+ m_pDocument(pDocument),
+ m_aAddress(rPos),
+ m_bForgetCaptionOwnership(bForgetCaptionOwnership) {}
+
+ void operator() ( size_t nRow, ScPostIt* p )
+ {
+ if (m_bForgetCaptionOwnership)
+ p->ForgetCaption();
+
+ // Create a 'complete' address object
+ ScAddress aAddr(m_aAddress);
+ aAddr.SetRow(nRow);
+ // Notify our LOK clients
+ ScDocShell::LOKCommentNotify(LOKCommentNotificationType::Remove, m_pDocument, aAddr, p);
+ }
+ };
+} // anonymous namespace
+
+void ScColumn::CellNotesDeleting(SCROW nRow1, SCROW nRow2, bool bForgetCaptionOwnership)
+{
+ ScAddress aAddr(nCol, 0, nTab);
+ CellNoteHandler aFunc(&GetDoc(), aAddr, bForgetCaptionOwnership);
+ sc::ParseNote(maCellNotes.begin(), maCellNotes, nRow1, nRow2, aFunc);
+}
+
+void ScColumn::DeleteCellNotes( sc::ColumnBlockPosition& rBlockPos, SCROW nRow1, SCROW nRow2, bool bForgetCaptionOwnership )
+{
+ CellNotesDeleting(nRow1, nRow2, bForgetCaptionOwnership);
+
+ rBlockPos.miCellNotePos =
+ maCellNotes.set_empty(rBlockPos.miCellNotePos, nRow1, nRow2);
+}
+
+bool ScColumn::HasCellNotes() const
+{
+ if (maCellNotes.block_size() == 1 && maCellNotes.begin()->type == sc::element_type_empty)
+ return false; // all elements are empty
+ return true; // otherwise some must be notes
+}
+
+SCROW ScColumn::GetCellNotesMaxRow() const
+{
+ // hypothesis : the column has cell notes (should be checked before)
+ SCROW maxRow = 0;
+ for (const auto& rCellNote : maCellNotes)
+ {
+ if (rCellNote.type == sc::element_type_cellnote)
+ maxRow = rCellNote.position + rCellNote.size -1;
+ }
+ return maxRow;
+}
+SCROW ScColumn::GetCellNotesMinRow() const
+{
+ // hypothesis : the column has cell notes (should be checked before)
+ SCROW minRow = 0;
+ sc::CellNoteStoreType::const_iterator it = std::find_if(maCellNotes.begin(), maCellNotes.end(),
+ [](const auto& rCellNote) { return rCellNote.type == sc::element_type_cellnote; });
+ if (it != maCellNotes.end())
+ minRow = it->position;
+ return minRow;
+}
+
+sal_uInt16 ScColumn::GetTextWidth(SCROW nRow) const
+{
+ return maCellTextAttrs.get<sc::CellTextAttr>(nRow).mnTextWidth;
+}
+
+void ScColumn::SetTextWidth(SCROW nRow, sal_uInt16 nWidth)
+{
+ sc::CellTextAttrStoreType::position_type aPos = maCellTextAttrs.position(nRow);
+ if (aPos.first->type != sc::element_type_celltextattr)
+ return;
+
+ // Set new value only when the slot is not empty.
+ sc::celltextattr_block::at(*aPos.first->data, aPos.second).mnTextWidth = nWidth;
+ CellStorageModified();
+}
+
+SvtScriptType ScColumn::GetScriptType( SCROW nRow ) const
+{
+ if (!GetDoc().ValidRow(nRow) || maCellTextAttrs.is_empty(nRow))
+ return SvtScriptType::NONE;
+
+ return maCellTextAttrs.get<sc::CellTextAttr>(nRow).mnScriptType;
+}
+
+SvtScriptType ScColumn::GetRangeScriptType(
+ sc::CellTextAttrStoreType::iterator& itPos, SCROW nRow1, SCROW nRow2, const sc::CellStoreType::iterator& itrCells_ )
+{
+ if (!GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2) || nRow1 > nRow2)
+ return SvtScriptType::NONE;
+
+ SCROW nRow = nRow1;
+ std::pair<sc::CellTextAttrStoreType::iterator,size_t> aRet =
+ maCellTextAttrs.position(itPos, nRow1);
+
+ itPos = aRet.first; // Track the position of cell text attribute array.
+ sc::CellStoreType::iterator itrCells = itrCells_;
+
+ SvtScriptType nScriptType = SvtScriptType::NONE;
+ bool bUpdated = false;
+ if (itPos->type == sc::element_type_celltextattr)
+ {
+ sc::celltextattr_block::iterator it = sc::celltextattr_block::begin(*itPos->data);
+ sc::celltextattr_block::iterator itEnd = sc::celltextattr_block::end(*itPos->data);
+ std::advance(it, aRet.second);
+ for (; it != itEnd; ++it, ++nRow)
+ {
+ if (nRow > nRow2)
+ return nScriptType;
+
+ sc::CellTextAttr& rVal = *it;
+ if (UpdateScriptType(rVal, nRow, itrCells))
+ bUpdated = true;
+ nScriptType |= rVal.mnScriptType;
+ }
+ }
+ else
+ {
+ // Skip this whole block.
+ nRow += itPos->size - aRet.second;
+ }
+
+ while (nRow <= nRow2)
+ {
+ ++itPos;
+ if (itPos == maCellTextAttrs.end())
+ return nScriptType;
+
+ if (itPos->type != sc::element_type_celltextattr)
+ {
+ // Skip this whole block.
+ nRow += itPos->size;
+ continue;
+ }
+
+ sc::celltextattr_block::iterator it = sc::celltextattr_block::begin(*itPos->data);
+ sc::celltextattr_block::iterator itEnd = sc::celltextattr_block::end(*itPos->data);
+ for (; it != itEnd; ++it, ++nRow)
+ {
+ if (nRow > nRow2)
+ return nScriptType;
+
+ sc::CellTextAttr& rVal = *it;
+ if (UpdateScriptType(rVal, nRow, itrCells))
+ bUpdated = true;
+
+ nScriptType |= rVal.mnScriptType;
+ }
+ }
+
+ if (bUpdated)
+ CellStorageModified();
+
+ return nScriptType;
+}
+
+void ScColumn::SetScriptType( SCROW nRow, SvtScriptType nType )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return;
+
+ sc::CellTextAttrStoreType::position_type aPos = maCellTextAttrs.position(nRow);
+ if (aPos.first->type != sc::element_type_celltextattr)
+ // Set new value only when the slot is already set.
+ return;
+
+ sc::celltextattr_block::at(*aPos.first->data, aPos.second).mnScriptType = nType;
+ CellStorageModified();
+}
+
+formula::FormulaTokenRef ScColumn::ResolveStaticReference( SCROW nRow )
+{
+ std::pair<sc::CellStoreType::iterator,size_t> aPos = maCells.position(nRow);
+ sc::CellStoreType::iterator it = aPos.first;
+ if (it == maCells.end())
+ // Invalid row. Return a null token.
+ return formula::FormulaTokenRef();
+
+ switch (it->type)
+ {
+ case sc::element_type_numeric:
+ {
+ double fVal = sc::numeric_block::at(*it->data, aPos.second);
+ return formula::FormulaTokenRef(new formula::FormulaDoubleToken(fVal));
+ }
+ case sc::element_type_formula:
+ {
+ ScFormulaCell* p = sc::formula_block::at(*it->data, aPos.second);
+ if (p->IsValue())
+ return formula::FormulaTokenRef(new formula::FormulaDoubleToken(p->GetValue()));
+
+ return formula::FormulaTokenRef(new formula::FormulaStringToken(p->GetString()));
+ }
+ case sc::element_type_string:
+ {
+ const svl::SharedString& rSS = sc::string_block::at(*it->data, aPos.second);
+ return formula::FormulaTokenRef(new formula::FormulaStringToken(rSS));
+ }
+ case sc::element_type_edittext:
+ {
+ const EditTextObject* pText = sc::edittext_block::at(*it->data, aPos.second);
+ OUString aStr = ScEditUtil::GetString(*pText, &GetDoc());
+ svl::SharedString aSS( GetDoc().GetSharedStringPool().intern(aStr));
+ return formula::FormulaTokenRef(new formula::FormulaStringToken(aSS));
+ }
+ case sc::element_type_empty:
+ default:
+ // Return a value of 0.0 in all the other cases.
+ return formula::FormulaTokenRef(new formula::FormulaDoubleToken(0.0));
+ }
+}
+
+namespace {
+
+class ToMatrixHandler
+{
+ ScMatrix& mrMat;
+ SCCOL mnMatCol;
+ SCROW mnTopRow;
+ ScDocument* mpDoc;
+ svl::SharedStringPool& mrStrPool;
+public:
+ ToMatrixHandler(ScMatrix& rMat, SCCOL nMatCol, SCROW nTopRow, ScDocument* pDoc) :
+ mrMat(rMat), mnMatCol(nMatCol), mnTopRow(nTopRow),
+ mpDoc(pDoc), mrStrPool(pDoc->GetSharedStringPool()) {}
+
+ void operator() (size_t nRow, double fVal)
+ {
+ mrMat.PutDouble(fVal, mnMatCol, nRow - mnTopRow);
+ }
+
+ void operator() (size_t nRow, const ScFormulaCell* p)
+ {
+ // Formula cell may need to re-calculate.
+ ScFormulaCell& rCell = const_cast<ScFormulaCell&>(*p);
+ if (rCell.IsValue())
+ mrMat.PutDouble(rCell.GetValue(), mnMatCol, nRow - mnTopRow);
+ else
+ mrMat.PutString(rCell.GetString(), mnMatCol, nRow - mnTopRow);
+ }
+
+ void operator() (size_t nRow, const svl::SharedString& rSS)
+ {
+ mrMat.PutString(rSS, mnMatCol, nRow - mnTopRow);
+ }
+
+ void operator() (size_t nRow, const EditTextObject* pStr)
+ {
+ mrMat.PutString(mrStrPool.intern(ScEditUtil::GetString(*pStr, mpDoc)), mnMatCol, nRow - mnTopRow);
+ }
+};
+
+}
+
+bool ScColumn::ResolveStaticReference( ScMatrix& rMat, SCCOL nMatCol, SCROW nRow1, SCROW nRow2 )
+{
+ if (nRow1 > nRow2)
+ return false;
+
+ ToMatrixHandler aFunc(rMat, nMatCol, nRow1, &GetDoc());
+ sc::ParseAllNonEmpty(maCells.begin(), maCells, nRow1, nRow2, aFunc);
+ return true;
+}
+
+namespace {
+
+struct CellBucket
+{
+ SCSIZE mnEmpValStart;
+ SCSIZE mnNumValStart;
+ SCSIZE mnStrValStart;
+ SCSIZE mnEmpValCount;
+ std::vector<double> maNumVals;
+ std::vector<svl::SharedString> maStrVals;
+
+ CellBucket() : mnEmpValStart(0), mnNumValStart(0), mnStrValStart(0), mnEmpValCount(0) {}
+
+ void flush(ScMatrix& rMat, SCSIZE nCol)
+ {
+ if (mnEmpValCount)
+ {
+ rMat.PutEmptyResultVector(mnEmpValCount, nCol, mnEmpValStart);
+ reset();
+ }
+ else if (!maNumVals.empty())
+ {
+ const double* p = maNumVals.data();
+ rMat.PutDouble(p, maNumVals.size(), nCol, mnNumValStart);
+ reset();
+ }
+ else if (!maStrVals.empty())
+ {
+ const svl::SharedString* p = maStrVals.data();
+ rMat.PutString(p, maStrVals.size(), nCol, mnStrValStart);
+ reset();
+ }
+ }
+
+ void reset()
+ {
+ mnEmpValStart = mnNumValStart = mnStrValStart = 0;
+ mnEmpValCount = 0;
+ maNumVals.clear();
+ maStrVals.clear();
+ }
+};
+
+class FillMatrixHandler
+{
+ ScMatrix& mrMat;
+ size_t mnMatCol;
+ size_t mnTopRow;
+
+ ScDocument* mpDoc;
+ svl::SharedStringPool& mrPool;
+ svl::SharedStringPool* mpPool; // if matrix is not in the same document
+
+public:
+ FillMatrixHandler(ScMatrix& rMat, size_t nMatCol, size_t nTopRow, ScDocument* pDoc, svl::SharedStringPool* pPool) :
+ mrMat(rMat), mnMatCol(nMatCol), mnTopRow(nTopRow),
+ mpDoc(pDoc), mrPool(pDoc->GetSharedStringPool()), mpPool(pPool) {}
+
+ void operator() (const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize)
+ {
+ size_t nMatRow = node.position + nOffset - mnTopRow;
+
+ switch (node.type)
+ {
+ case sc::element_type_numeric:
+ {
+ const double* p = &sc::numeric_block::at(*node.data, nOffset);
+ mrMat.PutDouble(p, nDataSize, mnMatCol, nMatRow);
+ }
+ break;
+ case sc::element_type_string:
+ {
+ if (!mpPool)
+ {
+ const svl::SharedString* p = &sc::string_block::at(*node.data, nOffset);
+ mrMat.PutString(p, nDataSize, mnMatCol, nMatRow);
+ }
+ else
+ {
+ std::vector<svl::SharedString> aStrings;
+ aStrings.reserve(nDataSize);
+ const svl::SharedString* p = &sc::string_block::at(*node.data, nOffset);
+ for (size_t i = 0; i < nDataSize; ++i)
+ {
+ aStrings.push_back(mpPool->intern(p[i].getString()));
+ }
+ mrMat.PutString(aStrings.data(), aStrings.size(), mnMatCol, nMatRow);
+ }
+ }
+ break;
+ case sc::element_type_edittext:
+ {
+ std::vector<svl::SharedString> aSSs;
+ aSSs.reserve(nDataSize);
+ sc::edittext_block::const_iterator it = sc::edittext_block::begin(*node.data);
+ std::advance(it, nOffset);
+ sc::edittext_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+ for (; it != itEnd; ++it)
+ {
+ OUString aStr = ScEditUtil::GetString(**it, mpDoc);
+ if (!mpPool)
+ aSSs.push_back(mrPool.intern(aStr));
+ else
+ aSSs.push_back(mpPool->intern(aStr));
+ }
+
+ const svl::SharedString* p = aSSs.data();
+ mrMat.PutString(p, nDataSize, mnMatCol, nMatRow);
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ CellBucket aBucket;
+ sc::formula_block::const_iterator it = sc::formula_block::begin(*node.data);
+ std::advance(it, nOffset);
+ sc::formula_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+
+ size_t nPrevRow = 0, nThisRow = node.position + nOffset;
+ for (; it != itEnd; ++it, nPrevRow = nThisRow, ++nThisRow)
+ {
+ ScFormulaCell& rCell = **it;
+
+ if (rCell.IsEmpty())
+ {
+ if (aBucket.mnEmpValCount && nThisRow == nPrevRow + 1)
+ {
+ // Secondary empty results.
+ ++aBucket.mnEmpValCount;
+ }
+ else
+ {
+ // First empty result.
+ aBucket.flush(mrMat, mnMatCol);
+ aBucket.mnEmpValStart = nThisRow - mnTopRow;
+ ++aBucket.mnEmpValCount;
+ }
+ continue;
+ }
+
+ FormulaError nErr;
+ double fVal;
+ if (rCell.GetErrorOrValue(nErr, fVal))
+ {
+ if (nErr != FormulaError::NONE)
+ fVal = CreateDoubleError(nErr);
+
+ if (!aBucket.maNumVals.empty() && nThisRow == nPrevRow + 1)
+ {
+ // Secondary numbers.
+ aBucket.maNumVals.push_back(fVal);
+ }
+ else
+ {
+ // First number.
+ aBucket.flush(mrMat, mnMatCol);
+ aBucket.mnNumValStart = nThisRow - mnTopRow;
+ aBucket.maNumVals.push_back(fVal);
+ }
+ continue;
+ }
+
+ svl::SharedString aStr = rCell.GetString();
+ if (mpPool)
+ aStr = mpPool->intern(aStr.getString());
+ if (!aBucket.maStrVals.empty() && nThisRow == nPrevRow + 1)
+ {
+ // Secondary strings.
+ aBucket.maStrVals.push_back(aStr);
+ }
+ else
+ {
+ // First string.
+ aBucket.flush(mrMat, mnMatCol);
+ aBucket.mnStrValStart = nThisRow - mnTopRow;
+ aBucket.maStrVals.push_back(aStr);
+ }
+ }
+
+ aBucket.flush(mrMat, mnMatCol);
+ }
+ break;
+ default:
+ ;
+ }
+ }
+};
+
+}
+
+void ScColumn::FillMatrix( ScMatrix& rMat, size_t nMatCol, SCROW nRow1, SCROW nRow2, svl::SharedStringPool* pPool ) const
+{
+ FillMatrixHandler aFunc(rMat, nMatCol, nRow1, &GetDoc(), pPool);
+ sc::ParseBlock(maCells.begin(), maCells, aFunc, nRow1, nRow2);
+}
+
+namespace {
+
+template<typename Blk>
+void getBlockIterators(
+ const sc::CellStoreType::iterator& it, size_t& rLenRemain,
+ typename Blk::iterator& rData, typename Blk::iterator& rDataEnd )
+{
+ rData = Blk::begin(*it->data);
+ if (rLenRemain >= it->size)
+ {
+ // Block is shorter than the remaining requested length.
+ rDataEnd = Blk::end(*it->data);
+ rLenRemain -= it->size;
+ }
+ else
+ {
+ rDataEnd = rData;
+ std::advance(rDataEnd, rLenRemain);
+ rLenRemain = 0;
+ }
+}
+
+bool appendToBlock(
+ ScDocument* pDoc, sc::FormulaGroupContext& rCxt, sc::FormulaGroupContext::ColArray& rColArray,
+ size_t nPos, size_t nArrayLen, const sc::CellStoreType::iterator& _it, const sc::CellStoreType::iterator& itEnd )
+{
+ svl::SharedStringPool& rPool = pDoc->GetSharedStringPool();
+ size_t nLenRemain = nArrayLen - nPos;
+
+ for (sc::CellStoreType::iterator it = _it; it != itEnd; ++it)
+ {
+ switch (it->type)
+ {
+ case sc::element_type_string:
+ {
+ sc::string_block::iterator itData, itDataEnd;
+ getBlockIterators<sc::string_block>(it, nLenRemain, itData, itDataEnd);
+ rCxt.ensureStrArray(rColArray, nArrayLen);
+
+ for (; itData != itDataEnd; ++itData, ++nPos)
+ (*rColArray.mpStrArray)[nPos] = itData->getData();
+ }
+ break;
+ case sc::element_type_edittext:
+ {
+ sc::edittext_block::iterator itData, itDataEnd;
+ getBlockIterators<sc::edittext_block>(it, nLenRemain, itData, itDataEnd);
+ rCxt.ensureStrArray(rColArray, nArrayLen);
+
+ for (; itData != itDataEnd; ++itData, ++nPos)
+ {
+ OUString aStr = ScEditUtil::GetString(**itData, pDoc);
+ (*rColArray.mpStrArray)[nPos] = rPool.intern(aStr).getData();
+ }
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ sc::formula_block::iterator itData, itDataEnd;
+ getBlockIterators<sc::formula_block>(it, nLenRemain, itData, itDataEnd);
+
+ /* tdf#91416 setting progress in triggers a resize of the window
+ and so ScTabView::DoResize and an InterpretVisible and
+ InterpretDirtyCells which resets the mpFormulaGroupCxt that
+ the current rCxt points to, which is bad, so disable progress
+ during GetResult
+ */
+ ScProgress *pProgress = ScProgress::GetInterpretProgress();
+ bool bTempDisableProgress = pProgress && pProgress->Enabled();
+ if (bTempDisableProgress)
+ pProgress->Disable();
+
+ for (; itData != itDataEnd; ++itData, ++nPos)
+ {
+ ScFormulaCell& rFC = **itData;
+
+ sc::FormulaResultValue aRes = rFC.GetResult();
+
+ if (aRes.meType == sc::FormulaResultValue::Invalid || aRes.mnError != FormulaError::NONE)
+ {
+ if (aRes.mnError == FormulaError::CircularReference)
+ {
+ // This cell needs to be recalculated on next visit.
+ rFC.SetErrCode(FormulaError::NONE);
+ rFC.SetDirtyVar();
+ }
+ return false;
+ }
+
+ if (aRes.meType == sc::FormulaResultValue::String)
+ {
+ rCxt.ensureStrArray(rColArray, nArrayLen);
+ (*rColArray.mpStrArray)[nPos] = aRes.maString.getData();
+ }
+ else
+ {
+ rCxt.ensureNumArray(rColArray, nArrayLen);
+ (*rColArray.mpNumArray)[nPos] = aRes.mfValue;
+ }
+ }
+
+ if (bTempDisableProgress)
+ pProgress->Enable();
+ }
+ break;
+ case sc::element_type_empty:
+ {
+ if (nLenRemain > it->size)
+ {
+ nPos += it->size;
+ nLenRemain -= it->size;
+ }
+ else
+ {
+ nPos = nArrayLen;
+ nLenRemain = 0;
+ }
+ }
+ break;
+ case sc::element_type_numeric:
+ {
+ sc::numeric_block::iterator itData, itDataEnd;
+ getBlockIterators<sc::numeric_block>(it, nLenRemain, itData, itDataEnd);
+ rCxt.ensureNumArray(rColArray, nArrayLen);
+
+ for (; itData != itDataEnd; ++itData, ++nPos)
+ (*rColArray.mpNumArray)[nPos] = *itData;
+ }
+ break;
+ default:
+ return false;
+ }
+
+ if (!nLenRemain)
+ return true;
+ }
+
+ return false;
+}
+
+void copyFirstStringBlock(
+ ScDocument& rDoc, sc::FormulaGroupContext::StrArrayType& rArray, size_t nLen, const sc::CellStoreType::iterator& itBlk )
+{
+ sc::FormulaGroupContext::StrArrayType::iterator itArray = rArray.begin();
+
+ switch (itBlk->type)
+ {
+ case sc::element_type_string:
+ {
+ sc::string_block::iterator it = sc::string_block::begin(*itBlk->data);
+ sc::string_block::iterator itEnd = it;
+ std::advance(itEnd, nLen);
+ for (; it != itEnd; ++it, ++itArray)
+ *itArray = it->getData();
+ }
+ break;
+ case sc::element_type_edittext:
+ {
+ sc::edittext_block::iterator it = sc::edittext_block::begin(*itBlk->data);
+ sc::edittext_block::iterator itEnd = it;
+ std::advance(itEnd, nLen);
+
+ svl::SharedStringPool& rPool = rDoc.GetSharedStringPool();
+ for (; it != itEnd; ++it, ++itArray)
+ {
+ EditTextObject* pText = *it;
+ OUString aStr = ScEditUtil::GetString(*pText, &rDoc);
+ *itArray = rPool.intern(aStr).getData();
+ }
+ }
+ break;
+ default:
+ ;
+ }
+}
+
+sc::FormulaGroupContext::ColArray*
+copyFirstFormulaBlock(
+ sc::FormulaGroupContext& rCxt, const sc::CellStoreType::iterator& itBlk, size_t nArrayLen,
+ SCTAB nTab, SCCOL nCol )
+{
+ size_t nLen = std::min(itBlk->size, nArrayLen);
+
+ sc::formula_block::iterator it = sc::formula_block::begin(*itBlk->data);
+ sc::formula_block::iterator itEnd;
+
+ sc::FormulaGroupContext::NumArrayType* pNumArray = nullptr;
+ sc::FormulaGroupContext::StrArrayType* pStrArray = nullptr;
+
+ itEnd = it;
+ std::advance(itEnd, nLen);
+ size_t nPos = 0;
+ for (; it != itEnd; ++it, ++nPos)
+ {
+ ScFormulaCell& rFC = **it;
+ sc::FormulaResultValue aRes = rFC.GetResult();
+ if (aRes.meType == sc::FormulaResultValue::Invalid || aRes.mnError != FormulaError::NONE)
+ {
+ if (aRes.mnError == FormulaError::CircularReference)
+ {
+ // This cell needs to be recalculated on next visit.
+ rFC.SetErrCode(FormulaError::NONE);
+ rFC.SetDirtyVar();
+ }
+ return nullptr;
+ }
+
+ if (aRes.meType == sc::FormulaResultValue::Value)
+ {
+ if (!pNumArray)
+ {
+ rCxt.m_NumArrays.push_back(
+ std::make_unique<sc::FormulaGroupContext::NumArrayType>(nArrayLen,
+ std::numeric_limits<double>::quiet_NaN()));
+ pNumArray = rCxt.m_NumArrays.back().get();
+ }
+
+ (*pNumArray)[nPos] = aRes.mfValue;
+ }
+ else
+ {
+ if (!pStrArray)
+ {
+ rCxt.m_StrArrays.push_back(
+ std::make_unique<sc::FormulaGroupContext::StrArrayType>(nArrayLen, nullptr));
+ pStrArray = rCxt.m_StrArrays.back().get();
+ }
+
+ (*pStrArray)[nPos] = aRes.maString.getData();
+ }
+ }
+
+ if (!pNumArray && !pStrArray)
+ // At least one of these arrays should be allocated.
+ return nullptr;
+
+ return rCxt.setCachedColArray(nTab, nCol, pNumArray, pStrArray);
+}
+
+struct NonNullStringFinder
+{
+ bool operator() (const rtl_uString* p) const { return p != nullptr; }
+};
+
+bool hasNonEmpty( const sc::FormulaGroupContext::StrArrayType& rArray, SCROW nRow1, SCROW nRow2 )
+{
+ // The caller has to make sure the array is at least nRow2+1 long.
+ sc::FormulaGroupContext::StrArrayType::const_iterator it = rArray.begin();
+ std::advance(it, nRow1);
+ sc::FormulaGroupContext::StrArrayType::const_iterator itEnd = it;
+ std::advance(itEnd, nRow2-nRow1+1);
+ return std::any_of(it, itEnd, NonNullStringFinder());
+}
+
+struct ProtectFormulaGroupContext
+{
+ ProtectFormulaGroupContext( ScDocument* d )
+ : doc( d ) { doc->BlockFormulaGroupContextDiscard( true ); }
+ ~ProtectFormulaGroupContext()
+ { doc->BlockFormulaGroupContextDiscard( false ); }
+ ScDocument* doc;
+};
+
+}
+
+formula::VectorRefArray ScColumn::FetchVectorRefArray( SCROW nRow1, SCROW nRow2 )
+{
+ if (nRow1 > nRow2)
+ return formula::VectorRefArray(formula::VectorRefArray::Invalid);
+
+ // See if the requested range is already cached.
+ ScDocument& rDocument = GetDoc();
+ sc::FormulaGroupContext& rCxt = *(rDocument.GetFormulaGroupContext());
+ sc::FormulaGroupContext::ColArray* pColArray = rCxt.getCachedColArray(nTab, nCol, nRow2+1);
+ if (pColArray)
+ {
+ const double* pNum = nullptr;
+ if (pColArray->mpNumArray)
+ pNum = &(*pColArray->mpNumArray)[nRow1];
+
+ rtl_uString** pStr = nullptr;
+ if (pColArray->mpStrArray && hasNonEmpty(*pColArray->mpStrArray, nRow1, nRow2))
+ pStr = &(*pColArray->mpStrArray)[nRow1];
+
+ return formula::VectorRefArray(pNum, pStr);
+ }
+
+ // ScColumn::CellStorageModified() simply discards the entire cache (FormulaGroupContext)
+ // on any modification. However getting cell values may cause this to be called
+ // if interpreting a cell results in a change to it (not just its result though).
+ // So temporarily block the discarding.
+ ProtectFormulaGroupContext protectContext(&GetDoc());
+
+ // We need to fetch all cell values from row 0 to nRow2 for caching purposes.
+ sc::CellStoreType::iterator itBlk = maCells.begin();
+ switch (itBlk->type)
+ {
+ case sc::element_type_numeric:
+ {
+ if (o3tl::make_unsigned(nRow2) < itBlk->size)
+ {
+ // Requested range falls within the first block. No need to cache.
+ const double* p = &sc::numeric_block::at(*itBlk->data, nRow1);
+ return formula::VectorRefArray(p);
+ }
+
+ // Allocate a new array and copy the values to it.
+ sc::numeric_block::const_iterator it = sc::numeric_block::begin(*itBlk->data);
+ sc::numeric_block::const_iterator itEnd = sc::numeric_block::end(*itBlk->data);
+ rCxt.m_NumArrays.push_back(
+ std::make_unique<sc::FormulaGroupContext::NumArrayType>(it, itEnd));
+ sc::FormulaGroupContext::NumArrayType& rArray = *rCxt.m_NumArrays.back();
+ // allocate to the requested length.
+ rArray.resize(nRow2+1, std::numeric_limits<double>::quiet_NaN());
+
+ pColArray = rCxt.setCachedColArray(nTab, nCol, &rArray, nullptr);
+ if (!pColArray)
+ // Failed to insert a new cached column array.
+ return formula::VectorRefArray(formula::VectorRefArray::Invalid);
+
+ // Fill the remaining array with values from the following blocks.
+ size_t nPos = itBlk->size;
+ ++itBlk;
+ if (!appendToBlock(&rDocument, rCxt, *pColArray, nPos, nRow2+1, itBlk, maCells.end()))
+ {
+ rCxt.discardCachedColArray(nTab, nCol);
+ return formula::VectorRefArray(formula::VectorRefArray::Invalid);
+ }
+
+ rtl_uString** pStr = nullptr;
+ if (pColArray->mpStrArray && hasNonEmpty(*pColArray->mpStrArray, nRow1, nRow2))
+ pStr = &(*pColArray->mpStrArray)[nRow1];
+
+ return formula::VectorRefArray(&(*pColArray->mpNumArray)[nRow1], pStr);
+ }
+ break;
+ case sc::element_type_string:
+ case sc::element_type_edittext:
+ {
+ rCxt.m_StrArrays.push_back(
+ std::make_unique<sc::FormulaGroupContext::StrArrayType>(nRow2+1, nullptr));
+ sc::FormulaGroupContext::StrArrayType& rArray = *rCxt.m_StrArrays.back();
+ pColArray = rCxt.setCachedColArray(nTab, nCol, nullptr, &rArray);
+ if (!pColArray)
+ // Failed to insert a new cached column array.
+ return formula::VectorRefArray();
+
+ if (o3tl::make_unsigned(nRow2) < itBlk->size)
+ {
+ // Requested range falls within the first block.
+ copyFirstStringBlock(rDocument, rArray, nRow2+1, itBlk);
+ return formula::VectorRefArray(&rArray[nRow1]);
+ }
+
+ copyFirstStringBlock(rDocument, rArray, itBlk->size, itBlk);
+
+ // Fill the remaining array with values from the following blocks.
+ size_t nPos = itBlk->size;
+ ++itBlk;
+ if (!appendToBlock(&rDocument, rCxt, *pColArray, nPos, nRow2+1, itBlk, maCells.end()))
+ {
+ rCxt.discardCachedColArray(nTab, nCol);
+ return formula::VectorRefArray(formula::VectorRefArray::Invalid);
+ }
+
+ assert(pColArray->mpStrArray);
+
+ rtl_uString** pStr = nullptr;
+ if (hasNonEmpty(*pColArray->mpStrArray, nRow1, nRow2))
+ pStr = &(*pColArray->mpStrArray)[nRow1];
+
+ if (pColArray->mpNumArray)
+ return formula::VectorRefArray(&(*pColArray->mpNumArray)[nRow1], pStr);
+ else
+ return formula::VectorRefArray(pStr);
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ if (o3tl::make_unsigned(nRow2) < itBlk->size)
+ {
+ // Requested length is within a single block, and the data is
+ // not cached.
+ pColArray = copyFirstFormulaBlock(rCxt, itBlk, nRow2+1, nTab, nCol);
+ if (!pColArray)
+ // Failed to insert a new cached column array.
+ return formula::VectorRefArray(formula::VectorRefArray::Invalid);
+
+ const double* pNum = nullptr;
+ rtl_uString** pStr = nullptr;
+ if (pColArray->mpNumArray)
+ pNum = &(*pColArray->mpNumArray)[nRow1];
+ if (pColArray->mpStrArray)
+ pStr = &(*pColArray->mpStrArray)[nRow1];
+
+ return formula::VectorRefArray(pNum, pStr);
+ }
+
+ pColArray = copyFirstFormulaBlock(rCxt, itBlk, nRow2+1, nTab, nCol);
+ if (!pColArray)
+ {
+ // Failed to insert a new cached column array.
+ return formula::VectorRefArray(formula::VectorRefArray::Invalid);
+ }
+
+ size_t nPos = itBlk->size;
+ ++itBlk;
+ if (!appendToBlock(&rDocument, rCxt, *pColArray, nPos, nRow2+1, itBlk, maCells.end()))
+ {
+ rCxt.discardCachedColArray(nTab, nCol);
+ return formula::VectorRefArray(formula::VectorRefArray::Invalid);
+ }
+
+ const double* pNum = nullptr;
+ rtl_uString** pStr = nullptr;
+ if (pColArray->mpNumArray)
+ pNum = &(*pColArray->mpNumArray)[nRow1];
+ if (pColArray->mpStrArray && hasNonEmpty(*pColArray->mpStrArray, nRow1, nRow2))
+ pStr = &(*pColArray->mpStrArray)[nRow1];
+
+ return formula::VectorRefArray(pNum, pStr);
+ }
+ break;
+ case sc::element_type_empty:
+ {
+ // Fill the whole length with NaN's.
+ rCxt.m_NumArrays.push_back(
+ std::make_unique<sc::FormulaGroupContext::NumArrayType>(nRow2+1,
+ std::numeric_limits<double>::quiet_NaN()));
+ sc::FormulaGroupContext::NumArrayType& rArray = *rCxt.m_NumArrays.back();
+ pColArray = rCxt.setCachedColArray(nTab, nCol, &rArray, nullptr);
+ if (!pColArray)
+ // Failed to insert a new cached column array.
+ return formula::VectorRefArray(formula::VectorRefArray::Invalid);
+
+ if (o3tl::make_unsigned(nRow2) < itBlk->size)
+ return formula::VectorRefArray(&(*pColArray->mpNumArray)[nRow1]);
+
+ // Fill the remaining array with values from the following blocks.
+ size_t nPos = itBlk->size;
+ ++itBlk;
+ if (!appendToBlock(&rDocument, rCxt, *pColArray, nPos, nRow2+1, itBlk, maCells.end()))
+ {
+ rCxt.discardCachedColArray(nTab, nCol);
+ return formula::VectorRefArray(formula::VectorRefArray::Invalid);
+ }
+
+ if (pColArray->mpStrArray && hasNonEmpty(*pColArray->mpStrArray, nRow1, nRow2))
+ return formula::VectorRefArray(&(*pColArray->mpNumArray)[nRow1], &(*pColArray->mpStrArray)[nRow1]);
+ else
+ return formula::VectorRefArray(&(*pColArray->mpNumArray)[nRow1]);
+ }
+ break;
+ default:
+ ;
+ }
+
+ return formula::VectorRefArray(formula::VectorRefArray::Invalid);
+}
+
+#ifdef DBG_UTIL
+static void assertNoInterpretNeededHelper( const sc::CellStoreType::value_type& node,
+ size_t nOffset, size_t nDataSize )
+{
+ switch (node.type)
+ {
+ case sc::element_type_formula:
+ {
+ sc::formula_block::const_iterator it = sc::formula_block::begin(*node.data);
+ std::advance(it, nOffset);
+ sc::formula_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+ for (; it != itEnd; ++it)
+ {
+ const ScFormulaCell* pCell = *it;
+ assert( !pCell->NeedsInterpret());
+ }
+ break;
+ }
+ }
+}
+void ScColumn::AssertNoInterpretNeeded( SCROW nRow1, SCROW nRow2 )
+{
+ assert(nRow2 >= nRow1);
+ sc::ParseBlock( maCells.begin(), maCells, assertNoInterpretNeededHelper, 0, nRow2 );
+}
+#endif
+
+void ScColumn::SetFormulaResults( SCROW nRow, const double* pResults, size_t nLen )
+{
+ sc::CellStoreType::position_type aPos = maCells.position(nRow);
+ sc::CellStoreType::iterator it = aPos.first;
+ if (it->type != sc::element_type_formula)
+ {
+ // This is not a formula block.
+ assert( false );
+ return;
+ }
+
+ size_t nBlockLen = it->size - aPos.second;
+ if (nBlockLen < nLen)
+ // Result array is longer than the length of formula cells. Not good.
+ return;
+
+ sc::formula_block::iterator itCell = sc::formula_block::begin(*it->data);
+ std::advance(itCell, aPos.second);
+
+ const double* pResEnd = pResults + nLen;
+ for (; pResults != pResEnd; ++pResults, ++itCell)
+ {
+ ScFormulaCell& rCell = **itCell;
+ FormulaError nErr = GetDoubleErrorValue(*pResults);
+ if (nErr != FormulaError::NONE)
+ rCell.SetResultError(nErr);
+ else
+ rCell.SetResultDouble(*pResults);
+ rCell.ResetDirty();
+ rCell.SetChanged(true);
+ }
+}
+
+void ScColumn::CalculateInThread( ScInterpreterContext& rContext, SCROW nRow, size_t nLen, size_t nOffset,
+ unsigned nThisThread, unsigned nThreadsTotal)
+{
+ assert(GetDoc().IsThreadedGroupCalcInProgress());
+
+ sc::CellStoreType::position_type aPos = maCells.position(nRow);
+ sc::CellStoreType::iterator it = aPos.first;
+ if (it->type != sc::element_type_formula)
+ {
+ // This is not a formula block.
+ assert( false );
+ return;
+ }
+
+ size_t nBlockLen = it->size - aPos.second;
+ if (nBlockLen < nLen)
+ // Length is longer than the length of formula cells. Not good.
+ return;
+
+ sc::formula_block::iterator itCell = sc::formula_block::begin(*it->data);
+ std::advance(itCell, aPos.second);
+
+ for (size_t i = 0; i < nLen; ++i, ++itCell)
+ {
+ if (nThreadsTotal > 0 && ((i + nOffset) % nThreadsTotal) != nThisThread)
+ continue;
+
+ ScFormulaCell& rCell = **itCell;
+ if (!rCell.NeedsInterpret())
+ continue;
+ // Here we don't call IncInterpretLevel() and DecInterpretLevel() as this call site is
+ // always in a threaded calculation.
+ rCell.InterpretTail(rContext, ScFormulaCell::SCITP_NORMAL);
+ }
+}
+
+void ScColumn::HandleStuffAfterParallelCalculation( SCROW nRow, size_t nLen, ScInterpreter* pInterpreter )
+{
+ sc::CellStoreType::position_type aPos = maCells.position(nRow);
+ sc::CellStoreType::iterator it = aPos.first;
+ if (it->type != sc::element_type_formula)
+ {
+ // This is not a formula block.
+ assert( false );
+ return;
+ }
+
+ size_t nBlockLen = it->size - aPos.second;
+ if (nBlockLen < nLen)
+ // Length is longer than the length of formula cells. Not good.
+ return;
+
+ sc::formula_block::iterator itCell = sc::formula_block::begin(*it->data);
+ std::advance(itCell, aPos.second);
+
+ for (size_t i = 0; i < nLen; ++i, ++itCell)
+ {
+ ScFormulaCell& rCell = **itCell;
+ rCell.HandleStuffAfterParallelCalculation(pInterpreter);
+ }
+}
+
+void ScColumn::SetNumberFormat( SCROW nRow, sal_uInt32 nNumberFormat )
+{
+ ApplyAttr(nRow, SfxUInt32Item(ATTR_VALUE_FORMAT, nNumberFormat));
+}
+
+ScFormulaCell * const * ScColumn::GetFormulaCellBlockAddress( SCROW nRow, size_t& rBlockSize ) const
+{
+ if (!GetDoc().ValidRow(nRow))
+ {
+ rBlockSize = 0;
+ return nullptr;
+ }
+
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it == maCells.end())
+ {
+ rBlockSize = 0;
+ return nullptr;
+ }
+
+ if (it->type != sc::element_type_formula)
+ {
+ // Not a formula cell.
+ rBlockSize = 0;
+ return nullptr;
+ }
+
+ rBlockSize = it->size;
+ return &sc::formula_block::at(*it->data, aPos.second);
+}
+
+const ScFormulaCell* ScColumn::FetchFormulaCell( SCROW nRow ) const
+{
+ size_t nBlockSize = 0;
+ ScFormulaCell const * const * pp = GetFormulaCellBlockAddress( nRow, nBlockSize );
+ return pp ? *pp : nullptr;
+}
+
+void ScColumn::FindDataAreaPos(SCROW& rRow, bool bDown) const
+{
+ // If the cell is empty, find the next non-empty cell position. If the
+ // cell is not empty, find the last non-empty cell position in the current
+ // contiguous cell block.
+
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(rRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it == maCells.end())
+ // Invalid row.
+ return;
+
+ if (it->type == sc::element_type_empty)
+ {
+ // Current cell is empty. Find the next non-empty cell.
+ rRow = FindNextVisibleRowWithContent(it, rRow, bDown);
+ return;
+ }
+
+ // Current cell is not empty.
+ SCROW nNextRow = FindNextVisibleRow(rRow, bDown);
+ aPos = maCells.position(it, nNextRow);
+ it = aPos.first;
+ if (it->type == sc::element_type_empty)
+ {
+ // Next visible cell is empty. Find the next non-empty cell.
+ rRow = FindNextVisibleRowWithContent(it, nNextRow, bDown);
+ return;
+ }
+
+ // Next visible cell is non-empty. Find the edge that's still visible.
+ SCROW nLastRow = nNextRow;
+ do
+ {
+ nNextRow = FindNextVisibleRow(nLastRow, bDown);
+ if (nNextRow == nLastRow)
+ break;
+
+ aPos = maCells.position(it, nNextRow);
+ it = aPos.first;
+ if (it->type != sc::element_type_empty)
+ nLastRow = nNextRow;
+ }
+ while (it->type != sc::element_type_empty);
+
+ rRow = nLastRow;
+}
+
+bool ScColumn::HasDataAt(SCROW nRow, ScDataAreaExtras* pDataAreaExtras ) const
+{
+ if (pDataAreaExtras)
+ GetDataExtrasAt( nRow, *pDataAreaExtras);
+
+ return maCells.get_type(nRow) != sc::element_type_empty;
+}
+
+bool ScColumn::HasDataAt( sc::ColumnBlockConstPosition& rBlockPos, SCROW nRow,
+ ScDataAreaExtras* pDataAreaExtras ) const
+{
+ if (pDataAreaExtras)
+ GetDataExtrasAt( nRow, *pDataAreaExtras);
+
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(rBlockPos.miCellPos, nRow);
+ if (aPos.first == maCells.end())
+ return false;
+ rBlockPos.miCellPos = aPos.first; // Store this for next call.
+ return aPos.first->type != sc::element_type_empty;
+}
+
+bool ScColumn::HasDataAt( sc::ColumnBlockPosition& rBlockPos, SCROW nRow,
+ ScDataAreaExtras* pDataAreaExtras )
+{
+ if (pDataAreaExtras)
+ GetDataExtrasAt( nRow, *pDataAreaExtras);
+
+ std::pair<sc::CellStoreType::iterator,size_t> aPos = maCells.position(rBlockPos.miCellPos, nRow);
+ if (aPos.first == maCells.end())
+ return false;
+ rBlockPos.miCellPos = aPos.first; // Store this for next call.
+ return aPos.first->type != sc::element_type_empty;
+}
+
+void ScColumn::GetDataExtrasAt( SCROW nRow, ScDataAreaExtras& rDataAreaExtras ) const
+{
+ if (rDataAreaExtras.mnStartRow <= nRow && nRow <= rDataAreaExtras.mnEndRow)
+ return;
+
+ // Check in order of likeliness.
+ if ( (rDataAreaExtras.mbCellFormats && HasVisibleAttrIn(nRow, nRow)) ||
+ (rDataAreaExtras.mbCellNotes && !IsNotesEmptyBlock(nRow, nRow)) ||
+ (rDataAreaExtras.mbCellDrawObjects && !IsDrawObjectsEmptyBlock(nRow, nRow)))
+ {
+ if (rDataAreaExtras.mnStartRow > nRow)
+ rDataAreaExtras.mnStartRow = nRow;
+ if (rDataAreaExtras.mnEndRow < nRow)
+ rDataAreaExtras.mnEndRow = nRow;
+ }
+}
+
+namespace {
+
+class FindUsedRowsHandler
+{
+ typedef mdds::flat_segment_tree<SCROW,bool> UsedRowsType;
+ UsedRowsType& mrUsed;
+ UsedRowsType::const_iterator miUsed;
+public:
+ explicit FindUsedRowsHandler(UsedRowsType& rUsed) : mrUsed(rUsed), miUsed(rUsed.begin()) {}
+
+ void operator() (const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize)
+ {
+ if (node.type == sc::element_type_empty)
+ return;
+
+ SCROW nRow1 = node.position + nOffset;
+ SCROW nRow2 = nRow1 + nDataSize - 1;
+ miUsed = mrUsed.insert(miUsed, nRow1, nRow2+1, true).first;
+ }
+};
+
+}
+
+void ScColumn::FindUsed( SCROW nStartRow, SCROW nEndRow, mdds::flat_segment_tree<SCROW,bool>& rUsed ) const
+{
+ FindUsedRowsHandler aFunc(rUsed);
+ sc::ParseBlock(maCells.begin(), maCells, aFunc, nStartRow, nEndRow);
+}
+
+namespace {
+
+void startListening(
+ sc::BroadcasterStoreType& rStore, sc::BroadcasterStoreType::iterator& itBlockPos, size_t nElemPos,
+ SCROW nRow, SvtListener& rLst)
+{
+ switch (itBlockPos->type)
+ {
+ case sc::element_type_broadcaster:
+ {
+ // Broadcaster already exists here.
+ SvtBroadcaster* pBC = sc::broadcaster_block::at(*itBlockPos->data, nElemPos);
+ rLst.StartListening(*pBC);
+ }
+ break;
+ case mdds::mtv::element_type_empty:
+ {
+ // No broadcaster exists at this position yet.
+ SvtBroadcaster* pBC = new SvtBroadcaster;
+ rLst.StartListening(*pBC);
+ itBlockPos = rStore.set(itBlockPos, nRow, pBC); // Store the block position for next iteration.
+ }
+ break;
+ default:
+ assert(false && "wrong block type encountered in the broadcaster storage.");
+ }
+}
+
+}
+
+void ScColumn::StartListening( SvtListener& rLst, SCROW nRow )
+{
+ std::pair<sc::BroadcasterStoreType::iterator,size_t> aPos = maBroadcasters.position(nRow);
+ startListening(maBroadcasters, aPos.first, aPos.second, nRow, rLst);
+}
+
+void ScColumn::EndListening( SvtListener& rLst, SCROW nRow )
+{
+ SvtBroadcaster* pBC = GetBroadcaster(nRow);
+ if (!pBC)
+ return;
+
+ rLst.EndListening(*pBC);
+ if (!pBC->HasListeners())
+ { // There is no more listeners for this cell. Remove the broadcaster.
+ if(GetDoc().IsDelayedDeletingBroadcasters())
+ mbEmptyBroadcastersPending = true;
+ else
+ maBroadcasters.set_empty(nRow, nRow);
+ }
+}
+
+void ScColumn::StartListening( sc::StartListeningContext& rCxt, const ScAddress& rAddress, SvtListener& rLst )
+{
+ if (!GetDoc().ValidRow(rAddress.Row()))
+ return;
+
+ sc::ColumnBlockPosition* p = rCxt.getBlockPosition(rAddress.Tab(), rAddress.Col());
+ if (!p)
+ return;
+
+ sc::BroadcasterStoreType::iterator& it = p->miBroadcasterPos;
+ std::pair<sc::BroadcasterStoreType::iterator,size_t> aPos = maBroadcasters.position(it, rAddress.Row());
+ it = aPos.first; // store the block position for next iteration.
+ startListening(maBroadcasters, it, aPos.second, rAddress.Row(), rLst);
+}
+
+void ScColumn::EndListening( sc::EndListeningContext& rCxt, const ScAddress& rAddress, SvtListener& rListener )
+{
+ sc::ColumnBlockPosition* p = rCxt.getBlockPosition(rAddress.Tab(), rAddress.Col());
+ if (!p)
+ return;
+
+ sc::BroadcasterStoreType::iterator& it = p->miBroadcasterPos;
+ std::pair<sc::BroadcasterStoreType::iterator,size_t> aPos = maBroadcasters.position(it, rAddress.Row());
+ it = aPos.first; // store the block position for next iteration.
+ if (it->type != sc::element_type_broadcaster)
+ return;
+
+ SvtBroadcaster* pBC = sc::broadcaster_block::at(*it->data, aPos.second);
+ assert(pBC);
+
+ rListener.EndListening(*pBC);
+ if (!pBC->HasListeners())
+ // There is no more listeners for this cell. Add it to the purge list for later purging.
+ rCxt.addEmptyBroadcasterPosition(rAddress.Tab(), rAddress.Col(), rAddress.Row());
+}
+
+namespace {
+
+class CompileDBFormulaHandler
+{
+ sc::CompileFormulaContext& mrCxt;
+
+public:
+ explicit CompileDBFormulaHandler( sc::CompileFormulaContext& rCxt ) :
+ mrCxt(rCxt) {}
+
+ void operator() (size_t, ScFormulaCell* p)
+ {
+ p->CompileDBFormula(mrCxt);
+ }
+};
+
+struct CompileColRowNameFormulaHandler
+{
+ sc::CompileFormulaContext& mrCxt;
+public:
+ explicit CompileColRowNameFormulaHandler( sc::CompileFormulaContext& rCxt ) : mrCxt(rCxt) {}
+
+ void operator() (size_t, ScFormulaCell* p)
+ {
+ p->CompileColRowNameFormula(mrCxt);
+ }
+};
+
+}
+
+void ScColumn::CompileDBFormula( sc::CompileFormulaContext& rCxt )
+{
+ CompileDBFormulaHandler aFunc(rCxt);
+ sc::ProcessFormula(maCells, aFunc);
+ RegroupFormulaCells();
+}
+
+void ScColumn::CompileColRowNameFormula( sc::CompileFormulaContext& rCxt )
+{
+ CompileColRowNameFormulaHandler aFunc(rCxt);
+ sc::ProcessFormula(maCells, aFunc);
+ RegroupFormulaCells();
+}
+
+namespace {
+
+class UpdateSubTotalHandler
+{
+ ScFunctionData& mrData;
+
+ void update(double fVal, bool bVal)
+ {
+ if (mrData.getError())
+ return;
+
+ switch (mrData.getFunc())
+ {
+ case SUBTOTAL_FUNC_CNT2: // everything
+ mrData.update( fVal);
+ break;
+ default: // only numeric values
+ if (bVal)
+ mrData.update( fVal);
+ }
+ }
+
+public:
+ explicit UpdateSubTotalHandler(ScFunctionData& rData) : mrData(rData) {}
+
+ void operator() (size_t /*nRow*/, double fVal)
+ {
+ update(fVal, true);
+ }
+
+ void operator() (size_t /*nRow*/, const svl::SharedString&)
+ {
+ update(0.0, false);
+ }
+
+ void operator() (size_t /*nRow*/, const EditTextObject*)
+ {
+ update(0.0, false);
+ }
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+ double fVal = 0.0;
+ bool bVal = false;
+ if (mrData.getFunc() != SUBTOTAL_FUNC_CNT2) // it doesn't interest us
+ {
+
+ if (pCell->GetErrCode() != FormulaError::NONE)
+ {
+ if (mrData.getFunc() != SUBTOTAL_FUNC_CNT) // simply remove from count
+ mrData.setError();
+ }
+ else if (pCell->IsValue())
+ {
+ fVal = pCell->GetValue();
+ bVal = true;
+ }
+ // otherwise text
+ }
+
+ update(fVal, bVal);
+ }
+};
+
+}
+
+// multiple selections:
+void ScColumn::UpdateSelectionFunction(
+ const ScRangeList& rRanges, ScFunctionData& rData, const ScFlatBoolRowSegments& rHiddenRows )
+{
+ sc::SingleColumnSpanSet aSpanSet(GetDoc().GetSheetLimits());
+ aSpanSet.scan(rRanges, nTab, nCol); // mark all selected rows.
+
+ if (aSpanSet.empty())
+ return; // nothing to do, bail out
+
+ // Exclude all hidden rows.
+ ScFlatBoolRowSegments::RangeData aRange;
+ SCROW nRow = 0;
+ while (nRow <= GetDoc().MaxRow())
+ {
+ if (!rHiddenRows.getRangeData(nRow, aRange))
+ break;
+
+ if (aRange.mbValue)
+ // Hidden range detected.
+ aSpanSet.set(nRow, aRange.mnRow2, false);
+
+ nRow = aRange.mnRow2 + 1;
+ }
+
+ sc::SingleColumnSpanSet::SpansType aSpans;
+ aSpanSet.getSpans(aSpans);
+
+ switch (rData.getFunc())
+ {
+ case SUBTOTAL_FUNC_SELECTION_COUNT:
+ {
+ // Simply count selected rows regardless of cell contents.
+ for (const auto& rSpan : aSpans)
+ rData.update( rSpan.mnRow2 - rSpan.mnRow1 + 1);
+ }
+ break;
+ case SUBTOTAL_FUNC_CNT2:
+ {
+ // We need to parse all non-empty cells.
+ sc::CellStoreType::const_iterator itCellPos = maCells.begin();
+ UpdateSubTotalHandler aFunc(rData);
+ for (const auto& rSpan : aSpans)
+ {
+ itCellPos = sc::ParseAllNonEmpty(
+ itCellPos, maCells, rSpan.mnRow1, rSpan.mnRow2, aFunc);
+ }
+ }
+ break;
+ default:
+ {
+ // We need to parse only numeric values.
+ sc::CellStoreType::const_iterator itCellPos = maCells.begin();
+ UpdateSubTotalHandler aFunc(rData);
+ for (const auto& rSpan : aSpans)
+ {
+ itCellPos = sc::ParseFormulaNumeric(
+ itCellPos, maCells, rSpan.mnRow1, rSpan.mnRow2, aFunc);
+ }
+ }
+ }
+}
+
+namespace {
+
+class WeightedCounter
+{
+ sal_uLong mnCount;
+public:
+ WeightedCounter() : mnCount(0) {}
+
+ void operator() (const sc::CellStoreType::value_type& node)
+ {
+ mnCount += getWeight(node);
+ }
+
+ static sal_uLong getWeight(const sc::CellStoreType::value_type& node)
+ {
+ switch (node.type)
+ {
+ case sc::element_type_numeric:
+ case sc::element_type_string:
+ return node.size;
+ case sc::element_type_formula:
+ {
+ // Each formula cell is worth its code length plus 5.
+ return std::accumulate(sc::formula_block::begin(*node.data), sc::formula_block::end(*node.data), size_t(0),
+ [](const size_t& rCount, const ScFormulaCell* p) { return rCount + 5 + p->GetCode()->GetCodeLen(); });
+ }
+ case sc::element_type_edittext:
+ // each edit-text cell is worth 50.
+ return node.size * 50;
+ default:
+ return 0;
+ }
+ }
+
+ sal_uLong getCount() const { return mnCount; }
+};
+
+class WeightedCounterWithRows
+{
+ const SCROW mnStartRow;
+ const SCROW mnEndRow;
+ sal_uLong mnCount;
+
+public:
+ WeightedCounterWithRows(SCROW nStartRow, SCROW nEndRow)
+ : mnStartRow(nStartRow)
+ , mnEndRow(nEndRow)
+ , mnCount(0)
+ {
+ }
+
+ void operator() (const sc::CellStoreType::value_type& node)
+ {
+ const SCROW nRow1 = node.position;
+ const SCROW nRow2 = nRow1 + 1;
+
+ if ((nRow2 >= mnStartRow) && (nRow1 <= mnEndRow))
+ {
+ mnCount += WeightedCounter::getWeight(node);
+ }
+ }
+
+ sal_uLong getCount() const { return mnCount; }
+};
+
+}
+
+sal_uInt64 ScColumn::GetWeightedCount() const
+{
+ const WeightedCounter aFunc = std::for_each(maCells.begin(), maCells.end(),
+ WeightedCounter());
+ return aFunc.getCount();
+}
+
+sal_uInt64 ScColumn::GetWeightedCount(SCROW nStartRow, SCROW nEndRow) const
+{
+ const WeightedCounterWithRows aFunc = std::for_each(maCells.begin(), maCells.end(),
+ WeightedCounterWithRows(nStartRow, nEndRow));
+ return aFunc.getCount();
+}
+
+namespace {
+
+class CodeCounter
+{
+ sal_uInt64 mnCount;
+public:
+ CodeCounter() : mnCount(0) {}
+
+ void operator() (size_t, const ScFormulaCell* p)
+ {
+ mnCount += p->GetCode()->GetCodeLen();
+ }
+
+ sal_uInt64 getCount() const { return mnCount; }
+};
+
+}
+
+sal_uInt64 ScColumn::GetCodeCount() const
+{
+ CodeCounter aFunc;
+ sc::ParseFormula(maCells, aFunc);
+ return aFunc.getCount();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/column3.cxx b/sc/source/core/data/column3.cxx
new file mode 100644
index 000000000..ead02920a
--- /dev/null
+++ b/sc/source/core/data/column3.cxx
@@ -0,0 +1,3726 @@
+/* -*- 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 <column.hxx>
+
+#include <scitems.hxx>
+#include <formulacell.hxx>
+#include <document.hxx>
+#include <attarray.hxx>
+#include <patattr.hxx>
+#include <cellform.hxx>
+#include <typedstrdata.hxx>
+#include <formula/errorcodes.hxx>
+#include <formula/token.hxx>
+#include <brdcst.hxx>
+#include <docoptio.hxx>
+#include <subtotal.hxx>
+#include <markdata.hxx>
+#include <stringutil.hxx>
+#include <docpool.hxx>
+#include <cellvalue.hxx>
+#include <tokenarray.hxx>
+#include <clipcontext.hxx>
+#include <columnspanset.hxx>
+#include <mtvcellfunc.hxx>
+#include <scopetools.hxx>
+#include <editutil.hxx>
+#include <sharedformula.hxx>
+#include <listenercontext.hxx>
+#include <filterentries.hxx>
+#include <conditio.hxx>
+#include <colorscale.hxx>
+#include <editeng/brushitem.hxx>
+#include <editeng/colritem.hxx>
+
+#include <com/sun/star/i18n/LocaleDataItem2.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+
+#include <memory>
+
+#include <o3tl/deleter.hxx>
+
+#include <rtl/tencinfo.h>
+
+#include <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+#include <svl/zformat.hxx>
+#include <svl/sharedstringpool.hxx>
+#include <osl/diagnose.h>
+
+#include <cstdio>
+#include <refdata.hxx>
+
+using ::com::sun::star::i18n::LocaleDataItem2;
+
+using namespace formula;
+
+void ScColumn::Broadcast( SCROW nRow )
+{
+ ScHint aHint(SfxHintId::ScDataChanged, ScAddress(nCol, nRow, nTab));
+ GetDoc().Broadcast(aHint);
+}
+
+void ScColumn::BroadcastCells( const std::vector<SCROW>& rRows, SfxHintId nHint )
+{
+ if (rRows.empty())
+ return;
+
+ // Broadcast the changes.
+ ScDocument& rDocument = GetDoc();
+ ScHint aHint(nHint, ScAddress(nCol, 0, nTab));
+ for (const auto& rRow : rRows)
+ {
+ aHint.SetAddressRow(rRow);
+ rDocument.Broadcast(aHint);
+ }
+}
+
+void ScColumn::BroadcastRows( SCROW nStartRow, SCROW nEndRow, SfxHintId nHint )
+{
+ if( nStartRow > GetLastDataPos())
+ return;
+ sc::SingleColumnSpanSet aSpanSet(GetDoc().GetSheetLimits());
+ aSpanSet.scan(*this, nStartRow, nEndRow);
+ std::vector<SCROW> aRows;
+ aSpanSet.getRows(aRows);
+ BroadcastCells(aRows, nHint);
+}
+
+namespace {
+
+class CellInterpreterBase
+{
+protected:
+ void Interpret(ScFormulaCell* p)
+ {
+ // Interpret() takes a range in a formula group, so group those together.
+ if( !groupCells.empty() && p->GetCellGroup() == groupCells.back()->GetCellGroup()
+ && p->aPos.Row() == groupCells.back()->aPos.Row() + 1 )
+ {
+ assert( p->aPos.Tab() == groupCells.back()->aPos.Tab()
+ && p->aPos.Col() == groupCells.back()->aPos.Col());
+ groupCells.push_back(p); // Extend range.
+ return;
+ }
+ flushPending();
+ if( !p->GetCellGroup())
+ {
+ p->Interpret();
+ return;
+ }
+ groupCells.push_back(p);
+
+ }
+ ~CellInterpreterBase()
+ {
+ suppress_fun_call_w_exception(flushPending());
+ }
+private:
+ void flushPending()
+ {
+ if(groupCells.empty())
+ return;
+ SCROW firstRow = groupCells.front()->GetCellGroup()->mpTopCell->aPos.Row();
+ if(!groupCells.front()->Interpret(
+ groupCells.front()->aPos.Row() - firstRow, groupCells.back()->aPos.Row() - firstRow))
+ {
+ // Interpret() will try to group-interpret the given cell range if possible, but if that
+ // is not possible, it will interpret just the given cell. So if group-interpreting
+ // wasn't possible, interpret them one by one.
+ for(ScFormulaCell* cell : groupCells)
+ cell->Interpret();
+ }
+ groupCells.clear();
+ }
+ std::vector<ScFormulaCell*> groupCells;
+};
+
+class DirtyCellInterpreter : public CellInterpreterBase
+{
+public:
+ void operator() (size_t, ScFormulaCell* p)
+ {
+ if(p->GetDirty())
+ Interpret(p);
+ }
+};
+
+class NeedsInterpretCellInterpreter : public CellInterpreterBase
+{
+public:
+ void operator() (size_t, ScFormulaCell* p)
+ {
+ if(p->NeedsInterpret())
+ {
+ Interpret(p);
+ // In some cases such as circular dependencies Interpret()
+ // will not reset the dirty flag, check that in order to tell
+ // the caller that the cell range may trigger Interpret() again.
+ if(p->NeedsInterpret())
+ allInterpreted = false;
+ }
+ }
+ bool allInterpreted = true;
+};
+
+}
+
+void ScColumn::InterpretDirtyCells( SCROW nRow1, SCROW nRow2 )
+{
+ if (!GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2) || nRow1 > nRow2)
+ return;
+
+ DirtyCellInterpreter aFunc;
+ sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aFunc);
+}
+
+bool ScColumn::InterpretCellsIfNeeded( SCROW nRow1, SCROW nRow2 )
+{
+ if (!GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2) || nRow1 > nRow2)
+ return false;
+
+ NeedsInterpretCellInterpreter aFunc;
+ sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aFunc);
+ return aFunc.allInterpreted;
+}
+
+void ScColumn::DeleteContent( SCROW nRow, bool bBroadcast )
+{
+ sc::CellStoreType::position_type aPos = maCells.position(nRow);
+ sc::CellStoreType::iterator it = aPos.first;
+ if (it == maCells.end())
+ return;
+
+ if (it->type == sc::element_type_formula)
+ {
+ ScFormulaCell* p = sc::formula_block::at(*it->data, aPos.second);
+ p->EndListeningTo(GetDoc());
+ sc::SharedFormulaUtil::unshareFormulaCell(aPos, *p);
+ }
+ maCells.set_empty(nRow, nRow);
+
+ if (bBroadcast)
+ {
+ Broadcast(nRow);
+ CellStorageModified();
+ }
+}
+
+void ScColumn::Delete( SCROW nRow )
+{
+ DeleteContent(nRow, false);
+ maCellTextAttrs.set_empty(nRow, nRow);
+ maCellNotes.set_empty(nRow, nRow);
+ maSparklines.set_empty(nRow, nRow);
+
+ Broadcast(nRow);
+ CellStorageModified();
+}
+
+void ScColumn::FreeAll()
+{
+ maCells.event_handler().stop();
+
+ auto maxRowCount = GetDoc().GetMaxRowCount();
+ // Keep a logical empty range of 0-rDoc.MaxRow() at all times.
+ maCells.clear();
+ maCells.resize(maxRowCount);
+ maCellTextAttrs.clear();
+ maCellTextAttrs.resize(maxRowCount);
+ maCellNotes.clear();
+ maCellNotes.resize(maxRowCount);
+ maSparklines.clear();
+ maSparklines.resize(maxRowCount);
+ CellStorageModified();
+}
+
+void ScColumn::FreeNotes()
+{
+ maCellNotes.clear();
+ maCellNotes.resize(GetDoc().GetMaxRowCount());
+}
+
+namespace {
+
+class ShiftFormulaPosHandler
+{
+public:
+
+ void operator() (size_t nRow, ScFormulaCell* pCell)
+ {
+ pCell->aPos.SetRow(nRow);
+ }
+};
+
+}
+
+void ScColumn::DeleteRow( SCROW nStartRow, SCSIZE nSize, std::vector<ScAddress>* pGroupPos )
+{
+ pAttrArray->DeleteRow( nStartRow, nSize );
+
+ SCROW nEndRow = nStartRow + nSize - 1;
+
+ maBroadcasters.erase(nStartRow, nEndRow);
+ maBroadcasters.resize(GetDoc().GetMaxRowCount());
+
+ CellNotesDeleting(nStartRow, nEndRow, false);
+ maCellNotes.erase(nStartRow, nEndRow);
+ maCellNotes.resize(GetDoc().GetMaxRowCount());
+
+ // See if we have any cells that would get deleted or shifted by deletion.
+ sc::CellStoreType::position_type aPos = maCells.position(nStartRow);
+ sc::CellStoreType::iterator itCell = aPos.first;
+ if (itCell->type == sc::element_type_empty)
+ {
+ // This is an empty block. If this is the last block, then there is no cells to delete or shift.
+ sc::CellStoreType::iterator itTest = itCell;
+ ++itTest;
+ if (itTest == maCells.end())
+ {
+ // No cells are affected by this deletion. Bail out.
+ CellStorageModified(); // broadcast array has been modified.
+ return;
+ }
+ }
+
+ // Check if there are any cells below the end row that will get shifted.
+ bool bShiftCells = false;
+ if (nEndRow < GetDoc().MaxRow()) //only makes sense to do this if there *is* a row after the end row
+ {
+ aPos = maCells.position(itCell, nEndRow+1);
+ itCell = aPos.first;
+ if (itCell->type == sc::element_type_empty)
+ {
+ // This block is empty. See if there is any block that follows.
+ sc::CellStoreType::iterator itTest = itCell;
+ ++itTest;
+ if (itTest != maCells.end())
+ // Non-empty block follows -> cells that will get shifted.
+ bShiftCells = true;
+ }
+ else
+ bShiftCells = true;
+ }
+
+ sc::SingleColumnSpanSet aNonEmptySpans(GetDoc().GetSheetLimits());
+ if (bShiftCells)
+ {
+ // Mark all non-empty cell positions below the end row.
+ sc::ColumnBlockConstPosition aBlockPos;
+ aBlockPos.miCellPos = itCell;
+ aNonEmptySpans.scan(aBlockPos, *this, nEndRow+1, GetDoc().MaxRow());
+ }
+
+ sc::AutoCalcSwitch aACSwitch(GetDoc(), false);
+
+ // Remove the cells.
+ maCells.erase(nStartRow, nEndRow);
+ maCells.resize(GetDoc().GetMaxRowCount());
+
+ // Get the position again after the container change.
+ aPos = maCells.position(nStartRow);
+
+ // Shift the formula cell positions below the start row.
+ ShiftFormulaPosHandler aShiftFormulaFunc;
+ sc::ProcessFormula(aPos.first, maCells, nStartRow, GetDoc().MaxRow(), aShiftFormulaFunc);
+
+ bool bJoined = sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
+ if (bJoined && pGroupPos)
+ pGroupPos->push_back(ScAddress(nCol, nStartRow, nTab));
+
+ // Shift the text attribute array too (before the broadcast).
+ maCellTextAttrs.erase(nStartRow, nEndRow);
+ maCellTextAttrs.resize(GetDoc().GetMaxRowCount());
+
+ CellStorageModified();
+}
+
+sc::CellStoreType::iterator ScColumn::GetPositionToInsert( SCROW nRow, std::vector<SCROW>& rNewSharedRows,
+ bool bInsertFormula )
+{
+ return GetPositionToInsert(maCells.begin(), nRow, rNewSharedRows, bInsertFormula);
+}
+
+void ScColumn::JoinNewFormulaCell(
+ const sc::CellStoreType::position_type& aPos, ScFormulaCell& rCell )
+{
+ // Check the previous row position for possible grouping.
+ if (aPos.first->type == sc::element_type_formula && aPos.second > 0)
+ {
+ ScFormulaCell& rPrev = *sc::formula_block::at(*aPos.first->data, aPos.second-1);
+ sc::CellStoreType::position_type aPosPrev = aPos;
+ --aPosPrev.second;
+ sc::SharedFormulaUtil::joinFormulaCells(aPosPrev, rPrev, rCell);
+ }
+
+ // Check the next row position for possible grouping.
+ if (aPos.first->type == sc::element_type_formula && aPos.second+1 < aPos.first->size)
+ {
+ ScFormulaCell& rNext = *sc::formula_block::at(*aPos.first->data, aPos.second+1);
+ sc::SharedFormulaUtil::joinFormulaCells(aPos, rCell, rNext);
+ }
+}
+
+void ScColumn::DetachFormulaCell(
+ const sc::CellStoreType::position_type& aPos, ScFormulaCell& rCell, std::vector<SCROW>& rNewSharedRows )
+{
+ if (!GetDoc().IsClipOrUndo())
+ {
+#if USE_FORMULA_GROUP_LISTENER
+ if (rCell.IsShared() && rCell.GetSharedLength() > 1)
+ {
+ // Record new spans (shared or remaining single) that will result
+ // from unsharing to reestablish listeners.
+ // Same cases as in unshareFormulaCell().
+ // XXX NOTE: this is not part of unshareFormulaCell() because that
+ // is called in other contexts as well, for which passing and
+ // determining the rows vector would be superfluous. If that was
+ // needed, move it there.
+ const SCROW nSharedTopRow = rCell.GetSharedTopRow();
+ const SCROW nSharedLength = rCell.GetSharedLength();
+ if (rCell.aPos.Row() == nSharedTopRow)
+ {
+ // Top cell.
+ // Next row will be new shared top or single cell.
+ rNewSharedRows.push_back( nSharedTopRow + 1);
+ rNewSharedRows.push_back( nSharedTopRow + nSharedLength - 1);
+ }
+ else if (rCell.aPos.Row() == nSharedTopRow + nSharedLength - 1)
+ {
+ // Bottom cell.
+ // Current shared top row will be new shared top again or
+ // single cell.
+ rNewSharedRows.push_back( nSharedTopRow);
+ rNewSharedRows.push_back( rCell.aPos.Row() - 1);
+ }
+ else
+ {
+ // Some mid cell.
+ // Current shared top row will be new shared top again or
+ // single cell, plus a new shared top below or single cell.
+ rNewSharedRows.push_back( nSharedTopRow);
+ rNewSharedRows.push_back( rCell.aPos.Row() - 1);
+ rNewSharedRows.push_back( rCell.aPos.Row() + 1);
+ rNewSharedRows.push_back( nSharedTopRow + nSharedLength - 1);
+ }
+ }
+#endif
+
+ // Have the dying formula cell stop listening.
+ // If in a shared formula group this ends the group listening.
+ rCell.EndListeningTo(GetDoc());
+ }
+
+ sc::SharedFormulaUtil::unshareFormulaCell(aPos, rCell);
+}
+
+void ScColumn::StartListeningUnshared( const std::vector<SCROW>& rNewSharedRows )
+{
+ assert(rNewSharedRows.empty() || rNewSharedRows.size() == 2 || rNewSharedRows.size() == 4);
+ ScDocument& rDoc = GetDoc();
+ if (rNewSharedRows.empty() || rDoc.IsDelayedFormulaGrouping())
+ return;
+
+ auto pPosSet = std::make_shared<sc::ColumnBlockPositionSet>(rDoc);
+ sc::StartListeningContext aStartCxt(rDoc, pPosSet);
+ sc::EndListeningContext aEndCxt(rDoc, pPosSet);
+ if (rNewSharedRows.size() >= 2)
+ {
+ if(!rDoc.CanDelayStartListeningFormulaCells( this, rNewSharedRows[0], rNewSharedRows[1]))
+ StartListeningFormulaCells(aStartCxt, aEndCxt, rNewSharedRows[0], rNewSharedRows[1]);
+ }
+ if (rNewSharedRows.size() >= 4)
+ {
+ if(!rDoc.CanDelayStartListeningFormulaCells( this, rNewSharedRows[2], rNewSharedRows[3]))
+ StartListeningFormulaCells(aStartCxt, aEndCxt, rNewSharedRows[2], rNewSharedRows[3]);
+ }
+}
+
+namespace {
+
+class AttachFormulaCellsHandler
+{
+ sc::StartListeningContext& mrCxt;
+
+public:
+ explicit AttachFormulaCellsHandler(sc::StartListeningContext& rCxt)
+ : mrCxt(rCxt) {}
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+ pCell->StartListeningTo(mrCxt);
+ }
+};
+
+class DetachFormulaCellsHandler
+{
+ ScDocument& mrDoc;
+ sc::EndListeningContext* mpCxt;
+
+public:
+ DetachFormulaCellsHandler( ScDocument& rDoc, sc::EndListeningContext* pCxt ) :
+ mrDoc(rDoc), mpCxt(pCxt) {}
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+ if (mpCxt)
+ pCell->EndListeningTo(*mpCxt);
+ else
+ pCell->EndListeningTo(mrDoc);
+ }
+};
+
+}
+
+void ScColumn::DetachFormulaCells(
+ const sc::CellStoreType::position_type& aPos, size_t nLength, std::vector<SCROW>* pNewSharedRows )
+{
+ const size_t nRow = aPos.first->position + aPos.second;
+ const size_t nNextTopRow = nRow + nLength; // start row of next formula group.
+
+ bool bLowerSplitOff = false;
+ if (pNewSharedRows && !GetDoc().IsClipOrUndo())
+ {
+ const ScFormulaCell* pFC = sc::SharedFormulaUtil::getSharedTopFormulaCell(aPos);
+ if (pFC)
+ {
+ const SCROW nTopRow = pFC->GetSharedTopRow();
+ const SCROW nBotRow = nTopRow + pFC->GetSharedLength() - 1;
+ // nTopRow <= nRow <= nBotRow, because otherwise pFC would not exist.
+ if (nTopRow < static_cast<SCROW>(nRow))
+ {
+ // Upper part will be split off.
+ pNewSharedRows->push_back(nTopRow);
+ pNewSharedRows->push_back(nRow - 1);
+ }
+ if (static_cast<SCROW>(nNextTopRow) <= nBotRow)
+ {
+ // Lower part will be split off.
+ pNewSharedRows->push_back(nNextTopRow);
+ pNewSharedRows->push_back(nBotRow);
+ bLowerSplitOff = true;
+ }
+ }
+ }
+
+ // Split formula grouping at the top and bottom boundaries.
+ sc::SharedFormulaUtil::splitFormulaCellGroup(aPos, nullptr);
+
+ if (nLength > 0 && GetDoc().ValidRow(nNextTopRow))
+ {
+ if (pNewSharedRows && !bLowerSplitOff && !GetDoc().IsClipOrUndo())
+ {
+ sc::CellStoreType::position_type aPos2 = maCells.position(aPos.first, nNextTopRow-1);
+ const ScFormulaCell* pFC = sc::SharedFormulaUtil::getSharedTopFormulaCell(aPos2);
+ if (pFC)
+ {
+ const SCROW nTopRow = pFC->GetSharedTopRow();
+ const SCROW nBotRow = nTopRow + pFC->GetSharedLength() - 1;
+ // nRow < nTopRow < nNextTopRow <= nBotRow
+ if (static_cast<SCROW>(nNextTopRow) <= nBotRow)
+ {
+ // Lower part will be split off.
+ pNewSharedRows->push_back(nNextTopRow);
+ pNewSharedRows->push_back(nBotRow);
+ }
+ }
+ }
+
+ sc::CellStoreType::position_type aPos2 = maCells.position(aPos.first, nNextTopRow);
+ sc::SharedFormulaUtil::splitFormulaCellGroup(aPos2, nullptr);
+ }
+
+ if (GetDoc().IsClipOrUndo())
+ return;
+
+ DetachFormulaCellsHandler aFunc(GetDoc(), nullptr);
+ sc::ProcessFormula(aPos.first, maCells, nRow, nNextTopRow-1, aFunc);
+}
+
+void ScColumn::AttachFormulaCells( sc::StartListeningContext& rCxt, SCROW nRow1, SCROW nRow2 )
+{
+ sc::CellStoreType::position_type aPos = maCells.position(nRow1);
+ sc::CellStoreType::iterator it = aPos.first;
+
+ sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
+ if (GetDoc().ValidRow(nRow2+1))
+ {
+ aPos = maCells.position(it, nRow2+1);
+ sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
+ }
+
+ if (GetDoc().IsClipOrUndo())
+ return;
+
+ // Need to process (start listening) entire shared formula groups, not just
+ // a slice thereof.
+ bool bEnlargedDown = false;
+ aPos = maCells.position(nRow1);
+ it = aPos.first;
+ if (it->type == sc::element_type_formula)
+ {
+ ScFormulaCell& rCell = *sc::formula_block::at(*it->data, aPos.second);
+ if (rCell.IsShared())
+ {
+ nRow1 = std::min( nRow1, rCell.GetSharedTopRow());
+ if (nRow2 < rCell.GetSharedTopRow() + rCell.GetSharedLength())
+ {
+ nRow2 = rCell.GetSharedTopRow() + rCell.GetSharedLength() - 1;
+ bEnlargedDown = true;
+ // Same end row is also enlarged, i.e. doesn't need to be
+ // checked for another group.
+ }
+ }
+ }
+ if (!bEnlargedDown)
+ {
+ aPos = maCells.position(it, nRow2);
+ it = aPos.first;
+ if (it->type == sc::element_type_formula)
+ {
+ ScFormulaCell& rCell = *sc::formula_block::at(*it->data, aPos.second);
+ if (rCell.IsShared())
+ {
+ nRow2 = std::max( nRow2, rCell.GetSharedTopRow() + rCell.GetSharedLength() - 1);
+ }
+ }
+ }
+
+ AttachFormulaCellsHandler aFunc(rCxt);
+ sc::ProcessFormula(it, maCells, nRow1, nRow2, aFunc);
+}
+
+void ScColumn::DetachFormulaCells( sc::EndListeningContext& rCxt, SCROW nRow1, SCROW nRow2 )
+{
+ sc::CellStoreType::position_type aPos = maCells.position(nRow1);
+ sc::CellStoreType::iterator it = aPos.first;
+
+ // Split formula grouping at the top and bottom boundaries.
+ sc::SharedFormulaUtil::splitFormulaCellGroup(aPos, &rCxt);
+ if (GetDoc().ValidRow(nRow2+1))
+ {
+ aPos = maCells.position(it, nRow2+1);
+ sc::SharedFormulaUtil::splitFormulaCellGroup(aPos, &rCxt);
+ }
+
+ if (GetDoc().IsClipOrUndo())
+ return;
+
+ DetachFormulaCellsHandler aFunc(GetDoc(), &rCxt);
+ sc::ProcessFormula(it, maCells, nRow1, nRow2, aFunc);
+}
+
+static void lcl_AddFormulaGroupBoundaries(const sc::CellStoreType::position_type& rPos,
+ std::vector<SCROW>& rNewSharedRows )
+{
+ sc::CellStoreType::iterator itRet = rPos.first;
+ if (itRet->type != sc::element_type_formula)
+ return;
+
+ ScFormulaCell& rFC = *sc::formula_block::at(*itRet->data, rPos.second);
+ if ( rFC.IsShared() )
+ {
+ const SCROW nSharedTopRow = rFC.GetSharedTopRow();
+ const SCROW nSharedLength = rFC.GetSharedLength();
+ rNewSharedRows.push_back( nSharedTopRow);
+ rNewSharedRows.push_back( nSharedTopRow + nSharedLength - 1);
+ }
+ else
+ {
+ const SCROW nRow = rFC.aPos.Row();
+ rNewSharedRows.push_back( nRow);
+ rNewSharedRows.push_back( nRow);
+ }
+}
+
+sc::CellStoreType::iterator ScColumn::GetPositionToInsert( const sc::CellStoreType::iterator& it, SCROW nRow,
+ std::vector<SCROW>& rNewSharedRows, bool bInsertFormula )
+{
+ // See if we are overwriting an existing formula cell.
+ sc::CellStoreType::position_type aPos = maCells.position(it, nRow);
+ sc::CellStoreType::iterator itRet = aPos.first;
+
+ if (itRet->type == sc::element_type_formula)
+ {
+ ScFormulaCell& rCell = *sc::formula_block::at(*itRet->data, aPos.second);
+ DetachFormulaCell(aPos, rCell, rNewSharedRows);
+ }
+ else if (bInsertFormula && !GetDoc().IsClipOrUndo())
+ {
+ if (nRow > 0)
+ {
+ sc::CellStoreType::position_type aPosBefore = maCells.position(maCells.begin(), nRow-1);
+ lcl_AddFormulaGroupBoundaries(aPosBefore, rNewSharedRows);
+ }
+ if (nRow < GetDoc().MaxRow())
+ {
+ sc::CellStoreType::position_type aPosAfter = maCells.position(maCells.begin(), nRow+1);
+ lcl_AddFormulaGroupBoundaries(aPosAfter, rNewSharedRows);
+ }
+ }
+
+ return itRet;
+}
+
+void ScColumn::AttachNewFormulaCell(
+ const sc::CellStoreType::iterator& itPos, SCROW nRow, ScFormulaCell& rCell,
+ const std::vector<SCROW>& rNewSharedRows,
+ bool bJoin, sc::StartListeningType eListenType )
+{
+ AttachNewFormulaCell(maCells.position(itPos, nRow), rCell, rNewSharedRows, bJoin, eListenType);
+}
+
+void ScColumn::AttachNewFormulaCell(
+ const sc::CellStoreType::position_type& aPos, ScFormulaCell& rCell,
+ const std::vector<SCROW>& rNewSharedRows,
+ bool bJoin, sc::StartListeningType eListenType )
+{
+ if (bJoin)
+ // See if this new formula cell can join an existing shared formula group.
+ JoinNewFormulaCell(aPos, rCell);
+
+ // When we insert from the Clipboard we still have wrong (old) References!
+ // First they are rewired in CopyBlockFromClip via UpdateReference and the
+ // we call StartListeningFromClip and BroadcastFromClip.
+ // If we insert into the Clipboard/andoDoc, we do not use a Broadcast.
+ // After Import we call CalcAfterLoad and in there Listening.
+ ScDocument& rDocument = GetDoc();
+ if (rDocument.IsClipOrUndo() || rDocument.IsInsertingFromOtherDoc())
+ return;
+
+ switch (eListenType)
+ {
+ case sc::ConvertToGroupListening:
+ {
+ auto pPosSet = std::make_shared<sc::ColumnBlockPositionSet>(rDocument);
+ sc::StartListeningContext aStartCxt(rDocument, pPosSet);
+ sc::EndListeningContext aEndCxt(rDocument, pPosSet);
+ SCROW nStartRow, nEndRow;
+ nStartRow = nEndRow = aPos.first->position + aPos.second;
+ for (const SCROW nR : rNewSharedRows)
+ {
+ if (nStartRow > nR)
+ nStartRow = nR;
+ if (nEndRow < nR)
+ nEndRow = nR;
+ }
+ StartListeningFormulaCells(aStartCxt, aEndCxt, nStartRow, nEndRow);
+ }
+ break;
+ case sc::SingleCellListening:
+ rCell.StartListeningTo(rDocument);
+ StartListeningUnshared( rNewSharedRows);
+ break;
+ case sc::NoListening:
+ default:
+ if (!rNewSharedRows.empty())
+ {
+ assert(rNewSharedRows.size() == 2 || rNewSharedRows.size() == 4);
+ // Calling SetNeedsListeningGroup() with a top row sets it to
+ // all affected formula cells of that group.
+ const ScFormulaCell* pFC = GetFormulaCell( rNewSharedRows[0]);
+ assert(pFC); // that *is* supposed to be a top row
+ if (pFC && !pFC->NeedsListening())
+ SetNeedsListeningGroup( rNewSharedRows[0]);
+ if (rNewSharedRows.size() > 2)
+ {
+ pFC = GetFormulaCell( rNewSharedRows[2]);
+ assert(pFC); // that *is* supposed to be a top row
+ if (pFC && !pFC->NeedsListening())
+ SetNeedsListeningGroup( rNewSharedRows[2]);
+ }
+ }
+ break;
+ }
+
+ if (!rDocument.IsCalcingAfterLoad())
+ rCell.SetDirty();
+}
+
+void ScColumn::AttachNewFormulaCells( const sc::CellStoreType::position_type& aPos, size_t nLength,
+ std::vector<SCROW>& rNewSharedRows )
+{
+ // Make sure the whole length consists of formula cells.
+ if (aPos.first->type != sc::element_type_formula)
+ return;
+
+ if (aPos.first->size < aPos.second + nLength)
+ // Block is shorter than specified length.
+ return;
+
+ // Join the top and bottom cells only.
+ ScFormulaCell* pCell1 = sc::formula_block::at(*aPos.first->data, aPos.second);
+ JoinNewFormulaCell(aPos, *pCell1);
+
+ sc::CellStoreType::position_type aPosLast = aPos;
+ aPosLast.second += nLength - 1;
+ ScFormulaCell* pCell2 = sc::formula_block::at(*aPosLast.first->data, aPosLast.second);
+ JoinNewFormulaCell(aPosLast, *pCell2);
+
+ ScDocument& rDocument = GetDoc();
+ if (rDocument.IsClipOrUndo() || rDocument.IsInsertingFromOtherDoc())
+ return;
+
+ const bool bShared = pCell1->IsShared() || pCell2->IsShared();
+ if (bShared)
+ {
+ const SCROW nTopRow = (pCell1->IsShared() ? pCell1->GetSharedTopRow() : pCell1->aPos.Row());
+ const SCROW nBotRow = (pCell2->IsShared() ?
+ pCell2->GetSharedTopRow() + pCell2->GetSharedLength() - 1 : pCell2->aPos.Row());
+ if (rNewSharedRows.empty())
+ {
+ rNewSharedRows.push_back( nTopRow);
+ rNewSharedRows.push_back( nBotRow);
+ }
+ else if (rNewSharedRows.size() == 2)
+ {
+ // Combine into one span.
+ if (rNewSharedRows[0] > nTopRow)
+ rNewSharedRows[0] = nTopRow;
+ if (rNewSharedRows[1] < nBotRow)
+ rNewSharedRows[1] = nBotRow;
+ }
+ else if (rNewSharedRows.size() == 4)
+ {
+ // Merge into one span.
+ // The original two spans are ordered from top to bottom.
+ std::vector<SCROW> aRows { std::min( rNewSharedRows[0], nTopRow), std::max( rNewSharedRows[3], nBotRow) };
+ rNewSharedRows.swap( aRows);
+ }
+ else
+ {
+ assert(!"rNewSharedRows?");
+ }
+ }
+ StartListeningUnshared( rNewSharedRows);
+
+ sc::StartListeningContext aCxt(rDocument);
+ ScFormulaCell** pp = &sc::formula_block::at(*aPos.first->data, aPos.second);
+ ScFormulaCell** ppEnd = pp + nLength;
+ for (; pp != ppEnd; ++pp)
+ {
+ if (!bShared)
+ (*pp)->StartListeningTo(aCxt);
+ if (!rDocument.IsCalcingAfterLoad())
+ (*pp)->SetDirty();
+ }
+}
+
+void ScColumn::BroadcastNewCell( SCROW nRow )
+{
+ // When we insert from the Clipboard we still have wrong (old) References!
+ // First they are rewired in CopyBlockFromClip via UpdateReference and the
+ // we call StartListeningFromClip and BroadcastFromClip.
+ // If we insert into the Clipboard/andoDoc, we do not use a Broadcast.
+ // After Import we call CalcAfterLoad and in there Listening.
+ if (GetDoc().IsClipOrUndo() || GetDoc().IsInsertingFromOtherDoc() || GetDoc().IsCalcingAfterLoad())
+ return;
+
+ Broadcast(nRow);
+}
+
+bool ScColumn::UpdateScriptType( sc::CellTextAttr& rAttr, SCROW nRow, sc::CellStoreType::iterator& itr )
+{
+ if (rAttr.mnScriptType != SvtScriptType::UNKNOWN)
+ // Already updated. Nothing to do.
+ return false;
+
+ // Script type not yet determined. Determine the real script
+ // type, and store it.
+ const ScPatternAttr* pPattern = GetPattern(nRow);
+ if (!pPattern)
+ return false;
+
+ sc::CellStoreType::position_type pos = maCells.position(itr, nRow);
+ itr = pos.first;
+ size_t nOffset = pos.second;
+ ScRefCellValue aCell = GetCellValue( itr, nOffset );
+ ScAddress aPos(nCol, nRow, nTab);
+
+ ScDocument& rDocument = GetDoc();
+ const SfxItemSet* pCondSet = nullptr;
+ ScConditionalFormatList* pCFList = rDocument.GetCondFormList(nTab);
+ if (pCFList)
+ {
+ const ScCondFormatItem& rItem =
+ pPattern->GetItem(ATTR_CONDITIONAL);
+ const ScCondFormatIndexes& rData = rItem.GetCondFormatData();
+ pCondSet = rDocument.GetCondResult(aCell, aPos, *pCFList, rData);
+ }
+
+ SvNumberFormatter* pFormatter = rDocument.GetFormatTable();
+
+ const Color* pColor;
+ sal_uInt32 nFormat = pPattern->GetNumberFormat(pFormatter, pCondSet);
+ OUString aStr = ScCellFormat::GetString(aCell, nFormat, &pColor, *pFormatter, rDocument);
+
+ // Store the real script type to the array.
+ rAttr.mnScriptType = rDocument.GetStringScriptType(aStr);
+ return true;
+}
+
+namespace {
+
+class DeleteAreaHandler
+{
+ ScDocument& mrDoc;
+ std::vector<ScFormulaCell*> maFormulaCells;
+ sc::SingleColumnSpanSet maDeleteRanges;
+
+ bool mbNumeric:1;
+ bool mbString:1;
+ bool mbFormula:1;
+ bool mbDateTime:1;
+ ScColumn& mrCol;
+
+public:
+ DeleteAreaHandler(ScDocument& rDoc, InsertDeleteFlags nDelFlag, ScColumn& rCol) :
+ mrDoc(rDoc),
+ maDeleteRanges(rDoc.GetSheetLimits()),
+ mbNumeric(nDelFlag & InsertDeleteFlags::VALUE),
+ mbString(nDelFlag & InsertDeleteFlags::STRING),
+ mbFormula(nDelFlag & InsertDeleteFlags::FORMULA),
+ mbDateTime(nDelFlag & InsertDeleteFlags::DATETIME),
+ mrCol(rCol) {}
+
+ void operator() (const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize)
+ {
+ switch (node.type)
+ {
+ case sc::element_type_numeric:
+ // Numeric type target datetime and number, thus we have a dedicated function
+ if (!mbNumeric && !mbDateTime)
+ return;
+
+ // If numeric and datetime selected, delete full range
+ if (mbNumeric && mbDateTime)
+ break;
+
+ deleteNumeric(node, nOffset, nDataSize);
+ return;
+ case sc::element_type_string:
+ case sc::element_type_edittext:
+ if (!mbString)
+ return;
+ break;
+ case sc::element_type_formula:
+ {
+ if (!mbFormula)
+ return;
+
+ sc::formula_block::iterator it = sc::formula_block::begin(*node.data) + nOffset;
+ sc::formula_block::iterator itEnd = it + nDataSize;
+ maFormulaCells.insert(maFormulaCells.end(), it, itEnd);
+ }
+ break;
+ case sc::element_type_empty:
+ default:
+ return;
+ }
+
+ // Tag these cells for deletion.
+ SCROW nRow1 = node.position + nOffset;
+ SCROW nRow2 = nRow1 + nDataSize - 1;
+ maDeleteRanges.set(nRow1, nRow2, true);
+ }
+
+ void deleteNumeric(const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize)
+ {
+ size_t nStart = node.position + nOffset;
+ size_t nElements = 1;
+ bool bLastTypeDateTime = isDateTime(nStart); // true = datetime, false = numeric
+ size_t nCount = nStart + nDataSize;
+
+ for (size_t i = nStart + 1; i < nCount; i++)
+ {
+ bool bIsDateTime = isDateTime(i);
+
+ // same type as previous
+ if (bIsDateTime == bLastTypeDateTime)
+ {
+ nElements++;
+ }
+ // type switching
+ else
+ {
+ deleteNumberOrDateTime(nStart, nStart + nElements - 1, bLastTypeDateTime);
+ nStart += nElements;
+ nElements = 1;
+ }
+
+ bLastTypeDateTime = bIsDateTime;
+ }
+
+ // delete last cells
+ deleteNumberOrDateTime(nStart, nStart + nElements - 1, bLastTypeDateTime);
+ }
+
+ void deleteNumberOrDateTime(SCROW nRow1, SCROW nRow2, bool dateTime)
+ {
+ if (!dateTime && !mbNumeric) // numeric flag must be selected
+ return;
+ if (dateTime && !mbDateTime) // datetime flag must be selected
+ return;
+ maDeleteRanges.set(nRow1, nRow2, true);
+ }
+
+ bool isDateTime(size_t position)
+ {
+ SvNumFormatType nType = mrDoc.GetFormatTable()->GetType(
+ mrCol.GetAttr(position, ATTR_VALUE_FORMAT).GetValue());
+
+ return (nType == SvNumFormatType::DATE) || (nType == SvNumFormatType::TIME) ||
+ (nType == SvNumFormatType::DATETIME);
+ }
+
+ /**
+ * Query the formula ranges that may have stopped listening, accounting for
+ * the formula groups.
+ */
+ std::vector<std::pair<SCROW, SCROW>> getFormulaRanges()
+ {
+ std::vector<std::pair<SCROW, SCROW>> aRet;
+
+ for (const ScFormulaCell* pFC : maFormulaCells)
+ {
+ SCROW nTopRow = pFC->aPos.Row();
+ SCROW nBottomRow = pFC->aPos.Row();
+
+ auto xGroup = pFC->GetCellGroup();
+ if (xGroup)
+ {
+ pFC = xGroup->mpTopCell;
+ nTopRow = pFC->aPos.Row();
+ nBottomRow = nTopRow + xGroup->mnLength - 1;
+ }
+
+ aRet.emplace_back(nTopRow, nBottomRow);
+ }
+
+ return aRet;
+ }
+
+ void endFormulas()
+ {
+ mrDoc.EndListeningFormulaCells(maFormulaCells);
+ }
+
+ sc::SingleColumnSpanSet& getSpans()
+ {
+ return maDeleteRanges;
+ }
+};
+
+class EmptyCells
+{
+ ScColumn& mrColumn;
+ sc::ColumnBlockPosition& mrPos;
+
+ static void splitFormulaGrouping(const sc::CellStoreType::position_type& rPos)
+ {
+ if (rPos.first->type == sc::element_type_formula)
+ {
+ ScFormulaCell& rCell = *sc::formula_block::at(*rPos.first->data, rPos.second);
+ sc::SharedFormulaUtil::unshareFormulaCell(rPos, rCell);
+ }
+ }
+
+public:
+ EmptyCells( sc::ColumnBlockPosition& rPos, ScColumn& rColumn ) :
+ mrColumn(rColumn), mrPos(rPos) {}
+
+ void operator() (const sc::RowSpan& rSpan)
+ {
+ sc::CellStoreType& rCells = mrColumn.GetCellStore();
+
+ // First, split formula grouping at the top and bottom boundaries
+ // before emptying the cells.
+ sc::CellStoreType::position_type aPos = rCells.position(mrPos.miCellPos, rSpan.mnRow1);
+ splitFormulaGrouping(aPos);
+ aPos = rCells.position(aPos.first, rSpan.mnRow2);
+ splitFormulaGrouping(aPos);
+
+ mrPos.miCellPos = rCells.set_empty(mrPos.miCellPos, rSpan.mnRow1, rSpan.mnRow2);
+ mrPos.miCellTextAttrPos = mrColumn.GetCellAttrStore().set_empty(mrPos.miCellTextAttrPos, rSpan.mnRow1, rSpan.mnRow2);
+ }
+};
+
+}
+
+ScColumn::DeleteCellsResult::DeleteCellsResult( const ScDocument& rDoc ) :
+ aDeletedRows( rDoc.GetSheetLimits() )
+{
+}
+
+std::unique_ptr<ScColumn::DeleteCellsResult> ScColumn::DeleteCells(
+ sc::ColumnBlockPosition& rBlockPos, SCROW nRow1, SCROW nRow2, InsertDeleteFlags nDelFlag )
+{
+ std::unique_ptr<DeleteCellsResult> xResult = std::make_unique<DeleteCellsResult>(GetDoc());
+
+ // Determine which cells to delete based on the deletion flags.
+ DeleteAreaHandler aFunc(GetDoc(), nDelFlag, *this);
+ sc::CellStoreType::iterator itPos = maCells.position(rBlockPos.miCellPos, nRow1).first;
+ sc::ProcessBlock(itPos, maCells, aFunc, nRow1, nRow2);
+ xResult->aFormulaRanges = aFunc.getFormulaRanges();
+ aFunc.endFormulas(); // Have the formula cells stop listening.
+
+ // Get the deletion spans.
+ sc::SingleColumnSpanSet::SpansType aSpans;
+ aFunc.getSpans().getSpans(aSpans);
+
+ // Delete the cells for real.
+ // tdf#139820: Deleting in reverse order is more efficient.
+ std::for_each(aSpans.rbegin(), aSpans.rend(), EmptyCells(rBlockPos, *this));
+ CellStorageModified();
+
+ aFunc.getSpans().swap(xResult->aDeletedRows);
+
+ return xResult;
+}
+
+void ScColumn::DeleteArea(
+ SCROW nStartRow, SCROW nEndRow, InsertDeleteFlags nDelFlag, bool bBroadcast,
+ sc::ColumnSpanSet* pBroadcastSpans )
+{
+ InsertDeleteFlags nContMask = InsertDeleteFlags::CONTENTS;
+ // InsertDeleteFlags::NOCAPTIONS needs to be passed too, if InsertDeleteFlags::NOTE is set
+ if( nDelFlag & InsertDeleteFlags::NOTE )
+ nContMask |= InsertDeleteFlags::NOCAPTIONS;
+ InsertDeleteFlags nContFlag = nDelFlag & nContMask;
+
+ sc::ColumnBlockPosition aBlockPos;
+ InitBlockPosition(aBlockPos);
+ std::unique_ptr<DeleteCellsResult> xResult;
+
+ if (!IsEmptyData() && nContFlag != InsertDeleteFlags::NONE)
+ {
+ xResult = DeleteCells(aBlockPos, nStartRow, nEndRow, nDelFlag);
+ if (pBroadcastSpans)
+ {
+ sc::SingleColumnSpanSet::SpansType aSpans;
+ xResult->aDeletedRows.getSpans(aSpans);
+ for (const auto& rSpan : aSpans)
+ pBroadcastSpans->set(GetDoc(), nTab, nCol, rSpan.mnRow1, rSpan.mnRow2, true);
+ }
+ }
+
+ if (nDelFlag & InsertDeleteFlags::NOTE)
+ {
+ bool bForgetCaptionOwnership = ((nDelFlag & InsertDeleteFlags::FORGETCAPTIONS) != InsertDeleteFlags::NONE);
+ DeleteCellNotes(aBlockPos, nStartRow, nEndRow, bForgetCaptionOwnership);
+ }
+
+ if (nDelFlag & InsertDeleteFlags::SPARKLINES)
+ {
+ DeleteSparklineCells(aBlockPos, nStartRow, nEndRow);
+ }
+
+ if ( nDelFlag & InsertDeleteFlags::EDITATTR )
+ {
+ OSL_ENSURE( nContFlag == InsertDeleteFlags::NONE, "DeleteArea: Wrong Flags" );
+ RemoveEditAttribs(aBlockPos, nStartRow, nEndRow);
+ }
+
+ // Delete attributes just now
+ if ((nDelFlag & InsertDeleteFlags::ATTRIB) == InsertDeleteFlags::ATTRIB)
+ pAttrArray->DeleteArea( nStartRow, nEndRow );
+ else if ((nDelFlag & InsertDeleteFlags::HARDATTR) == InsertDeleteFlags::HARDATTR)
+ pAttrArray->DeleteHardAttr( nStartRow, nEndRow );
+
+ if (xResult && bBroadcast)
+ {
+ // Broadcast on only cells that were deleted; no point broadcasting on
+ // cells that were already empty before the deletion.
+ std::vector<SCROW> aRows;
+ xResult->aDeletedRows.getRows(aRows);
+ BroadcastCells(aRows, SfxHintId::ScDataChanged);
+ }
+}
+
+void ScColumn::InitBlockPosition( sc::ColumnBlockPosition& rBlockPos )
+{
+ rBlockPos.miBroadcasterPos = maBroadcasters.begin();
+ rBlockPos.miCellNotePos = maCellNotes.begin();
+ rBlockPos.miCellTextAttrPos = maCellTextAttrs.begin();
+ rBlockPos.miCellPos = maCells.begin();
+ rBlockPos.miSparklinePos = maSparklines.begin();
+}
+
+void ScColumn::InitBlockPosition( sc::ColumnBlockConstPosition& rBlockPos ) const
+{
+ rBlockPos.miCellNotePos = maCellNotes.begin();
+ rBlockPos.miCellTextAttrPos = maCellTextAttrs.begin();
+ rBlockPos.miCellPos = maCells.begin();
+}
+
+namespace {
+
+class CopyAttrArrayByRange
+{
+ ScAttrArray& mrDestAttrArray;
+ ScAttrArray& mrSrcAttrArray;
+ tools::Long mnRowOffset;
+public:
+ CopyAttrArrayByRange(ScAttrArray& rDestAttrArray, ScAttrArray& rSrcAttrArray, tools::Long nRowOffset) :
+ mrDestAttrArray(rDestAttrArray), mrSrcAttrArray(rSrcAttrArray), mnRowOffset(nRowOffset) {}
+
+ void operator() (const sc::RowSpan& rSpan)
+ {
+ mrDestAttrArray.CopyAreaSafe(
+ rSpan.mnRow1+mnRowOffset, rSpan.mnRow2+mnRowOffset, mnRowOffset, mrSrcAttrArray);
+ }
+};
+
+class CopyCellsFromClipHandler
+{
+ sc::CopyFromClipContext& mrCxt;
+ ScColumn& mrSrcCol;
+ ScColumn& mrDestCol;
+ SCTAB mnTab;
+ SCCOL mnCol;
+ SCTAB mnSrcTab;
+ SCCOL mnSrcCol;
+ tools::Long mnRowOffset;
+ sc::ColumnBlockPosition maDestBlockPos;
+ sc::ColumnBlockPosition* mpDestBlockPos; // to save it for next iteration.
+ svl::SharedStringPool* mpSharedStringPool;
+
+ void insertRefCell(SCROW nSrcRow, SCROW nDestRow)
+ {
+ ScAddress aSrcPos(mnSrcCol, nSrcRow, mnSrcTab);
+ ScAddress aDestPos(mnCol, nDestRow, mnTab);
+ ScSingleRefData aRef;
+ aRef.InitAddress(aSrcPos);
+ aRef.SetFlag3D(true);
+
+ ScTokenArray aArr(*mrCxt.getDestDoc());
+ aArr.AddSingleReference(aRef);
+
+ mrDestCol.SetFormulaCell(
+ maDestBlockPos, nDestRow, new ScFormulaCell(mrDestCol.GetDoc(), aDestPos, aArr));
+ }
+
+ void duplicateNotes(SCROW nStartRow, size_t nDataSize, bool bCloneCaption )
+ {
+ mrSrcCol.DuplicateNotes(nStartRow, nDataSize, mrDestCol, maDestBlockPos, bCloneCaption, mnRowOffset);
+ }
+
+ void duplicateSparklines(SCROW nStartRow, size_t nDataSize)
+ {
+ mrSrcCol.DuplicateSparklines(nStartRow, nDataSize, mrDestCol, maDestBlockPos, mnRowOffset);
+ }
+
+public:
+ CopyCellsFromClipHandler(sc::CopyFromClipContext& rCxt, ScColumn& rSrcCol, ScColumn& rDestCol, SCTAB nDestTab, SCCOL nDestCol, tools::Long nRowOffset, svl::SharedStringPool* pSharedStringPool) :
+ mrCxt(rCxt),
+ mrSrcCol(rSrcCol),
+ mrDestCol(rDestCol),
+ mnTab(nDestTab),
+ mnCol(nDestCol),
+ mnSrcTab(rSrcCol.GetTab()),
+ mnSrcCol(rSrcCol.GetCol()),
+ mnRowOffset(nRowOffset),
+ mpDestBlockPos(mrCxt.getBlockPosition(nDestTab, nDestCol)),
+ mpSharedStringPool(pSharedStringPool)
+ {
+ if (mpDestBlockPos)
+ {
+ {
+ // Re-initialize the broadcaster position hint, which may have
+ // become invalid by the time it gets here...
+ sc::ColumnBlockPosition aTempPos;
+ mrDestCol.InitBlockPosition(aTempPos);
+ mpDestBlockPos->miBroadcasterPos = aTempPos.miBroadcasterPos;
+ }
+ maDestBlockPos = *mpDestBlockPos;
+ }
+ else
+ mrDestCol.InitBlockPosition(maDestBlockPos);
+ }
+
+ ~CopyCellsFromClipHandler()
+ {
+ if (mpDestBlockPos)
+ // Don't forget to save this to the context!
+ *mpDestBlockPos = maDestBlockPos;
+ }
+
+ void operator() (const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize)
+ {
+ SCROW nSrcRow1 = node.position + nOffset;
+ bool bCopyCellNotes = mrCxt.isCloneNotes();
+ bool bCopySparklines = mrCxt.isCloneSparklines();
+
+ InsertDeleteFlags nFlags = mrCxt.getInsertFlag();
+
+ if (node.type == sc::element_type_empty)
+ {
+ if (bCopyCellNotes && !mrCxt.isSkipEmptyCells())
+ {
+ bool bCloneCaption = (nFlags & InsertDeleteFlags::NOCAPTIONS) == InsertDeleteFlags::NONE;
+ duplicateNotes(nSrcRow1, nDataSize, bCloneCaption );
+ }
+ if (bCopySparklines) // If there is a sparkline is it empty?
+ {
+ duplicateSparklines(nSrcRow1, nDataSize);
+ }
+ return;
+ }
+
+ bool bNumeric = (nFlags & InsertDeleteFlags::VALUE) != InsertDeleteFlags::NONE;
+ bool bDateTime = (nFlags & InsertDeleteFlags::DATETIME) != InsertDeleteFlags::NONE;
+ bool bString = (nFlags & InsertDeleteFlags::STRING) != InsertDeleteFlags::NONE;
+ bool bBoolean = (nFlags & InsertDeleteFlags::SPECIAL_BOOLEAN) != InsertDeleteFlags::NONE;
+ bool bFormula = (nFlags & InsertDeleteFlags::FORMULA) != InsertDeleteFlags::NONE;
+
+ bool bAsLink = mrCxt.isAsLink();
+
+ switch (node.type)
+ {
+ case sc::element_type_numeric:
+ {
+ // We need to copy numeric cells individually because of date type check.
+ sc::numeric_block::const_iterator it = sc::numeric_block::begin(*node.data);
+ std::advance(it, nOffset);
+ sc::numeric_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+ for (SCROW nSrcRow = nSrcRow1; it != itEnd; ++it, ++nSrcRow)
+ {
+ bool bCopy = mrCxt.isDateCell(mrSrcCol, nSrcRow) ? bDateTime : bNumeric;
+ if (!bCopy)
+ continue;
+
+ if (bAsLink)
+ insertRefCell(nSrcRow, nSrcRow + mnRowOffset);
+ else
+ mrDestCol.SetValue(maDestBlockPos, nSrcRow + mnRowOffset, *it);
+ }
+ }
+ break;
+ case sc::element_type_string:
+ {
+ if (!bString)
+ break;
+
+ sc::string_block::const_iterator it = sc::string_block::begin(*node.data);
+ std::advance(it, nOffset);
+ sc::string_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+ for (SCROW nSrcRow = nSrcRow1; it != itEnd; ++it, ++nSrcRow)
+ {
+ if (bAsLink)
+ insertRefCell(nSrcRow, nSrcRow + mnRowOffset);
+ else if (mpSharedStringPool)
+ {
+ // Re-intern the string if source is a different document.
+ svl::SharedString aInterned = mpSharedStringPool->intern( (*it).getString());
+ mrDestCol.SetRawString(maDestBlockPos, nSrcRow + mnRowOffset, aInterned);
+ }
+ else
+ mrDestCol.SetRawString(maDestBlockPos, nSrcRow + mnRowOffset, *it);
+ }
+ }
+ break;
+ case sc::element_type_edittext:
+ {
+ if (!bString)
+ break;
+
+ sc::edittext_block::const_iterator it = sc::edittext_block::begin(*node.data);
+ std::advance(it, nOffset);
+ sc::edittext_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+ for (SCROW nSrcRow = nSrcRow1; it != itEnd; ++it, ++nSrcRow)
+ {
+
+ if (bAsLink)
+ insertRefCell(nSrcRow, nSrcRow + mnRowOffset);
+ else
+ mrDestCol.SetEditText(maDestBlockPos, nSrcRow + mnRowOffset, **it);
+ }
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ sc::formula_block::const_iterator it = sc::formula_block::begin(*node.data);
+ std::advance(it, nOffset);
+ sc::formula_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+ for (SCROW nSrcRow = nSrcRow1; it != itEnd; ++it, ++nSrcRow)
+ {
+ ScFormulaCell& rSrcCell = **it;
+ bool bForceFormula = false;
+ if (bBoolean)
+ {
+ // See if the formula consists of =TRUE() or =FALSE().
+ const ScTokenArray* pCode = rSrcCell.GetCode();
+ if (pCode && pCode->GetLen() == 1)
+ {
+ const formula::FormulaToken* p = pCode->FirstToken();
+ if (p->GetOpCode() == ocTrue || p->GetOpCode() == ocFalse)
+ // This is a boolean formula.
+ bForceFormula = true;
+ }
+ }
+
+ ScAddress aDestPos(mnCol, nSrcRow + mnRowOffset, mnTab);
+ if (bFormula || bForceFormula)
+ {
+ if (bAsLink)
+ insertRefCell(nSrcRow, nSrcRow + mnRowOffset);
+ else
+ {
+ mrDestCol.SetFormulaCell(
+ maDestBlockPos, nSrcRow + mnRowOffset,
+ new ScFormulaCell(rSrcCell, mrDestCol.GetDoc(), aDestPos),
+ sc::SingleCellListening,
+ rSrcCell.NeedsNumberFormat());
+ }
+ }
+ else if (bNumeric || bDateTime || bString)
+ {
+ // Always just copy the original row to the Undo Document;
+ // do not create Value/string cells from formulas
+
+ FormulaError nErr = rSrcCell.GetErrCode();
+ if (nErr != FormulaError::NONE)
+ {
+ // error codes are cloned with values
+ if (bNumeric)
+ {
+ if (bAsLink)
+ insertRefCell(nSrcRow, nSrcRow + mnRowOffset);
+ else
+ {
+ ScFormulaCell* pErrCell = new ScFormulaCell(mrDestCol.GetDoc(), aDestPos);
+ pErrCell->SetErrCode(nErr);
+ mrDestCol.SetFormulaCell(
+ maDestBlockPos, nSrcRow + mnRowOffset, pErrCell);
+ }
+ }
+ }
+ else if (rSrcCell.IsEmptyDisplayedAsString())
+ {
+ // Empty stays empty and doesn't become 0.
+ continue;
+ }
+ else if (rSrcCell.IsValue())
+ {
+ bool bCopy = mrCxt.isDateCell(mrSrcCol, nSrcRow) ? bDateTime : bNumeric;
+ if (!bCopy)
+ continue;
+
+ if (bAsLink)
+ insertRefCell(nSrcRow, nSrcRow + mnRowOffset);
+ else
+ mrDestCol.SetValue(maDestBlockPos, nSrcRow + mnRowOffset, rSrcCell.GetValue());
+ }
+ else if (bString)
+ {
+ svl::SharedString aStr = rSrcCell.GetString();
+ if (aStr.isEmpty())
+ // do not clone empty string
+ continue;
+
+ if (bAsLink)
+ insertRefCell(nSrcRow, nSrcRow + mnRowOffset);
+ else if (rSrcCell.IsMultilineResult())
+ {
+ // Clone as an edit text object.
+ ScFieldEditEngine& rEngine = mrDestCol.GetDoc().GetEditEngine();
+ rEngine.SetTextCurrentDefaults(aStr.getString());
+ mrDestCol.SetEditText(maDestBlockPos, nSrcRow + mnRowOffset, rEngine.CreateTextObject());
+ }
+ else if (mpSharedStringPool)
+ {
+ // Re-intern the string if source is a different document.
+ svl::SharedString aInterned = mpSharedStringPool->intern( aStr.getString());
+ mrDestCol.SetRawString(maDestBlockPos, nSrcRow + mnRowOffset, aInterned);
+ }
+ else
+ {
+ mrDestCol.SetRawString(maDestBlockPos, nSrcRow + mnRowOffset, aStr);
+ }
+ }
+ }
+ }
+ }
+ break;
+ default:
+ ;
+ }
+ if (bCopyCellNotes)
+ {
+ bool bCloneCaption = (nFlags & InsertDeleteFlags::NOCAPTIONS) == InsertDeleteFlags::NONE;
+ duplicateNotes(nSrcRow1, nDataSize, bCloneCaption );
+ }
+ if (bCopySparklines)
+ {
+ duplicateSparklines(nSrcRow1, nDataSize);
+ }
+ }
+};
+
+class CopyTextAttrsFromClipHandler
+{
+ sc::CellTextAttrStoreType& mrAttrs;
+ size_t mnDelta;
+ sc::ColumnBlockPosition maDestBlockPos;
+ sc::ColumnBlockPosition* mpDestBlockPos; // to save it for next iteration.
+
+public:
+ CopyTextAttrsFromClipHandler( sc::CopyFromClipContext& rCxt, sc::CellTextAttrStoreType& rAttrs,
+ ScColumn& rDestCol, SCTAB nDestTab, SCCOL nDestCol, size_t nDelta ) :
+ mrAttrs(rAttrs),
+ mnDelta(nDelta),
+ mpDestBlockPos(rCxt.getBlockPosition(nDestTab, nDestCol))
+ {
+ if (mpDestBlockPos)
+ maDestBlockPos.miCellTextAttrPos = mpDestBlockPos->miCellTextAttrPos;
+ else
+ rDestCol.InitBlockPosition(maDestBlockPos);
+ }
+
+ ~CopyTextAttrsFromClipHandler()
+ {
+ if (mpDestBlockPos)
+ // Don't forget to save this to the context!
+ mpDestBlockPos->miCellTextAttrPos = maDestBlockPos.miCellTextAttrPos;
+ }
+
+ void operator() ( const sc::CellTextAttrStoreType::value_type& aNode, size_t nOffset, size_t nDataSize )
+ {
+ if (aNode.type != sc::element_type_celltextattr)
+ return;
+
+ sc::celltextattr_block::const_iterator it = sc::celltextattr_block::begin(*aNode.data);
+ std::advance(it, nOffset);
+ sc::celltextattr_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+
+ size_t nPos = aNode.position + nOffset + mnDelta;
+ maDestBlockPos.miCellTextAttrPos = mrAttrs.set(maDestBlockPos.miCellTextAttrPos, nPos, it, itEnd);
+ }
+};
+
+}
+
+// rColumn = source
+// nRow1, nRow2 = target position
+
+void ScColumn::CopyFromClip(
+ sc::CopyFromClipContext& rCxt, SCROW nRow1, SCROW nRow2, tools::Long nDy, ScColumn& rColumn )
+{
+ sc::ColumnBlockPosition* pBlockPos = rCxt.getBlockPosition(nTab, nCol);
+ if (!pBlockPos)
+ return;
+
+ if ((rCxt.getInsertFlag() & InsertDeleteFlags::ATTRIB) != InsertDeleteFlags::NONE)
+ {
+ if (rCxt.isSkipEmptyCells())
+ {
+ // copy only attributes for non-empty cells between nRow1-nDy and nRow2-nDy.
+ sc::SingleColumnSpanSet aSpanSet(GetDoc().GetSheetLimits());
+ aSpanSet.scan(rColumn, nRow1-nDy, nRow2-nDy);
+ sc::SingleColumnSpanSet::SpansType aSpans;
+ aSpanSet.getSpans(aSpans);
+ std::for_each(
+ aSpans.begin(), aSpans.end(), CopyAttrArrayByRange(*rColumn.pAttrArray, *pAttrArray, nDy));
+ }
+ else
+ rColumn.pAttrArray->CopyAreaSafe( nRow1, nRow2, nDy, *pAttrArray );
+ }
+ if ((rCxt.getInsertFlag() & InsertDeleteFlags::CONTENTS) == InsertDeleteFlags::NONE)
+ return;
+
+ ScDocument& rDocument = GetDoc();
+ if (rCxt.isAsLink() && rCxt.getInsertFlag() == InsertDeleteFlags::ALL)
+ {
+ // We also reference empty cells for "ALL"
+ // InsertDeleteFlags::ALL must always contain more flags when compared to "Insert contents" as
+ // contents can be selected one by one!
+
+ ScAddress aDestPos( nCol, 0, nTab ); // Adapt Row
+
+ // Create reference (Source Position)
+ ScSingleRefData aRef;
+ aRef.InitFlags(); // -> All absolute
+ aRef.SetAbsCol(rColumn.nCol);
+ aRef.SetAbsTab(rColumn.nTab);
+ aRef.SetFlag3D(true);
+
+ for (SCROW nDestRow = nRow1; nDestRow <= nRow2; nDestRow++)
+ {
+ aRef.SetAbsRow(nDestRow - nDy); // Source row
+ aDestPos.SetRow( nDestRow );
+
+ ScTokenArray aArr(GetDoc());
+ aArr.AddSingleReference( aRef );
+ SetFormulaCell(*pBlockPos, nDestRow, new ScFormulaCell(rDocument, aDestPos, aArr));
+ }
+
+ // Don't forget to copy the cell text attributes.
+ CopyTextAttrsFromClipHandler aFunc(rCxt, maCellTextAttrs, *this, nTab, nCol, nDy);
+ sc::ParseBlock(rColumn.maCellTextAttrs.begin(), rColumn.maCellTextAttrs, aFunc, nRow1-nDy, nRow2-nDy);
+
+ return;
+ }
+
+ // Compare the ScDocumentPool* to determine if we are copying within the
+ // same document. If not, re-intern shared strings.
+ svl::SharedStringPool* pSharedStringPool = (rColumn.GetDoc().GetPool() != rDocument.GetPool()) ?
+ &rDocument.GetSharedStringPool() : nullptr;
+
+ // nRow1 to nRow2 is for destination (this) column. Subtract nDy to get the source range.
+ // Copy all cells in the source column (rColumn) from nRow1-nDy to nRow2-nDy to this column.
+ {
+ CopyCellsFromClipHandler aFunc(rCxt, rColumn, *this, nTab, nCol, nDy, pSharedStringPool);
+ sc::ParseBlock(rColumn.maCells.begin(), rColumn.maCells, aFunc, nRow1-nDy, nRow2-nDy);
+ }
+
+ {
+ // Don't forget to copy the cell text attributes.
+ CopyTextAttrsFromClipHandler aFunc(rCxt, maCellTextAttrs, *this, nTab, nCol, nDy);
+ sc::ParseBlock(rColumn.maCellTextAttrs.begin(), rColumn.maCellTextAttrs, aFunc, nRow1-nDy, nRow2-nDy);
+ }
+}
+
+void ScColumn::MixMarked(
+ sc::MixDocContext& rCxt, const ScMarkData& rMark, ScPasteFunc nFunction,
+ bool bSkipEmpty, const ScColumn& rSrcCol )
+{
+ SCROW nRow1, nRow2;
+
+ if (rMark.IsMultiMarked())
+ {
+ ScMultiSelIter aIter( rMark.GetMultiSelData(), nCol );
+ while (aIter.Next( nRow1, nRow2 ))
+ MixData(rCxt, nRow1, nRow2, nFunction, bSkipEmpty, rSrcCol);
+ }
+}
+
+namespace {
+
+// Result in rVal1
+bool lcl_DoFunction( double& rVal1, double nVal2, ScPasteFunc nFunction )
+{
+ bool bOk = false;
+ switch (nFunction)
+ {
+ case ScPasteFunc::ADD:
+ bOk = SubTotal::SafePlus( rVal1, nVal2 );
+ break;
+ case ScPasteFunc::SUB:
+ nVal2 = -nVal2; // FIXME: Can we do this always without error?
+ bOk = SubTotal::SafePlus( rVal1, nVal2 );
+ break;
+ case ScPasteFunc::MUL:
+ bOk = SubTotal::SafeMult( rVal1, nVal2 );
+ break;
+ case ScPasteFunc::DIV:
+ bOk = SubTotal::SafeDiv( rVal1, nVal2 );
+ break;
+ default: break;
+ }
+ return bOk;
+}
+
+void lcl_AddCode( ScTokenArray& rArr, const ScFormulaCell* pCell )
+{
+ rArr.AddOpCode(ocOpen);
+
+ const ScTokenArray* pCode = pCell->GetCode();
+ if (pCode)
+ {
+ FormulaTokenArrayPlainIterator aIter(*pCode);
+ const formula::FormulaToken* pToken = aIter.First();
+ while (pToken)
+ {
+ rArr.AddToken( *pToken );
+ pToken = aIter.Next();
+ }
+ }
+
+ rArr.AddOpCode(ocClose);
+}
+
+class MixDataHandler
+{
+ ScColumn& mrDestColumn;
+ sc::ColumnBlockPosition& mrBlockPos;
+
+ sc::CellStoreType maNewCells;
+ sc::CellStoreType::iterator miNewCellsPos;
+
+ size_t mnRowOffset;
+ ScPasteFunc mnFunction;
+
+ bool mbSkipEmpty;
+
+ void doFunction( size_t nDestRow, double fVal1, double fVal2 )
+ {
+ bool bOk = lcl_DoFunction(fVal1, fVal2, mnFunction);
+
+ if (bOk)
+ miNewCellsPos = maNewCells.set(miNewCellsPos, nDestRow-mnRowOffset, fVal1);
+ else
+ {
+ ScAddress aPos(mrDestColumn.GetCol(), nDestRow, mrDestColumn.GetTab());
+
+ ScFormulaCell* pFC = new ScFormulaCell(mrDestColumn.GetDoc(), aPos);
+ pFC->SetErrCode(FormulaError::NoValue);
+
+ miNewCellsPos = maNewCells.set(miNewCellsPos, nDestRow-mnRowOffset, pFC);
+ }
+ }
+
+public:
+ MixDataHandler(
+ sc::ColumnBlockPosition& rBlockPos,
+ ScColumn& rDestColumn,
+ SCROW nRow1, SCROW nRow2,
+ ScPasteFunc nFunction, bool bSkipEmpty) :
+ mrDestColumn(rDestColumn),
+ mrBlockPos(rBlockPos),
+ maNewCells(nRow2 - nRow1 + 1),
+ miNewCellsPos(maNewCells.begin()),
+ mnRowOffset(nRow1),
+ mnFunction(nFunction),
+ mbSkipEmpty(bSkipEmpty)
+ {
+ }
+
+ void operator() (size_t nRow, double f)
+ {
+ sc::CellStoreType::position_type aPos = mrDestColumn.GetCellStore().position(mrBlockPos.miCellPos, nRow);
+ mrBlockPos.miCellPos = aPos.first;
+ switch (aPos.first->type)
+ {
+ case sc::element_type_empty:
+ case sc::element_type_numeric:
+ {
+ double fSrcVal = 0.0;
+ if (aPos.first->type == sc::element_type_numeric)
+ fSrcVal = sc::numeric_block::at(*aPos.first->data, aPos.second);
+
+ // Both src and dest are of numeric type.
+ doFunction(nRow, f, fSrcVal);
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ // Combination of value and at least one formula -> Create formula
+ ScTokenArray aArr(mrDestColumn.GetDoc());
+
+ // First row
+ aArr.AddDouble(f);
+
+ // Operator
+ OpCode eOp = ocAdd;
+ switch (mnFunction)
+ {
+ case ScPasteFunc::ADD: eOp = ocAdd; break;
+ case ScPasteFunc::SUB: eOp = ocSub; break;
+ case ScPasteFunc::MUL: eOp = ocMul; break;
+ case ScPasteFunc::DIV: eOp = ocDiv; break;
+ default: break;
+ }
+ aArr.AddOpCode(eOp); // Function
+
+ // Second row
+ ScFormulaCell* pDest = sc::formula_block::at(*aPos.first->data, aPos.second);
+ lcl_AddCode(aArr, pDest);
+
+ miNewCellsPos = maNewCells.set(
+ miNewCellsPos, nRow-mnRowOffset,
+ new ScFormulaCell(
+ mrDestColumn.GetDoc(), ScAddress(mrDestColumn.GetCol(), nRow, mrDestColumn.GetTab()), aArr));
+ }
+ break;
+ case sc::element_type_string:
+ case sc::element_type_edittext:
+ {
+ // Destination cell is not a number. Just take the source cell.
+ miNewCellsPos = maNewCells.set(miNewCellsPos, nRow-mnRowOffset, f);
+ }
+ break;
+ default:
+ ;
+ }
+ }
+
+ void operator() (size_t nRow, const svl::SharedString& rStr)
+ {
+ miNewCellsPos = maNewCells.set(miNewCellsPos, nRow-mnRowOffset, rStr);
+ }
+
+ void operator() (size_t nRow, const EditTextObject* p)
+ {
+ miNewCellsPos = maNewCells.set(miNewCellsPos, nRow-mnRowOffset, p->Clone().release());
+ }
+
+ void operator() (size_t nRow, const ScFormulaCell* p)
+ {
+ sc::CellStoreType::position_type aPos = mrDestColumn.GetCellStore().position(mrBlockPos.miCellPos, nRow);
+ mrBlockPos.miCellPos = aPos.first;
+ switch (aPos.first->type)
+ {
+ case sc::element_type_numeric:
+ {
+ // Source is formula, and dest is value.
+ ScTokenArray aArr(mrDestColumn.GetDoc());
+
+ // First row
+ lcl_AddCode(aArr, p);
+
+ // Operator
+ OpCode eOp = ocAdd;
+ switch (mnFunction)
+ {
+ case ScPasteFunc::ADD: eOp = ocAdd; break;
+ case ScPasteFunc::SUB: eOp = ocSub; break;
+ case ScPasteFunc::MUL: eOp = ocMul; break;
+ case ScPasteFunc::DIV: eOp = ocDiv; break;
+ default: break;
+ }
+ aArr.AddOpCode(eOp); // Function
+
+ // Second row
+ aArr.AddDouble(sc::numeric_block::at(*aPos.first->data, aPos.second));
+
+ miNewCellsPos = maNewCells.set(
+ miNewCellsPos, nRow-mnRowOffset,
+ new ScFormulaCell(
+ mrDestColumn.GetDoc(), ScAddress(mrDestColumn.GetCol(), nRow, mrDestColumn.GetTab()), aArr));
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ // Both are formulas.
+ ScTokenArray aArr(mrDestColumn.GetDoc());
+
+ // First row
+ lcl_AddCode(aArr, p);
+
+ // Operator
+ OpCode eOp = ocAdd;
+ switch (mnFunction)
+ {
+ case ScPasteFunc::ADD: eOp = ocAdd; break;
+ case ScPasteFunc::SUB: eOp = ocSub; break;
+ case ScPasteFunc::MUL: eOp = ocMul; break;
+ case ScPasteFunc::DIV: eOp = ocDiv; break;
+ default: break;
+ }
+ aArr.AddOpCode(eOp); // Function
+
+ // Second row
+ ScFormulaCell* pDest = sc::formula_block::at(*aPos.first->data, aPos.second);
+ lcl_AddCode(aArr, pDest);
+
+ miNewCellsPos = maNewCells.set(
+ miNewCellsPos, nRow-mnRowOffset,
+ new ScFormulaCell(
+ mrDestColumn.GetDoc(), ScAddress(mrDestColumn.GetCol(), nRow, mrDestColumn.GetTab()), aArr));
+ }
+ break;
+ case sc::element_type_string:
+ case sc::element_type_edittext:
+ case sc::element_type_empty:
+ {
+ // Destination cell is not a number. Just take the source cell.
+ ScAddress aDestPos(mrDestColumn.GetCol(), nRow, mrDestColumn.GetTab());
+ miNewCellsPos = maNewCells.set(
+ miNewCellsPos, nRow-mnRowOffset, new ScFormulaCell(*p, mrDestColumn.GetDoc(), aDestPos));
+ }
+ break;
+ default:
+ ;
+ }
+ }
+
+ /**
+ * Empty cell series in the source (clip) document.
+ */
+ void operator() (mdds::mtv::element_t, size_t nTopRow, size_t nDataSize)
+ {
+ if (mbSkipEmpty)
+ return;
+
+ // Source cells are empty. Treat them as if they have a value of 0.0.
+ for (size_t i = 0; i < nDataSize; ++i)
+ {
+ size_t nDestRow = nTopRow + i;
+ sc::CellStoreType::position_type aPos = mrDestColumn.GetCellStore().position(mrBlockPos.miCellPos, nDestRow);
+ mrBlockPos.miCellPos = aPos.first;
+ switch (aPos.first->type)
+ {
+ case sc::element_type_numeric:
+ {
+ double fVal2 = sc::numeric_block::at(*aPos.first->data, aPos.second);
+ doFunction(nDestRow, 0.0, fVal2);
+ }
+ break;
+ case sc::element_type_string:
+ {
+ const svl::SharedString& aVal = sc::string_block::at(*aPos.first->data, aPos.second);
+ miNewCellsPos = maNewCells.set(
+ miNewCellsPos, nDestRow-mnRowOffset, aVal);
+ }
+ break;
+ case sc::element_type_edittext:
+ {
+ EditTextObject* pObj = sc::edittext_block::at(*aPos.first->data, aPos.second);
+ miNewCellsPos = maNewCells.set(
+ miNewCellsPos, nDestRow-mnRowOffset, pObj->Clone().release());
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ ScTokenArray aArr(mrDestColumn.GetDoc());
+
+ // First row
+ ScFormulaCell* pSrc = sc::formula_block::at(*aPos.first->data, aPos.second);
+ lcl_AddCode( aArr, pSrc);
+
+ // Operator
+ OpCode eOp = ocAdd;
+ switch (mnFunction)
+ {
+ case ScPasteFunc::ADD: eOp = ocAdd; break;
+ case ScPasteFunc::SUB: eOp = ocSub; break;
+ case ScPasteFunc::MUL: eOp = ocMul; break;
+ case ScPasteFunc::DIV: eOp = ocDiv; break;
+ default: break;
+ }
+
+ aArr.AddOpCode(eOp); // Function
+ aArr.AddDouble(0.0);
+
+ miNewCellsPos = maNewCells.set(
+ miNewCellsPos, nDestRow-mnRowOffset,
+ new ScFormulaCell(
+ mrDestColumn.GetDoc(), ScAddress(mrDestColumn.GetCol(), nDestRow, mrDestColumn.GetTab()), aArr));
+ }
+ break;
+ default:
+ ;
+ }
+ }
+ }
+
+ /**
+ * Set the new cells to the destination (this) column.
+ */
+ void commit()
+ {
+ sc::CellStoreType& rDestCells = mrDestColumn.GetCellStore();
+
+ // Stop all formula cells in the destination range first.
+ sc::CellStoreType::position_type aPos = rDestCells.position(mrBlockPos.miCellPos, mnRowOffset);
+ mrDestColumn.DetachFormulaCells(aPos, maNewCells.size(), nullptr);
+
+ // Move the new cells to the destination range.
+ sc::CellStoreType::iterator& itDestPos = mrBlockPos.miCellPos;
+ sc::CellTextAttrStoreType::iterator& itDestAttrPos = mrBlockPos.miCellTextAttrPos;
+
+ for (const auto& rNewCell : maNewCells)
+ {
+ bool bHasContent = true;
+ size_t nDestRow = mnRowOffset + rNewCell.position;
+
+ switch (rNewCell.type)
+ {
+ case sc::element_type_numeric:
+ {
+ sc::numeric_block::iterator itData = sc::numeric_block::begin(*rNewCell.data);
+ sc::numeric_block::iterator itDataEnd = sc::numeric_block::end(*rNewCell.data);
+ itDestPos = mrDestColumn.GetCellStore().set(itDestPos, nDestRow, itData, itDataEnd);
+ }
+ break;
+ case sc::element_type_string:
+ {
+ sc::string_block::iterator itData = sc::string_block::begin(*rNewCell.data);
+ sc::string_block::iterator itDataEnd = sc::string_block::end(*rNewCell.data);
+ itDestPos = rDestCells.set(itDestPos, nDestRow, itData, itDataEnd);
+ }
+ break;
+ case sc::element_type_edittext:
+ {
+ sc::edittext_block::iterator itData = sc::edittext_block::begin(*rNewCell.data);
+ sc::edittext_block::iterator itDataEnd = sc::edittext_block::end(*rNewCell.data);
+ itDestPos = rDestCells.set(itDestPos, nDestRow, itData, itDataEnd);
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ sc::formula_block::iterator itData = sc::formula_block::begin(*rNewCell.data);
+ sc::formula_block::iterator itDataEnd = sc::formula_block::end(*rNewCell.data);
+
+ // Group new formula cells before inserting them.
+ sc::SharedFormulaUtil::groupFormulaCells(itData, itDataEnd);
+
+ // Insert the formula cells to the column.
+ itDestPos = rDestCells.set(itDestPos, nDestRow, itData, itDataEnd);
+
+ // Merge with the previous formula group (if any).
+ aPos = rDestCells.position(itDestPos, nDestRow);
+ sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
+
+ // Merge with the next formula group (if any).
+ size_t nNextRow = nDestRow + rNewCell.size;
+ if (mrDestColumn.GetDoc().ValidRow(nNextRow))
+ {
+ aPos = rDestCells.position(aPos.first, nNextRow);
+ sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
+ }
+
+ // Start listening on cells to get them updated by changes of referenced cells
+ std::vector<SCROW> aNewSharedRows;
+ aPos = rDestCells.position(itDestPos, nDestRow);
+ size_t nFormulaCells = std::distance(itData, itDataEnd);
+ mrDestColumn.AttachNewFormulaCells(aPos, nFormulaCells, aNewSharedRows);
+ }
+ break;
+ case sc::element_type_empty:
+ {
+ itDestPos = rDestCells.set_empty(itDestPos, nDestRow, nDestRow+rNewCell.size-1);
+ bHasContent = false;
+ }
+ break;
+ default:
+ ;
+ }
+
+ sc::CellTextAttrStoreType& rDestAttrs = mrDestColumn.GetCellAttrStore();
+ if (bHasContent)
+ {
+ std::vector<sc::CellTextAttr> aAttrs(rNewCell.size, sc::CellTextAttr());
+ itDestAttrPos = rDestAttrs.set(itDestAttrPos, nDestRow, aAttrs.begin(), aAttrs.end());
+ }
+ else
+ itDestAttrPos = rDestAttrs.set_empty(itDestAttrPos, nDestRow, nDestRow+rNewCell.size-1);
+ }
+
+ maNewCells.release();
+ }
+};
+
+}
+
+void ScColumn::MixData(
+ sc::MixDocContext& rCxt, SCROW nRow1, SCROW nRow2, ScPasteFunc nFunction,
+ bool bSkipEmpty, const ScColumn& rSrcCol )
+{
+ // destination (this column) block position.
+
+ sc::ColumnBlockPosition* p = rCxt.getBlockPosition(nTab, nCol);
+ if (!p)
+ return;
+
+ MixDataHandler aFunc(*p, *this, nRow1, nRow2, nFunction, bSkipEmpty);
+ sc::ParseAll(rSrcCol.maCells.begin(), rSrcCol.maCells, nRow1, nRow2, aFunc, aFunc);
+
+ aFunc.commit();
+ CellStorageModified();
+}
+
+std::unique_ptr<ScAttrIterator> ScColumnData::CreateAttrIterator( SCROW nStartRow, SCROW nEndRow ) const
+{
+ return std::make_unique<ScAttrIterator>( pAttrArray.get(), nStartRow, nEndRow, GetDoc().GetDefPattern() );
+}
+
+namespace {
+
+class StartListenersHandler
+{
+ sc::StartListeningContext* mpCxt;
+ bool mbAllListeners;
+
+public:
+ StartListenersHandler( sc::StartListeningContext& rCxt, bool bAllListeners ) :
+ mpCxt(&rCxt), mbAllListeners(bAllListeners) {}
+
+ void operator() ( sc::CellStoreType::value_type& aBlk )
+ {
+ if (aBlk.type != sc::element_type_formula)
+ return;
+
+ ScFormulaCell** pp = &sc::formula_block::at(*aBlk.data, 0);
+ ScFormulaCell** ppEnd = pp + aBlk.size;
+
+ for (; pp != ppEnd; ++pp)
+ {
+ ScFormulaCell& rFC = **pp;
+ if (!mbAllListeners && !rFC.NeedsListening())
+ continue;
+
+ if (rFC.IsSharedTop())
+ {
+ sc::SharedFormulaUtil::startListeningAsGroup(*mpCxt, pp);
+ pp += rFC.GetSharedLength() - 1; // Move to the last cell in the group.
+ }
+ else
+ rFC.StartListeningTo(*mpCxt);
+ }
+ }
+};
+
+}
+
+void ScColumn::StartListeners( sc::StartListeningContext& rCxt, bool bAll )
+{
+ std::for_each(maCells.begin(), maCells.end(), StartListenersHandler(rCxt, bAll));
+}
+
+namespace {
+
+void applyTextNumFormat( ScColumn& rCol, SCROW nRow, SvNumberFormatter* pFormatter )
+{
+ sal_uInt32 nFormat = pFormatter->GetStandardFormat(SvNumFormatType::TEXT);
+ ScPatternAttr aNewAttrs(rCol.GetDoc().GetPool());
+ SfxItemSet& rSet = aNewAttrs.GetItemSet();
+ rSet.Put(SfxUInt32Item(ATTR_VALUE_FORMAT, nFormat));
+ rCol.ApplyPattern(nRow, aNewAttrs);
+}
+
+}
+
+bool ScColumn::ParseString(
+ ScCellValue& rCell, SCROW nRow, SCTAB nTabP, const OUString& rString,
+ formula::FormulaGrammar::AddressConvention eConv,
+ const ScSetStringParam* pParam )
+{
+ if (rString.isEmpty())
+ return false;
+
+ bool bNumFmtSet = false;
+
+ ScSetStringParam aParam;
+
+ if (pParam)
+ aParam = *pParam;
+
+ sal_uInt32 nIndex = 0;
+ sal_uInt32 nOldIndex = 0;
+ SvNumFormatType eNumFormatType = SvNumFormatType::ALL;
+ if (!aParam.mpNumFormatter)
+ aParam.mpNumFormatter = GetDoc().GetFormatTable();
+
+ sal_Unicode cFirstChar = 0; // Text
+ nIndex = nOldIndex = GetNumberFormat( GetDoc().GetNonThreadedContext(), nRow );
+ if ( rString.getLength() > 1 )
+ {
+ eNumFormatType = aParam.mpNumFormatter->GetType(nIndex);
+ if ( eNumFormatType != SvNumFormatType::TEXT )
+ cFirstChar = rString[0];
+ }
+
+ svl::SharedStringPool& rPool = GetDoc().GetSharedStringPool();
+
+ if ( cFirstChar == '=' )
+ {
+ if ( rString.getLength() == 1 ) // = Text
+ {
+ rCell.set(rPool.intern(rString));
+ }
+ else if (aParam.meSetTextNumFormat == ScSetStringParam::Always)
+ {
+ // Set the cell format type to Text.
+ applyTextNumFormat(*this, nRow, aParam.mpNumFormatter);
+ rCell.set(rPool.intern(rString));
+ }
+ else // = Formula
+ {
+ ScFormulaCell* pFormulaCell = new ScFormulaCell(
+ GetDoc(), ScAddress(nCol, nRow, nTabP), rString,
+ formula::FormulaGrammar::mergeToGrammar(formula::FormulaGrammar::GRAM_DEFAULT, eConv),
+ ScMatrixMode::NONE);
+ if (aParam.mbCheckLinkFormula)
+ GetDoc().CheckLinkFormulaNeedingCheck( *pFormulaCell->GetCode());
+ rCell.set( pFormulaCell);
+ }
+ }
+ else if ( cFirstChar == '\'') // 'Text
+ {
+ bool bNumeric = false;
+ if (aParam.mbHandleApostrophe)
+ {
+ // Cell format is not 'Text', and the first char is an apostrophe.
+ // Check if the input is considered a number with all leading
+ // apostrophes removed. All because ''1 should produce '1 not ''1,
+ // thus '''1 be ''1 and so on.
+ // NOTE: this corresponds with sc/source/ui/view/tabvwsha.cxx
+ // ScTabViewShell::UpdateInputHandler() prepending an apostrophe if
+ // necessary.
+ sal_Int32 i = 1;
+ while (i < rString.getLength() && rString[i] == '\'')
+ ++i;
+ if (i < rString.getLength())
+ {
+ OUString aTest = rString.copy(i);
+ double fTest;
+ bNumeric = aParam.mpNumFormatter->IsNumberFormat(aTest, nIndex, fTest);
+ if (bNumeric)
+ // This is a number. Strip out the first apostrophe.
+ rCell.set(rPool.intern(rString.copy(1)));
+ }
+ }
+ if (!bNumeric)
+ // This is normal text. Take it as-is.
+ rCell.set(rPool.intern(rString));
+ }
+ else
+ {
+ double nVal;
+
+ do
+ {
+ if (aParam.mbDetectNumberFormat)
+ {
+ // Editing a date prefers the format's locale's edit date
+ // format's date acceptance patterns and YMD order.
+ /* TODO: this could be determined already far above when
+ * starting to edit a date "cell" and passed down. A problem
+ * could also be if a new date was typed over or written by a
+ * macro assuming the current locale if that conflicts somehow.
+ * You can't have everything. See tdf#116579 and tdf#125109. */
+ if (eNumFormatType == SvNumFormatType::ALL)
+ eNumFormatType = aParam.mpNumFormatter->GetType(nIndex);
+ bool bForceFormatDate = (eNumFormatType == SvNumFormatType::DATE
+ || eNumFormatType == SvNumFormatType::DATETIME);
+ const SvNumberformat* pOldFormat = nullptr;
+ NfEvalDateFormat eEvalDateFormat = NF_EVALDATEFORMAT_INTL_FORMAT;
+ if (bForceFormatDate)
+ {
+ ScRefCellValue aCell = GetCellValue(nRow);
+ if (aCell.meType == CELLTYPE_VALUE)
+ {
+ // Only for an actual date (serial number), not an
+ // arbitrary string or formula or empty cell.
+ // Also ensure the edit date format's pattern is used,
+ // not the display format's.
+ pOldFormat = aParam.mpNumFormatter->GetEntry( nOldIndex);
+ if (!pOldFormat)
+ bForceFormatDate = false;
+ else
+ {
+ nIndex = aParam.mpNumFormatter->GetEditFormat(
+ aCell.getValue(), nOldIndex, eNumFormatType, pOldFormat);
+ eEvalDateFormat = aParam.mpNumFormatter->GetEvalDateFormat();
+ aParam.mpNumFormatter->SetEvalDateFormat( NF_EVALDATEFORMAT_FORMAT_INTL);
+ }
+ }
+ else
+ {
+ bForceFormatDate = false;
+ }
+ }
+
+ const bool bIsNumberFormat = aParam.mpNumFormatter->IsNumberFormat(rString, nIndex, nVal);
+
+ if (bForceFormatDate)
+ aParam.mpNumFormatter->SetEvalDateFormat( eEvalDateFormat);
+
+ if (!bIsNumberFormat)
+ break;
+
+ // If we have bForceFormatDate, the pOldFormat was/is of
+ // nOldIndex date(+time) type already, if detected type is
+ // compatible keep the original format.
+ if (bForceFormatDate && SvNumberFormatter::IsCompatible(
+ eNumFormatType, aParam.mpNumFormatter->GetType( nIndex)))
+ {
+ nIndex = nOldIndex;
+ }
+ else
+ {
+ // convert back to the original language if a built-in format was detected
+ if (!pOldFormat)
+ pOldFormat = aParam.mpNumFormatter->GetEntry( nOldIndex );
+ if (pOldFormat)
+ nIndex = aParam.mpNumFormatter->GetFormatForLanguageIfBuiltIn(
+ nIndex, pOldFormat->GetLanguage());
+ }
+
+ rCell.set(nVal);
+ if ( nIndex != nOldIndex)
+ {
+ // #i22345# New behavior: Apply the detected number format only if
+ // the old one was the default number, date, time or boolean format.
+ // Exception: If the new format is boolean, always apply it.
+
+ bool bOverwrite = false;
+ if ( pOldFormat )
+ {
+ SvNumFormatType nOldType = pOldFormat->GetMaskedType();
+ if ( nOldType == SvNumFormatType::NUMBER || nOldType == SvNumFormatType::DATE ||
+ nOldType == SvNumFormatType::TIME || nOldType == SvNumFormatType::LOGICAL )
+ {
+ if ( nOldIndex == aParam.mpNumFormatter->GetStandardFormat(
+ nOldType, pOldFormat->GetLanguage() ) )
+ {
+ bOverwrite = true; // default of these types can be overwritten
+ }
+ }
+ }
+ if ( !bOverwrite && aParam.mpNumFormatter->GetType( nIndex ) == SvNumFormatType::LOGICAL )
+ {
+ bOverwrite = true; // overwrite anything if boolean was detected
+ }
+
+ if ( bOverwrite )
+ {
+ ApplyAttr( nRow, SfxUInt32Item( ATTR_VALUE_FORMAT,
+ nIndex) );
+ bNumFmtSet = true;
+ }
+ }
+ }
+ else if (aParam.meSetTextNumFormat == ScSetStringParam::Never ||
+ aParam.meSetTextNumFormat == ScSetStringParam::SpecialNumberOnly)
+ {
+ // Only check if the string is a regular number.
+ const LocaleDataWrapper* pLocale = aParam.mpNumFormatter->GetLocaleData();
+ if (!pLocale)
+ break;
+
+ const LocaleDataItem2& aLocaleItem = pLocale->getLocaleItem();
+ const OUString& rDecSep = aLocaleItem.decimalSeparator;
+ const OUString& rGroupSep = aLocaleItem.thousandSeparator;
+ const OUString& rDecSepAlt = aLocaleItem.decimalSeparatorAlternative;
+ if (rDecSep.getLength() != 1 || rGroupSep.getLength() != 1 || rDecSepAlt.getLength() > 1)
+ break;
+
+ sal_Unicode dsep = rDecSep[0];
+ sal_Unicode gsep = rGroupSep[0];
+ sal_Unicode dsepa = rDecSepAlt.toChar();
+
+ if (!ScStringUtil::parseSimpleNumber(rString, dsep, gsep, dsepa, nVal))
+ break;
+
+ rCell.set(nVal);
+ }
+ }
+ while (false);
+
+ if (rCell.meType == CELLTYPE_NONE)
+ {
+ // If we reach here with ScSetStringParam::SpecialNumberOnly it
+ // means a simple number was not detected above, so test for
+ // special numbers. In any case ScSetStringParam::Always does not
+ // mean always, but only always for content that could be any
+ // numeric.
+ if ((aParam.meSetTextNumFormat == ScSetStringParam::Always ||
+ aParam.meSetTextNumFormat == ScSetStringParam::SpecialNumberOnly) &&
+ aParam.mpNumFormatter->IsNumberFormat(rString, nIndex, nVal))
+ {
+ // Set the cell format type to Text.
+ applyTextNumFormat(*this, nRow, aParam.mpNumFormatter);
+ }
+
+ rCell.set(rPool.intern(rString));
+ }
+ }
+
+ return bNumFmtSet;
+}
+
+/**
+ * Returns true if the cell format was set as well
+ */
+bool ScColumn::SetString( SCROW nRow, SCTAB nTabP, const OUString& rString,
+ formula::FormulaGrammar::AddressConvention eConv,
+ const ScSetStringParam* pParam )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return false;
+
+ ScCellValue aNewCell;
+ bool bNumFmtSet = ParseString(aNewCell, nRow, nTabP, rString, eConv, pParam);
+ if (pParam)
+ aNewCell.release(*this, nRow, pParam->meStartListening);
+ else
+ aNewCell.release(*this, nRow);
+
+ // Do not set Formats and Formulas here anymore!
+ // These are queried during output
+
+ return bNumFmtSet;
+}
+
+void ScColumn::SetEditText( SCROW nRow, std::unique_ptr<EditTextObject> pEditText )
+{
+ pEditText->NormalizeString(GetDoc().GetSharedStringPool());
+ std::vector<SCROW> aNewSharedRows;
+ sc::CellStoreType::iterator it = GetPositionToInsert(nRow, aNewSharedRows, false);
+ maCells.set(it, nRow, pEditText.release());
+ maCellTextAttrs.set(nRow, sc::CellTextAttr());
+ CellStorageModified();
+
+ StartListeningUnshared( aNewSharedRows);
+
+ BroadcastNewCell(nRow);
+}
+
+void ScColumn::SetEditText( sc::ColumnBlockPosition& rBlockPos, SCROW nRow, std::unique_ptr<EditTextObject> pEditText )
+{
+ pEditText->NormalizeString(GetDoc().GetSharedStringPool());
+ std::vector<SCROW> aNewSharedRows;
+ rBlockPos.miCellPos = GetPositionToInsert(rBlockPos.miCellPos, nRow, aNewSharedRows, false);
+ rBlockPos.miCellPos = maCells.set(rBlockPos.miCellPos, nRow, pEditText.release());
+ rBlockPos.miCellTextAttrPos = maCellTextAttrs.set(
+ rBlockPos.miCellTextAttrPos, nRow, sc::CellTextAttr());
+
+ CellStorageModified();
+
+ StartListeningUnshared( aNewSharedRows);
+
+ BroadcastNewCell(nRow);
+}
+
+void ScColumn::SetEditText( sc::ColumnBlockPosition& rBlockPos, SCROW nRow, const EditTextObject& rEditText )
+{
+ if (GetDoc().GetEditPool() == rEditText.GetPool())
+ {
+ SetEditText(rBlockPos, nRow, rEditText.Clone());
+ return;
+ }
+
+ // rats, yet another "spool"
+ // Sadly there is no other way to change the Pool than to
+ // "spool" the Object through a corresponding Engine
+ EditEngine& rEngine = GetDoc().GetEditEngine();
+ rEngine.SetText(rEditText);
+ SetEditText(rBlockPos, nRow, rEngine.CreateTextObject());
+}
+
+void ScColumn::SetEditText( SCROW nRow, const EditTextObject& rEditText, const SfxItemPool* pEditPool )
+{
+ if (pEditPool && GetDoc().GetEditPool() == pEditPool)
+ {
+ SetEditText(nRow, rEditText.Clone());
+ return;
+ }
+
+ // rats, yet another "spool"
+ // Sadly there is no other way to change the Pool than to
+ // "spool" the Object through a corresponding Engine
+ EditEngine& rEngine = GetDoc().GetEditEngine();
+ rEngine.SetText(rEditText);
+ SetEditText(nRow, rEngine.CreateTextObject());
+}
+
+void ScColumn::SetFormula( SCROW nRow, const ScTokenArray& rArray, formula::FormulaGrammar::Grammar eGram )
+{
+ ScAddress aPos(nCol, nRow, nTab);
+
+ std::vector<SCROW> aNewSharedRows;
+ sc::CellStoreType::iterator it = GetPositionToInsert(nRow, aNewSharedRows, true);
+ ScFormulaCell* pCell = new ScFormulaCell(GetDoc(), aPos, rArray, eGram);
+ sal_uInt32 nCellFormat = GetNumberFormat(GetDoc().GetNonThreadedContext(), nRow);
+ if( (nCellFormat % SV_COUNTRY_LANGUAGE_OFFSET) == 0)
+ pCell->SetNeedNumberFormat(true);
+ it = maCells.set(it, nRow, pCell);
+ maCellTextAttrs.set(nRow, sc::CellTextAttr());
+
+ CellStorageModified();
+
+ AttachNewFormulaCell(it, nRow, *pCell, aNewSharedRows);
+}
+
+void ScColumn::SetFormula( SCROW nRow, const OUString& rFormula, formula::FormulaGrammar::Grammar eGram )
+{
+ ScAddress aPos(nCol, nRow, nTab);
+
+ std::vector<SCROW> aNewSharedRows;
+ sc::CellStoreType::iterator it = GetPositionToInsert(nRow, aNewSharedRows, true);
+ ScFormulaCell* pCell = new ScFormulaCell(GetDoc(), aPos, rFormula, eGram);
+ sal_uInt32 nCellFormat = GetNumberFormat(GetDoc().GetNonThreadedContext(), nRow);
+ if( (nCellFormat % SV_COUNTRY_LANGUAGE_OFFSET) == 0)
+ pCell->SetNeedNumberFormat(true);
+ it = maCells.set(it, nRow, pCell);
+ maCellTextAttrs.set(nRow, sc::CellTextAttr());
+
+ CellStorageModified();
+
+ AttachNewFormulaCell(it, nRow, *pCell, aNewSharedRows);
+}
+
+ScFormulaCell* ScColumn::SetFormulaCell(
+ SCROW nRow, ScFormulaCell* pCell, sc::StartListeningType eListenType,
+ bool bInheritNumFormatIfNeeded )
+{
+ std::vector<SCROW> aNewSharedRows;
+ sc::CellStoreType::iterator it = GetPositionToInsert(nRow, aNewSharedRows, true);
+ sal_uInt32 nCellFormat = GetNumberFormat(GetDoc().GetNonThreadedContext(), nRow);
+ if( (nCellFormat % SV_COUNTRY_LANGUAGE_OFFSET) == 0 && bInheritNumFormatIfNeeded )
+ pCell->SetNeedNumberFormat(true);
+ it = maCells.set(it, nRow, pCell);
+ maCellTextAttrs.set(nRow, sc::CellTextAttr());
+
+ CellStorageModified();
+
+ AttachNewFormulaCell(it, nRow, *pCell, aNewSharedRows, true, eListenType);
+
+ return pCell;
+}
+
+void ScColumn::SetFormulaCell(
+ sc::ColumnBlockPosition& rBlockPos, SCROW nRow, ScFormulaCell* pCell,
+ sc::StartListeningType eListenType,
+ bool bInheritNumFormatIfNeeded )
+{
+ std::vector<SCROW> aNewSharedRows;
+ rBlockPos.miCellPos = GetPositionToInsert(rBlockPos.miCellPos, nRow, aNewSharedRows, true);
+ sal_uInt32 nCellFormat = GetNumberFormat(GetDoc().GetNonThreadedContext(), nRow);
+ if( (nCellFormat % SV_COUNTRY_LANGUAGE_OFFSET) == 0 && bInheritNumFormatIfNeeded )
+ pCell->SetNeedNumberFormat(true);
+ rBlockPos.miCellPos = maCells.set(rBlockPos.miCellPos, nRow, pCell);
+ rBlockPos.miCellTextAttrPos = maCellTextAttrs.set(
+ rBlockPos.miCellTextAttrPos, nRow, sc::CellTextAttr());
+
+ CellStorageModified();
+
+ AttachNewFormulaCell(rBlockPos.miCellPos, nRow, *pCell, aNewSharedRows, true, eListenType);
+}
+
+bool ScColumn::SetFormulaCells( SCROW nRow, std::vector<ScFormulaCell*>& rCells )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return false;
+
+ SCROW nEndRow = nRow + rCells.size() - 1;
+ if (!GetDoc().ValidRow(nEndRow))
+ return false;
+
+ sc::CellStoreType::position_type aPos = maCells.position(nRow);
+
+ // Detach all formula cells that will be overwritten.
+ std::vector<SCROW> aNewSharedRows;
+ DetachFormulaCells(aPos, rCells.size(), &aNewSharedRows);
+
+ if (!GetDoc().IsClipOrUndo())
+ {
+ for (size_t i = 0, n = rCells.size(); i < n; ++i)
+ {
+ SCROW nThisRow = nRow + i;
+ sal_uInt32 nFmt = GetNumberFormat(GetDoc().GetNonThreadedContext(), nThisRow);
+ if ((nFmt % SV_COUNTRY_LANGUAGE_OFFSET) == 0)
+ rCells[i]->SetNeedNumberFormat(true);
+ }
+ }
+
+ std::vector<sc::CellTextAttr> aDefaults(rCells.size(), sc::CellTextAttr());
+ maCellTextAttrs.set(nRow, aDefaults.begin(), aDefaults.end());
+
+ maCells.set(aPos.first, nRow, rCells.begin(), rCells.end());
+
+ CellStorageModified();
+
+ // Reget position_type as the type may have changed to formula, block and
+ // block size changed, ...
+ aPos = maCells.position(nRow);
+ AttachNewFormulaCells(aPos, rCells.size(), aNewSharedRows);
+
+ return true;
+}
+
+svl::SharedString ScColumn::GetSharedString( SCROW nRow ) const
+{
+ sc::CellStoreType::const_position_type aPos = maCells.position(nRow);
+ switch (aPos.first->type)
+ {
+ case sc::element_type_string:
+ return sc::string_block::at(*aPos.first->data, aPos.second);
+ case sc::element_type_edittext:
+ {
+ const EditTextObject* pObj = sc::edittext_block::at(*aPos.first->data, aPos.second);
+ std::vector<svl::SharedString> aSSs = pObj->GetSharedStrings();
+ if (aSSs.size() != 1)
+ // We don't handle multiline content for now.
+ return svl::SharedString();
+
+ return aSSs[0];
+ }
+ break;
+ default:
+ ;
+ }
+ return svl::SharedString();
+}
+
+namespace {
+
+class FilterEntriesHandler
+{
+ ScColumn& mrColumn;
+ ScFilterEntries& mrFilterEntries;
+
+ void processCell(const ScColumn& rColumn, SCROW nRow, ScRefCellValue& rCell)
+ {
+ SvNumberFormatter* pFormatter = mrColumn.GetDoc().GetFormatTable();
+ sal_uLong nFormat = mrColumn.GetNumberFormat(mrColumn.GetDoc().GetNonThreadedContext(), nRow);
+ OUString aStr = ScCellFormat::GetInputString(rCell, nFormat, *pFormatter, mrColumn.GetDoc(), mrColumn.HasFiltering());
+
+ // Colors
+ ScAddress aPos(rColumn.GetCol(), nRow, rColumn.GetTab());
+
+ Color backgroundColor;
+ bool bHasConditionalBackgroundColor = false;
+
+ Color textColor;
+ bool bHasConditionalTextColor = false;
+ // Check text & background color from cond. formatting
+ const ScPatternAttr* pPattern
+ = mrColumn.GetDoc().GetPattern(aPos.Col(), aPos.Row(), aPos.Tab());
+ if (pPattern)
+ {
+ if (!pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData().empty())
+ {
+ const SfxItemSet* pCondSet
+ = mrColumn.GetDoc().GetCondResult(aPos.Col(), aPos.Row(), aPos.Tab());
+ const SvxColorItem* pColor = &pPattern->GetItem(ATTR_FONT_COLOR, pCondSet);
+ textColor = pColor->GetValue();
+ bHasConditionalTextColor = true;
+
+ const SvxBrushItem* pBackgroundColor = &pPattern->GetItem(ATTR_BACKGROUND, pCondSet);
+ backgroundColor = pBackgroundColor->GetColor();
+ bHasConditionalBackgroundColor = true;
+ }
+ }
+
+ if (!bHasConditionalTextColor)
+ {
+ const SvxColorItem* pColor = rColumn.GetDoc().GetAttr(aPos, ATTR_FONT_COLOR);
+ textColor = pColor->GetValue();
+ }
+ mrFilterEntries.addTextColor(textColor);
+
+ // Color scale needs a different handling
+ ScConditionalFormat* pCondFormat
+ = rColumn.GetDoc().GetCondFormat(aPos.Col(), aPos.Row(), aPos.Tab());
+ if (pCondFormat)
+ {
+ for (size_t i = 0; i < pCondFormat->size(); i++)
+ {
+ auto aEntry = pCondFormat->GetEntry(i);
+ if (aEntry->GetType() == ScFormatEntry::Type::Colorscale)
+ {
+ const ScColorScaleFormat* pColFormat
+ = static_cast<const ScColorScaleFormat*>(aEntry);
+ std::optional<Color> oColor = pColFormat->GetColor(aPos);
+ if (oColor)
+ {
+ backgroundColor = *oColor;
+ bHasConditionalBackgroundColor = true;
+ }
+ }
+ }
+ }
+ if (!bHasConditionalBackgroundColor)
+ {
+ const SvxBrushItem* pBrush = rColumn.GetDoc().GetAttr(aPos, ATTR_BACKGROUND);
+ backgroundColor = pBrush->GetColor();
+ }
+ mrFilterEntries.addBackgroundColor(backgroundColor);
+
+ if (rCell.hasString())
+ {
+ mrFilterEntries.push_back(ScTypedStrData(std::move(aStr)));
+ return;
+ }
+
+ double fVal = 0.0;
+
+ switch (rCell.meType)
+ {
+ case CELLTYPE_VALUE:
+ fVal = rCell.mfValue;
+ break;
+
+ case CELLTYPE_FORMULA:
+ {
+ ScFormulaCell* pFC = rCell.mpFormula;
+ FormulaError nErr = pFC->GetErrCode();
+ if (nErr != FormulaError::NONE)
+ {
+ // Error cell is evaluated as string (for now).
+ OUString aErr = ScGlobal::GetErrorString(nErr);
+ if (!aErr.isEmpty())
+ {
+ mrFilterEntries.push_back(ScTypedStrData(std::move(aErr)));
+ return;
+ }
+ }
+ else
+ fVal = pFC->GetValue();
+ }
+ break;
+ default:
+ ;
+ }
+
+ SvNumFormatType nType = pFormatter->GetType(nFormat);
+ bool bDate = false;
+ if ((nType & SvNumFormatType::DATE) && !(nType & SvNumFormatType::TIME))
+ {
+ // special case for date values. Disregard the time
+ // element if the number format is of date type.
+ fVal = rtl::math::approxFloor(fVal);
+ mrFilterEntries.mbHasDates = true;
+ bDate = true;
+ // Convert string representation to ISO 8601 date to eliminate
+ // locale dependent behaviour later when filtering for dates.
+ sal_uInt32 nIndex = pFormatter->GetFormatIndex( NF_DATE_DIN_YYYYMMDD);
+ pFormatter->GetInputLineString( fVal, nIndex, aStr);
+ }
+ else if (nType == SvNumFormatType::DATETIME)
+ {
+ // special case for datetime values.
+ // Convert string representation to ISO 8601 (with blank instead of T) datetime
+ // to eliminate locale dependent behaviour later when filtering for datetimes.
+ sal_uInt32 nIndex = pFormatter->GetFormatIndex(NF_DATETIME_ISO_YYYYMMDD_HHMMSS);
+ pFormatter->GetInputLineString(fVal, nIndex, aStr);
+ }
+ // store the formatted/rounded value for filtering
+ if ((nFormat % SV_COUNTRY_LANGUAGE_OFFSET) != 0 && !bDate)
+ mrFilterEntries.push_back(ScTypedStrData(std::move(aStr), fVal, rColumn.GetDoc().RoundValueAsShown(fVal, nFormat), ScTypedStrData::Value, bDate));
+ else
+ mrFilterEntries.push_back(ScTypedStrData(std::move(aStr), fVal, fVal, ScTypedStrData::Value, bDate));
+ }
+
+public:
+ FilterEntriesHandler(ScColumn& rColumn, ScFilterEntries& rFilterEntries) :
+ mrColumn(rColumn), mrFilterEntries(rFilterEntries) {}
+
+ void operator() (size_t nRow, double fVal)
+ {
+ ScRefCellValue aCell(fVal);
+ processCell(mrColumn, nRow, aCell);
+ }
+
+ void operator() (size_t nRow, const svl::SharedString& rStr)
+ {
+ ScRefCellValue aCell(&rStr);
+ processCell(mrColumn, nRow, aCell);
+ }
+
+ void operator() (size_t nRow, const EditTextObject* p)
+ {
+ ScRefCellValue aCell(p);
+ processCell(mrColumn, nRow, aCell);
+ }
+
+ void operator() (size_t nRow, const ScFormulaCell* p)
+ {
+ ScRefCellValue aCell(const_cast<ScFormulaCell*>(p));
+ processCell(mrColumn, nRow, aCell);
+ }
+
+ void operator() (const int nElemType, size_t nRow, size_t /* nDataSize */)
+ {
+ if ( nElemType == sc::element_type_empty )
+ {
+ if (!mrFilterEntries.mbHasEmpties)
+ {
+ mrFilterEntries.push_back(ScTypedStrData(OUString()));
+ mrFilterEntries.mbHasEmpties = true;
+ }
+ return;
+ }
+ ScRefCellValue aCell = mrColumn.GetCellValue(nRow);
+ processCell(mrColumn, nRow, aCell);
+ }
+};
+
+}
+
+void ScColumn::GetFilterEntries(
+ sc::ColumnBlockConstPosition& rBlockPos, SCROW nStartRow, SCROW nEndRow,
+ ScFilterEntries& rFilterEntries, bool bFiltering )
+{
+ mbFiltering = bFiltering;
+ FilterEntriesHandler aFunc(*this, rFilterEntries);
+ rBlockPos.miCellPos =
+ sc::ParseAll(rBlockPos.miCellPos, maCells, nStartRow, nEndRow, aFunc, aFunc);
+}
+
+namespace {
+
+/**
+ * Iterate over only string and edit-text cells.
+ */
+class StrCellIterator
+{
+ typedef std::pair<sc::CellStoreType::const_iterator,size_t> PosType;
+ PosType maPos;
+ sc::CellStoreType::const_iterator miBeg;
+ sc::CellStoreType::const_iterator miEnd;
+ const ScDocument* mpDoc;
+public:
+ StrCellIterator(const sc::CellStoreType& rCells, SCROW nStart, const ScDocument* pDoc) :
+ miBeg(rCells.begin()), miEnd(rCells.end()), mpDoc(pDoc)
+ {
+ if (pDoc->ValidRow(nStart))
+ maPos = rCells.position(nStart);
+ else
+ // Make this iterator invalid.
+ maPos.first = miEnd;
+ }
+
+ bool valid() const { return (maPos.first != miEnd); }
+
+ bool has() const
+ {
+ return (maPos.first->type == sc::element_type_string || maPos.first->type == sc::element_type_edittext);
+ }
+
+ bool prev()
+ {
+ if (!has())
+ {
+ // Not in a string block. Move back until we hit a string block.
+ while (!has())
+ {
+ if (maPos.first == miBeg)
+ return false;
+
+ --maPos.first; // move to the preceding block.
+ maPos.second = maPos.first->size - 1; // last cell in the block.
+ }
+ return true;
+ }
+
+ // We are in a string block.
+ if (maPos.second > 0)
+ {
+ // Move back one cell in the same block.
+ --maPos.second;
+ }
+ else
+ {
+ // Move back to the preceding string block.
+ while (true)
+ {
+ if (maPos.first == miBeg)
+ return false;
+
+ // Move to the last cell of the previous block.
+ --maPos.first;
+ maPos.second = maPos.first->size - 1;
+ if (has())
+ break;
+ }
+ }
+ return true;
+ }
+
+ bool next()
+ {
+ if (!has())
+ {
+ // Not in a string block. Move forward until we hit a string block.
+ while (!has())
+ {
+ ++maPos.first;
+ if (maPos.first == miEnd)
+ return false;
+
+ maPos.second = 0; // First cell in this block.
+ }
+ return true;
+ }
+
+ // We are in a string block.
+ ++maPos.second;
+ if (maPos.second >= maPos.first->size)
+ {
+ // Move to the next string block.
+ while (true)
+ {
+ ++maPos.first;
+ if (maPos.first == miEnd)
+ return false;
+
+ maPos.second = 0;
+ if (has())
+ break;
+ }
+ }
+ return true;
+ }
+
+ OUString get() const
+ {
+ switch (maPos.first->type)
+ {
+ case sc::element_type_string:
+ return sc::string_block::at(*maPos.first->data, maPos.second).getString();
+ case sc::element_type_edittext:
+ {
+ const EditTextObject* p = sc::edittext_block::at(*maPos.first->data, maPos.second);
+ return ScEditUtil::GetString(*p, mpDoc);
+ }
+ default:
+ ;
+ }
+ return OUString();
+ }
+};
+
+}
+
+// GetDataEntries - Strings from continuous Section around nRow
+bool ScColumn::GetDataEntries(
+ SCROW nStartRow, std::set<ScTypedStrData>& rStrings) const
+{
+ // Start at the specified row position, and collect all string values
+ // going upward and downward directions in parallel. The start position
+ // cell must be skipped.
+
+ StrCellIterator aItrUp(maCells, nStartRow, &GetDoc());
+ StrCellIterator aItrDown(maCells, nStartRow+1, &GetDoc());
+
+ bool bMoveUp = aItrUp.valid();
+ if (!bMoveUp)
+ // Current cell is invalid.
+ return false;
+
+ // Skip the start position cell.
+ bMoveUp = aItrUp.prev(); // Find the previous string cell position.
+
+ bool bMoveDown = aItrDown.valid();
+ if (bMoveDown && !aItrDown.has())
+ bMoveDown = aItrDown.next(); // Find the next string cell position.
+
+ bool bFound = false;
+ while (bMoveUp)
+ {
+ // Get the current string and move up.
+ OUString aStr = aItrUp.get();
+ if (!aStr.isEmpty())
+ {
+ if (rStrings.insert(ScTypedStrData(std::move(aStr))).second)
+ bFound = true;
+ }
+
+ bMoveUp = aItrUp.prev();
+ }
+
+ while (bMoveDown)
+ {
+ // Get the current string and move down.
+ OUString aStr = aItrDown.get();
+ if (!aStr.isEmpty())
+ {
+ if (rStrings.insert(ScTypedStrData(std::move(aStr))).second)
+ bFound = true;
+ }
+
+ bMoveDown = aItrDown.next();
+ }
+
+ return bFound;
+}
+
+namespace {
+
+class FormulaToValueHandler
+{
+ struct Entry
+ {
+ SCROW mnRow;
+ ScCellValue maValue;
+
+ Entry(SCROW nRow, double f) : mnRow(nRow), maValue(f) {}
+ Entry(SCROW nRow, const svl::SharedString& rStr) : mnRow(nRow), maValue(rStr) {}
+ };
+
+ typedef std::vector<Entry> EntriesType;
+ EntriesType maEntries;
+
+public:
+
+ void operator() (size_t nRow, const ScFormulaCell* p)
+ {
+ ScFormulaCell* p2 = const_cast<ScFormulaCell*>(p);
+ if (p2->IsValue())
+ maEntries.emplace_back(nRow, p2->GetValue());
+ else
+ maEntries.emplace_back(nRow, p2->GetString());
+ }
+
+ void commitCells(ScColumn& rColumn)
+ {
+ sc::ColumnBlockPosition aBlockPos;
+ rColumn.InitBlockPosition(aBlockPos);
+
+ for (const Entry& r : maEntries)
+ {
+ switch (r.maValue.meType)
+ {
+ case CELLTYPE_VALUE:
+ rColumn.SetValue(aBlockPos, r.mnRow, r.maValue.mfValue, false);
+ break;
+ case CELLTYPE_STRING:
+ rColumn.SetRawString(aBlockPos, r.mnRow, *r.maValue.mpString, false);
+ break;
+ default:
+ ;
+ }
+ }
+ }
+};
+
+}
+
+void ScColumn::RemoveProtected( SCROW nStartRow, SCROW nEndRow )
+{
+ FormulaToValueHandler aFunc;
+ sc::CellStoreType::const_iterator itPos = maCells.begin();
+
+ ScAttrIterator aAttrIter( pAttrArray.get(), nStartRow, nEndRow, GetDoc().GetDefPattern() );
+ SCROW nTop = -1;
+ SCROW nBottom = -1;
+ const ScPatternAttr* pPattern = aAttrIter.Next( nTop, nBottom );
+ while (pPattern)
+ {
+ const ScProtectionAttr* pAttr = &pPattern->GetItem(ATTR_PROTECTION);
+ if ( pAttr->GetHideCell() )
+ DeleteArea( nTop, nBottom, InsertDeleteFlags::CONTENTS );
+ else if ( pAttr->GetHideFormula() )
+ {
+ // Replace all formula cells between nTop and nBottom with raw value cells.
+ itPos = sc::ParseFormula(itPos, maCells, nTop, nBottom, aFunc);
+ }
+
+ pPattern = aAttrIter.Next( nTop, nBottom );
+ }
+
+ aFunc.commitCells(*this);
+}
+
+void ScColumn::SetError( SCROW nRow, const FormulaError nError)
+{
+ if (!GetDoc().ValidRow(nRow))
+ return;
+
+ ScFormulaCell* pCell = new ScFormulaCell(GetDoc(), ScAddress(nCol, nRow, nTab));
+ pCell->SetErrCode(nError);
+
+ std::vector<SCROW> aNewSharedRows;
+ sc::CellStoreType::iterator it = GetPositionToInsert(nRow, aNewSharedRows, true);
+ it = maCells.set(it, nRow, pCell);
+ maCellTextAttrs.set(nRow, sc::CellTextAttr());
+
+ CellStorageModified();
+
+ AttachNewFormulaCell(it, nRow, *pCell, aNewSharedRows);
+}
+
+void ScColumn::SetRawString( SCROW nRow, const OUString& rStr )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return;
+
+ svl::SharedString aSS = GetDoc().GetSharedStringPool().intern(rStr);
+ if (!aSS.getData())
+ return;
+
+ SetRawString(nRow, aSS);
+}
+
+void ScColumn::SetRawString( SCROW nRow, const svl::SharedString& rStr )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return;
+
+ std::vector<SCROW> aNewSharedRows;
+ sc::CellStoreType::iterator it = GetPositionToInsert(nRow, aNewSharedRows, false);
+ maCells.set(it, nRow, rStr);
+ maCellTextAttrs.set(nRow, sc::CellTextAttr());
+
+ CellStorageModified();
+
+ StartListeningUnshared( aNewSharedRows);
+
+ BroadcastNewCell(nRow);
+}
+
+void ScColumn::SetRawString(
+ sc::ColumnBlockPosition& rBlockPos, SCROW nRow, const svl::SharedString& rStr, bool bBroadcast )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return;
+
+ std::vector<SCROW> aNewSharedRows;
+ rBlockPos.miCellPos = GetPositionToInsert(rBlockPos.miCellPos, nRow, aNewSharedRows, false);
+ rBlockPos.miCellPos = maCells.set(rBlockPos.miCellPos, nRow, rStr);
+ rBlockPos.miCellTextAttrPos = maCellTextAttrs.set(
+ rBlockPos.miCellTextAttrPos, nRow, sc::CellTextAttr());
+
+ CellStorageModified();
+
+ StartListeningUnshared( aNewSharedRows);
+
+ if (bBroadcast)
+ BroadcastNewCell(nRow);
+}
+
+void ScColumn::SetValue( SCROW nRow, double fVal )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return;
+
+ std::vector<SCROW> aNewSharedRows;
+ sc::CellStoreType::iterator it = GetPositionToInsert(nRow, aNewSharedRows, false);
+ maCells.set(it, nRow, fVal);
+ maCellTextAttrs.set(nRow, sc::CellTextAttr());
+
+ CellStorageModified();
+
+ StartListeningUnshared( aNewSharedRows);
+
+ BroadcastNewCell(nRow);
+}
+
+void ScColumn::SetValue(
+ sc::ColumnBlockPosition& rBlockPos, SCROW nRow, double fVal, bool bBroadcast )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return;
+
+ std::vector<SCROW> aNewSharedRows;
+ rBlockPos.miCellPos = GetPositionToInsert(rBlockPos.miCellPos, nRow, aNewSharedRows, false);
+ rBlockPos.miCellPos = maCells.set(rBlockPos.miCellPos, nRow, fVal);
+ rBlockPos.miCellTextAttrPos = maCellTextAttrs.set(
+ rBlockPos.miCellTextAttrPos, nRow, sc::CellTextAttr());
+
+ CellStorageModified();
+
+ StartListeningUnshared( aNewSharedRows);
+
+ if (bBroadcast)
+ BroadcastNewCell(nRow);
+}
+
+OUString ScColumn::GetString( const ScRefCellValue& aCell, SCROW nRow, const ScInterpreterContext* pContext ) const
+{
+ // ugly hack for ordering problem with GetNumberFormat and missing inherited formats
+ if (aCell.meType == CELLTYPE_FORMULA)
+ aCell.mpFormula->MaybeInterpret();
+
+ sal_uInt32 nFormat = GetNumberFormat( pContext ? *pContext : GetDoc().GetNonThreadedContext(), nRow);
+ const Color* pColor = nullptr;
+ return ScCellFormat::GetString(aCell, nFormat, &pColor,
+ pContext ? *(pContext->GetFormatTable()) : *(GetDoc().GetFormatTable()), GetDoc());
+}
+
+double* ScColumn::GetValueCell( SCROW nRow )
+{
+ std::pair<sc::CellStoreType::iterator,size_t> aPos = maCells.position(nRow);
+ sc::CellStoreType::iterator it = aPos.first;
+ if (it == maCells.end())
+ return nullptr;
+
+ if (it->type != sc::element_type_numeric)
+ return nullptr;
+
+ return &sc::numeric_block::at(*it->data, aPos.second);
+}
+
+OUString ScColumn::GetInputString( const ScRefCellValue& aCell, SCROW nRow, const svl::SharedString** pShared, bool bForceSystemLocale ) const
+{
+ sal_uLong nFormat = GetNumberFormat(GetDoc().GetNonThreadedContext(), nRow);
+ return ScCellFormat::GetInputString(aCell, nFormat, *(GetDoc().GetFormatTable()), GetDoc(), pShared, false, bForceSystemLocale);
+}
+
+double ScColumn::GetValue( SCROW nRow ) const
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ switch (it->type)
+ {
+ case sc::element_type_numeric:
+ return sc::numeric_block::at(*it->data, aPos.second);
+ case sc::element_type_formula:
+ {
+ const ScFormulaCell* p = sc::formula_block::at(*it->data, aPos.second);
+ ScFormulaCell* p2 = const_cast<ScFormulaCell*>(p);
+ return p2->IsValue() ? p2->GetValue() : 0.0;
+ }
+ default:
+ ;
+ }
+
+ return 0.0;
+}
+
+const EditTextObject* ScColumn::GetEditText( SCROW nRow ) const
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it == maCells.end())
+ return nullptr;
+
+ if (it->type != sc::element_type_edittext)
+ return nullptr;
+
+ return sc::edittext_block::at(*it->data, aPos.second);
+}
+
+void ScColumn::RemoveEditTextCharAttribs( SCROW nRow, const ScPatternAttr& rAttr )
+{
+ std::pair<sc::CellStoreType::iterator,size_t> aPos = maCells.position(nRow);
+ sc::CellStoreType::iterator it = aPos.first;
+ if (it == maCells.end())
+ return;
+
+ if (it->type != sc::element_type_edittext)
+ return;
+
+ EditTextObject* p = sc::edittext_block::at(*it->data, aPos.second);
+ ScEditUtil::RemoveCharAttribs(*p, rAttr);
+}
+
+OUString ScColumn::GetFormula( SCROW nRow ) const
+{
+ const ScFormulaCell* p = FetchFormulaCell(nRow);
+ if (p)
+ return p->GetFormula();
+ return OUString();
+}
+
+const ScFormulaCell* ScColumn::GetFormulaCell( SCROW nRow ) const
+{
+ return FetchFormulaCell(nRow);
+}
+
+ScFormulaCell* ScColumn::GetFormulaCell( SCROW nRow )
+{
+ return const_cast<ScFormulaCell*>(FetchFormulaCell(nRow));
+}
+
+CellType ScColumn::GetCellType( SCROW nRow ) const
+{
+ switch (maCells.get_type(nRow))
+ {
+ case sc::element_type_numeric:
+ return CELLTYPE_VALUE;
+ case sc::element_type_string:
+ return CELLTYPE_STRING;
+ case sc::element_type_edittext:
+ return CELLTYPE_EDIT;
+ case sc::element_type_formula:
+ return CELLTYPE_FORMULA;
+ default:
+ ;
+ }
+ return CELLTYPE_NONE;
+}
+
+namespace {
+
+/**
+ * Count the number of all non-empty cells.
+ */
+class CellCounter
+{
+ size_t mnCount;
+public:
+ CellCounter() : mnCount(0) {}
+
+ void operator() (const sc::CellStoreType::value_type& node)
+ {
+ if (node.type == sc::element_type_empty)
+ return;
+
+ mnCount += node.size;
+ }
+
+ size_t getCount() const { return mnCount; }
+};
+
+}
+
+SCSIZE ScColumn::GetCellCount() const
+{
+ CellCounter aFunc;
+ aFunc = std::for_each(maCells.begin(), maCells.end(), aFunc);
+ return aFunc.getCount();
+}
+
+FormulaError ScColumn::GetErrCode( SCROW nRow ) const
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ if (it == maCells.end())
+ return FormulaError::NONE;
+
+ if (it->type != sc::element_type_formula)
+ return FormulaError::NONE;
+
+ const ScFormulaCell* p = sc::formula_block::at(*it->data, aPos.second);
+ return const_cast<ScFormulaCell*>(p)->GetErrCode();
+}
+
+bool ScColumn::HasStringData( SCROW nRow ) const
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
+ switch (aPos.first->type)
+ {
+ case sc::element_type_string:
+ case sc::element_type_edittext:
+ return true;
+ case sc::element_type_formula:
+ {
+ const ScFormulaCell* p = sc::formula_block::at(*aPos.first->data, aPos.second);
+ return !const_cast<ScFormulaCell*>(p)->IsValue();
+ }
+ default:
+ ;
+ }
+
+ return false;
+}
+
+bool ScColumn::HasValueData( SCROW nRow ) const
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
+ switch (aPos.first->type)
+ {
+ case sc::element_type_numeric:
+ return true;
+ case sc::element_type_formula:
+ {
+ const ScFormulaCell* p = sc::formula_block::at(*aPos.first->data, aPos.second);
+ return const_cast<ScFormulaCell*>(p)->IsValue();
+ }
+ default:
+ ;
+ }
+
+ return false;
+}
+
+/**
+ * Return true if there is a string or editcell in the range
+ */
+bool ScColumn::HasStringCells( SCROW nStartRow, SCROW nEndRow ) const
+{
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nStartRow);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ size_t nOffset = aPos.second;
+ SCROW nRow = nStartRow;
+ for (; it != maCells.end() && nRow <= nEndRow; ++it)
+ {
+ if (it->type == sc::element_type_string || it->type == sc::element_type_edittext)
+ return true;
+
+ nRow += it->size - nOffset;
+ nOffset = 0;
+ }
+
+ return false;
+}
+
+namespace {
+
+class MaxStringLenHandler
+{
+ sal_Int32 mnMaxLen;
+ const ScColumn& mrColumn;
+ SvNumberFormatter* mpFormatter;
+ rtl_TextEncoding meCharSet;
+ bool mbOctetEncoding;
+
+ void processCell(size_t nRow, const ScRefCellValue& rCell)
+ {
+ const Color* pColor;
+ sal_uInt32 nFormat = mrColumn.GetAttr(nRow, ATTR_VALUE_FORMAT).GetValue();
+ OUString aString = ScCellFormat::GetString(rCell, nFormat, &pColor, *mpFormatter, mrColumn.GetDoc());
+ sal_Int32 nLen = 0;
+ if (mbOctetEncoding)
+ {
+ OString aOString;
+ if (!aString.convertToString(&aOString, meCharSet,
+ RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR |
+ RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR))
+ {
+ // TODO: anything? this is used by the dBase export filter
+ // that throws an error anyway, but in case of another
+ // context we might want to indicate a conversion error
+ // early.
+ }
+ nLen = aOString.getLength();
+ }
+ else
+ nLen = aString.getLength() * sizeof(sal_Unicode);
+
+ if (mnMaxLen < nLen)
+ mnMaxLen = nLen;
+ }
+
+public:
+ MaxStringLenHandler(const ScColumn& rColumn, rtl_TextEncoding eCharSet) :
+ mnMaxLen(0),
+ mrColumn(rColumn),
+ mpFormatter(rColumn.GetDoc().GetFormatTable()),
+ meCharSet(eCharSet),
+ mbOctetEncoding(rtl_isOctetTextEncoding(eCharSet))
+ {
+ }
+
+ void operator() (size_t nRow, double fVal)
+ {
+ ScRefCellValue aCell(fVal);
+ processCell(nRow, aCell);
+ }
+
+ void operator() (size_t nRow, const svl::SharedString& rStr)
+ {
+ ScRefCellValue aCell(&rStr);
+ processCell(nRow, aCell);
+ }
+
+ void operator() (size_t nRow, const EditTextObject* p)
+ {
+ ScRefCellValue aCell(p);
+ processCell(nRow, aCell);
+ }
+
+ void operator() (size_t nRow, const ScFormulaCell* p)
+ {
+ ScRefCellValue aCell(const_cast<ScFormulaCell*>(p));
+ processCell(nRow, aCell);
+ }
+
+ sal_Int32 getMaxLen() const { return mnMaxLen; }
+};
+
+}
+
+sal_Int32 ScColumn::GetMaxStringLen( SCROW nRowStart, SCROW nRowEnd, rtl_TextEncoding eCharSet ) const
+{
+ MaxStringLenHandler aFunc(*this, eCharSet);
+ sc::ParseAllNonEmpty(maCells.begin(), maCells, nRowStart, nRowEnd, aFunc);
+ return aFunc.getMaxLen();
+}
+
+namespace {
+
+class MaxNumStringLenHandler
+{
+ const ScColumn& mrColumn;
+ SvNumberFormatter* mpFormatter;
+ sal_Int32 mnMaxLen;
+ sal_uInt16 mnPrecision;
+ sal_uInt16 mnMaxGeneralPrecision;
+ bool mbHaveSigned;
+
+ void processCell(size_t nRow, ScRefCellValue& rCell)
+ {
+ sal_uInt16 nCellPrecision = mnMaxGeneralPrecision;
+ if (rCell.meType == CELLTYPE_FORMULA)
+ {
+ if (!rCell.mpFormula->IsValue())
+ return;
+
+ // Limit unformatted formula cell precision to precision
+ // encountered so far, if any, otherwise we'd end up with 15 just
+ // because of =1/3 ... If no precision yet then arbitrarily limit
+ // to a maximum of 4 unless a maximum general precision is set.
+ if (mnPrecision)
+ nCellPrecision = mnPrecision;
+ else
+ nCellPrecision = (mnMaxGeneralPrecision >= 15) ? 4 : mnMaxGeneralPrecision;
+ }
+
+ double fVal = rCell.getValue();
+ if (!mbHaveSigned && fVal < 0.0)
+ mbHaveSigned = true;
+
+ OUString aString;
+ OUString aSep;
+ sal_uInt16 nPrec;
+ sal_uInt32 nFormat =
+ mrColumn.GetAttr(nRow, ATTR_VALUE_FORMAT).GetValue();
+ if (nFormat % SV_COUNTRY_LANGUAGE_OFFSET)
+ {
+ aSep = mpFormatter->GetFormatDecimalSep(nFormat);
+ aString = ScCellFormat::GetInputString(rCell, nFormat, *mpFormatter, mrColumn.GetDoc());
+ const SvNumberformat* pEntry = mpFormatter->GetEntry(nFormat);
+ if (pEntry)
+ {
+ bool bThousand, bNegRed;
+ sal_uInt16 nLeading;
+ pEntry->GetFormatSpecialInfo(bThousand, bNegRed, nPrec, nLeading);
+ }
+ else
+ nPrec = mpFormatter->GetFormatPrecision(nFormat);
+ }
+ else
+ {
+ if (mnPrecision >= mnMaxGeneralPrecision)
+ return; // early bail out for nothing changes here
+
+ if (!fVal)
+ {
+ // 0 doesn't change precision, but set a maximum length if none yet.
+ if (!mnMaxLen)
+ mnMaxLen = 1;
+ return;
+ }
+
+ // Simple number string with at most 15 decimals and trailing
+ // decimal zeros eliminated.
+ aSep = ".";
+ aString = rtl::math::doubleToUString( fVal, rtl_math_StringFormat_F, nCellPrecision, '.', true);
+ nPrec = SvNumberFormatter::UNLIMITED_PRECISION;
+ }
+
+ sal_Int32 nLen = aString.getLength();
+ if (nLen <= 0)
+ // Ignore empty string.
+ return;
+
+ if (nPrec == SvNumberFormatter::UNLIMITED_PRECISION && mnPrecision < mnMaxGeneralPrecision)
+ {
+ if (nFormat % SV_COUNTRY_LANGUAGE_OFFSET)
+ {
+ // For some reason we couldn't obtain a precision from the
+ // format, retry with simple number string.
+ aSep = ".";
+ aString = rtl::math::doubleToUString( fVal, rtl_math_StringFormat_F, nCellPrecision, '.', true);
+ nLen = aString.getLength();
+ }
+ sal_Int32 nSep = aString.indexOf( aSep);
+ if (nSep != -1)
+ nPrec = aString.getLength() - nSep - 1;
+
+ }
+
+ if (nPrec != SvNumberFormatter::UNLIMITED_PRECISION && nPrec > mnPrecision)
+ mnPrecision = nPrec;
+
+ if (mnPrecision)
+ { // less than mnPrecision in string => widen it
+ // more => shorten it
+ sal_Int32 nTmp = aString.indexOf(aSep);
+ if ( nTmp == -1 )
+ nLen += mnPrecision + aSep.getLength();
+ else
+ {
+ nTmp = aString.getLength() - (nTmp + aSep.getLength());
+ if (nTmp != mnPrecision)
+ nLen += mnPrecision - nTmp;
+ // nPrecision > nTmp : nLen + Diff
+ // nPrecision < nTmp : nLen - Diff
+ }
+ }
+
+ // Enlarge for sign if necessary. Bear in mind that
+ // GetMaxNumberStringLen() is for determining dBase decimal field width
+ // and precision where the overall field width must include the sign.
+ // Fitting -1 into "#.##" (width 4, 2 decimals) does not work.
+ if (mbHaveSigned && fVal >= 0.0)
+ ++nLen;
+
+ if (mnMaxLen < nLen)
+ mnMaxLen = nLen;
+ }
+
+public:
+ MaxNumStringLenHandler(const ScColumn& rColumn, sal_uInt16 nMaxGeneralPrecision) :
+ mrColumn(rColumn), mpFormatter(rColumn.GetDoc().GetFormatTable()),
+ mnMaxLen(0), mnPrecision(0), mnMaxGeneralPrecision(nMaxGeneralPrecision),
+ mbHaveSigned(false)
+ {
+ // Limit the decimals passed to doubleToUString().
+ // Also, the dBaseIII maximum precision is 15.
+ if (mnMaxGeneralPrecision > 15)
+ mnMaxGeneralPrecision = 15;
+ }
+
+ void operator() (size_t nRow, double fVal)
+ {
+ ScRefCellValue aCell(fVal);
+ processCell(nRow, aCell);
+ }
+
+ void operator() (size_t nRow, const ScFormulaCell* p)
+ {
+ ScRefCellValue aCell(const_cast<ScFormulaCell*>(p));
+ processCell(nRow, aCell);
+ }
+
+ sal_Int32 getMaxLen() const { return mnMaxLen; }
+
+ sal_uInt16 getPrecision() const { return mnPrecision; }
+};
+
+}
+
+sal_Int32 ScColumn::GetMaxNumberStringLen(
+ sal_uInt16& nPrecision, SCROW nRowStart, SCROW nRowEnd ) const
+{
+ sal_uInt16 nMaxGeneralPrecision = GetDoc().GetDocOptions().GetStdPrecision();
+ MaxNumStringLenHandler aFunc(*this, nMaxGeneralPrecision);
+ sc::ParseFormulaNumeric(maCells.begin(), maCells, nRowStart, nRowEnd, aFunc);
+ nPrecision = aFunc.getPrecision();
+ return aFunc.getMaxLen();
+}
+
+namespace {
+
+class GroupFormulaCells
+{
+ std::vector<ScAddress>* mpGroupPos;
+
+public:
+ explicit GroupFormulaCells(std::vector<ScAddress>* pGroupPos)
+ : mpGroupPos(pGroupPos) {}
+
+ void operator() (sc::CellStoreType::value_type& node)
+ {
+ if (node.type != sc::element_type_formula)
+ // We are only interested in formula cells.
+ return;
+
+ size_t nRow = node.position; // start row position.
+
+ sc::formula_block::iterator it = sc::formula_block::begin(*node.data);
+ sc::formula_block::iterator itEnd = sc::formula_block::end(*node.data);
+
+ // This block should never be empty.
+
+ ScFormulaCell* pPrev = *it;
+ ScFormulaCellGroupRef xPrevGrp = pPrev->GetCellGroup();
+ if (xPrevGrp)
+ {
+ // Move to the cell after the last cell of the current group.
+ std::advance(it, xPrevGrp->mnLength);
+ nRow += xPrevGrp->mnLength;
+ }
+ else
+ {
+ ++it;
+ ++nRow;
+ }
+
+ ScFormulaCell* pCur = nullptr;
+ ScFormulaCellGroupRef xCurGrp;
+ for (; it != itEnd; pPrev = pCur, xPrevGrp = xCurGrp)
+ {
+ pCur = *it;
+ xCurGrp = pCur->GetCellGroup();
+
+ ScFormulaCell::CompareState eCompState = pPrev->CompareByTokenArray(*pCur);
+ if (eCompState == ScFormulaCell::NotEqual)
+ {
+ // different formula tokens.
+ if (xCurGrp)
+ {
+ // Move to the cell after the last cell of the current group.
+ if (xCurGrp->mnLength > std::distance(it, itEnd))
+ throw css::lang::IllegalArgumentException();
+ std::advance(it, xCurGrp->mnLength);
+ nRow += xCurGrp->mnLength;
+ }
+ else
+ {
+ ++it;
+ ++nRow;
+ }
+
+ continue;
+ }
+
+ // Formula tokens equal those of the previous formula cell or cell group.
+ if (xPrevGrp)
+ {
+ // Previous cell is a group.
+ if (xCurGrp)
+ {
+ // The current cell is a group. Merge these two groups.
+ xPrevGrp->mnLength += xCurGrp->mnLength;
+ pCur->SetCellGroup(xPrevGrp);
+ sc::formula_block::iterator itGrpEnd = it;
+ if (xCurGrp->mnLength > std::distance(itGrpEnd, itEnd))
+ throw css::lang::IllegalArgumentException();
+ std::advance(itGrpEnd, xCurGrp->mnLength);
+ for (++it; it != itGrpEnd; ++it)
+ {
+ ScFormulaCell* pCell = *it;
+ pCell->SetCellGroup(xPrevGrp);
+ }
+ nRow += xCurGrp->mnLength;
+ }
+ else
+ {
+ // Add this cell to the previous group.
+ pCur->SetCellGroup(xPrevGrp);
+ ++xPrevGrp->mnLength;
+ ++nRow;
+ ++it;
+ }
+
+ }
+ else if (xCurGrp)
+ {
+ // Previous cell is a regular cell and current cell is a group.
+ nRow += xCurGrp->mnLength;
+ if (xCurGrp->mnLength > std::distance(it, itEnd))
+ throw css::lang::IllegalArgumentException();
+ std::advance(it, xCurGrp->mnLength);
+ pPrev->SetCellGroup(xCurGrp);
+ xCurGrp->mpTopCell = pPrev;
+ ++xCurGrp->mnLength;
+ xPrevGrp = xCurGrp;
+ }
+ else
+ {
+ // Both previous and current cells are regular cells.
+ assert(pPrev->aPos.Row() == static_cast<SCROW>(nRow - 1));
+ xPrevGrp = pPrev->CreateCellGroup(2, eCompState == ScFormulaCell::EqualInvariant);
+ pCur->SetCellGroup(xPrevGrp);
+ ++nRow;
+ ++it;
+ }
+
+ if (mpGroupPos)
+ mpGroupPos->push_back(pCur->aPos);
+
+ pCur = pPrev;
+ xCurGrp = xPrevGrp;
+ }
+ }
+};
+
+}
+
+void ScColumn::RegroupFormulaCells( std::vector<ScAddress>* pGroupPos )
+{
+ // re-build formula groups.
+ std::for_each(maCells.begin(), maCells.end(), GroupFormulaCells(pGroupPos));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/column4.cxx b/sc/source/core/data/column4.cxx
new file mode 100644
index 000000000..731812485
--- /dev/null
+++ b/sc/source/core/data/column4.cxx
@@ -0,0 +1,2225 @@
+/* -*- 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 <column.hxx>
+#include <clipparam.hxx>
+#include <cellvalue.hxx>
+#include <attarray.hxx>
+#include <document.hxx>
+#include <cellvalues.hxx>
+#include <columnspanset.hxx>
+#include <columniterator.hxx>
+#include <mtvcellfunc.hxx>
+#include <clipcontext.hxx>
+#include <attrib.hxx>
+#include <patattr.hxx>
+#include <docpool.hxx>
+#include <conditio.hxx>
+#include <formulagroup.hxx>
+#include <tokenarray.hxx>
+#include <scitems.hxx>
+#include <cellform.hxx>
+#include <sharedformula.hxx>
+#include <drwlayer.hxx>
+#include <compiler.hxx>
+#include <recursionhelper.hxx>
+#include <docsh.hxx>
+
+#include <SparklineGroup.hxx>
+
+#include <o3tl/safeint.hxx>
+#include <svl/sharedstringpool.hxx>
+#include <sal/log.hxx>
+#include <tools/stream.hxx>
+
+#include <numeric>
+#include <vector>
+#include <cassert>
+
+sc::MultiDataCellState::StateType ScColumn::HasDataCellsInRange(
+ SCROW nRow1, SCROW nRow2, SCROW* pRow1 ) const
+{
+ sc::CellStoreType::const_position_type aPos = maCells.position(nRow1);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ size_t nOffset = aPos.second;
+ SCROW nRow = nRow1;
+ bool bHasOne = false; // whether or not we have found a non-empty block of size one.
+
+ for (; it != maCells.end() && nRow <= nRow2; ++it)
+ {
+ if (it->type != sc::element_type_empty)
+ {
+ // non-empty block found.
+ assert(it->size > 0); // mtv should never contain a block of zero length.
+ size_t nSize = it->size - nOffset;
+
+ SCROW nLastRow = nRow + nSize - 1;
+ if (nLastRow > nRow2)
+ // shrink the size to avoid exceeding the specified last row position.
+ nSize -= nLastRow - nRow2;
+
+ if (nSize == 1)
+ {
+ // this block is of size one.
+ if (bHasOne)
+ return sc::MultiDataCellState::HasMultipleCells;
+
+ bHasOne = true;
+ if (pRow1)
+ *pRow1 = nRow;
+ }
+ else
+ {
+ // size of this block is greater than one.
+ if (pRow1)
+ *pRow1 = nRow;
+ return sc::MultiDataCellState::HasMultipleCells;
+ }
+ }
+
+ nRow += it->size - nOffset;
+ nOffset = 0;
+ }
+
+ return bHasOne ? sc::MultiDataCellState::HasOneCell : sc::MultiDataCellState::Empty;
+}
+
+void ScColumn::DeleteBeforeCopyFromClip(
+ sc::CopyFromClipContext& rCxt, const ScColumn& rClipCol, sc::ColumnSpanSet& rBroadcastSpans )
+{
+ ScDocument& rDocument = GetDoc();
+ sc::CopyFromClipContext::Range aRange = rCxt.getDestRange();
+ if (!rDocument.ValidRow(aRange.mnRow1) || !rDocument.ValidRow(aRange.mnRow2))
+ return;
+
+ sc::ColumnBlockPosition* pBlockPos = rCxt.getBlockPosition(nTab, nCol);
+ if (!pBlockPos)
+ return;
+
+ InsertDeleteFlags nDelFlag = rCxt.getDeleteFlag();
+
+ if (!rCxt.isSkipEmptyCells())
+ {
+ // Delete the whole destination range.
+
+ if (nDelFlag & InsertDeleteFlags::CONTENTS)
+ {
+ auto xResult = DeleteCells(*pBlockPos, aRange.mnRow1, aRange.mnRow2, nDelFlag);
+ rBroadcastSpans.set(GetDoc(), nTab, nCol, xResult->aDeletedRows, true);
+
+ for (const auto& rRange : xResult->aFormulaRanges)
+ rCxt.setListeningFormulaSpans(
+ nTab, nCol, rRange.first, nCol, rRange.second);
+ }
+
+ if (nDelFlag & InsertDeleteFlags::NOTE)
+ DeleteCellNotes(*pBlockPos, aRange.mnRow1, aRange.mnRow2, false);
+
+ if (nDelFlag & InsertDeleteFlags::SPARKLINES)
+ DeleteSparklineCells(*pBlockPos, aRange.mnRow1, aRange.mnRow2);
+
+ if (nDelFlag & InsertDeleteFlags::EDITATTR)
+ RemoveEditAttribs(*pBlockPos, aRange.mnRow1, aRange.mnRow2);
+
+ if (nDelFlag & InsertDeleteFlags::ATTRIB)
+ {
+ pAttrArray->DeleteArea(aRange.mnRow1, aRange.mnRow2);
+
+ if (rCxt.isTableProtected())
+ {
+ ScPatternAttr aPattern(rDocument.GetPool());
+ aPattern.GetItemSet().Put(ScProtectionAttr(false));
+ ApplyPatternArea(aRange.mnRow1, aRange.mnRow2, aPattern);
+ }
+
+ ScConditionalFormatList* pCondList = rCxt.getCondFormatList();
+ if (pCondList)
+ pCondList->DeleteArea(nCol, aRange.mnRow1, nCol, aRange.mnRow2);
+ }
+ else if ((nDelFlag & InsertDeleteFlags::HARDATTR) == InsertDeleteFlags::HARDATTR)
+ pAttrArray->DeleteHardAttr(aRange.mnRow1, aRange.mnRow2);
+
+ return;
+ }
+
+ ScRange aClipRange = rCxt.getClipDoc()->GetClipParam().getWholeRange();
+ SCROW nClipRow1 = aClipRange.aStart.Row();
+ SCROW nClipRow2 = aClipRange.aEnd.Row();
+ SCROW nClipRowLen = nClipRow2 - nClipRow1 + 1;
+
+ // Check for non-empty cell ranges in the clip column.
+ sc::SingleColumnSpanSet aSpanSet(GetDoc().GetSheetLimits());
+ aSpanSet.scan(rClipCol, nClipRow1, nClipRow2);
+ sc::SingleColumnSpanSet::SpansType aSpans;
+ aSpanSet.getSpans(aSpans);
+
+ if (aSpans.empty())
+ // All cells in the range in the clip are empty. Nothing to delete.
+ return;
+
+ // Translate the clip column spans into the destination column, and repeat as needed.
+ std::vector<sc::RowSpan> aDestSpans;
+ SCROW nDestOffset = aRange.mnRow1 - nClipRow1;
+ bool bContinue = true;
+ while (bContinue)
+ {
+ for (const sc::RowSpan& r : aSpans)
+ {
+ SCROW nDestRow1 = r.mnRow1 + nDestOffset;
+ SCROW nDestRow2 = r.mnRow2 + nDestOffset;
+
+ if (nDestRow1 > aRange.mnRow2)
+ {
+ // We're done.
+ bContinue = false;
+ break;
+ }
+
+ if (nDestRow2 > aRange.mnRow2)
+ {
+ // Truncate this range, and set it as the last span.
+ nDestRow2 = aRange.mnRow2;
+ bContinue = false;
+ }
+
+ aDestSpans.emplace_back(nDestRow1, nDestRow2);
+
+ if (!bContinue)
+ break;
+ }
+
+ nDestOffset += nClipRowLen;
+ }
+
+ for (const auto& rDestSpan : aDestSpans)
+ {
+ SCROW nRow1 = rDestSpan.mnRow1;
+ SCROW nRow2 = rDestSpan.mnRow2;
+
+ if (nDelFlag & InsertDeleteFlags::CONTENTS)
+ {
+ auto xResult = DeleteCells(*pBlockPos, nRow1, nRow2, nDelFlag);
+ rBroadcastSpans.set(GetDoc(), nTab, nCol, xResult->aDeletedRows, true);
+
+ for (const auto& rRange : xResult->aFormulaRanges)
+ rCxt.setListeningFormulaSpans(
+ nTab, nCol, rRange.first, nCol, rRange.second);
+ }
+
+ if (nDelFlag & InsertDeleteFlags::NOTE)
+ DeleteCellNotes(*pBlockPos, nRow1, nRow2, false);
+
+ if (nDelFlag & InsertDeleteFlags::SPARKLINES)
+ DeleteSparklineCells(*pBlockPos, nRow1, nRow2);
+
+ if (nDelFlag & InsertDeleteFlags::EDITATTR)
+ RemoveEditAttribs(*pBlockPos, nRow1, nRow2);
+
+ // Delete attributes just now
+ if (nDelFlag & InsertDeleteFlags::ATTRIB)
+ {
+ pAttrArray->DeleteArea(nRow1, nRow2);
+
+ if (rCxt.isTableProtected())
+ {
+ ScPatternAttr aPattern(rDocument.GetPool());
+ aPattern.GetItemSet().Put(ScProtectionAttr(false));
+ ApplyPatternArea(nRow1, nRow2, aPattern);
+ }
+
+ ScConditionalFormatList* pCondList = rCxt.getCondFormatList();
+ if (pCondList)
+ pCondList->DeleteArea(nCol, nRow1, nCol, nRow2);
+ }
+ else if ((nDelFlag & InsertDeleteFlags::HARDATTR) == InsertDeleteFlags::HARDATTR)
+ pAttrArray->DeleteHardAttr(nRow1, nRow2);
+ }
+}
+
+void ScColumn::CopyOneCellFromClip( sc::CopyFromClipContext& rCxt, SCROW nRow1, SCROW nRow2, size_t nColOffset )
+{
+ assert(nRow1 <= nRow2);
+
+ size_t nDestSize = nRow2 - nRow1 + 1;
+ sc::ColumnBlockPosition* pBlockPos = rCxt.getBlockPosition(nTab, nCol);
+ if (!pBlockPos)
+ return;
+
+ ScDocument& rDocument = GetDoc();
+ bool bSameDocPool = (rCxt.getClipDoc()->GetPool() == rDocument.GetPool());
+
+ ScCellValue& rSrcCell = rCxt.getSingleCell(nColOffset);
+ sc::CellTextAttr& rSrcAttr = rCxt.getSingleCellAttr(nColOffset);
+
+ InsertDeleteFlags nFlags = rCxt.getInsertFlag();
+
+ if ((nFlags & InsertDeleteFlags::ATTRIB) != InsertDeleteFlags::NONE)
+ {
+ if (!rCxt.isSkipEmptyCells() || rSrcCell.meType != CELLTYPE_NONE)
+ {
+ const ScPatternAttr* pAttr = (bSameDocPool ? rCxt.getSingleCellPattern(nColOffset) :
+ rCxt.getSingleCellPattern(nColOffset)->PutInPool( &rDocument, rCxt.getClipDoc()));
+
+ auto pNewPattern = std::make_unique<ScPatternAttr>(*pAttr);
+ sal_uInt16 pItems[2];
+ pItems[0] = ATTR_CONDITIONAL;
+ pItems[1] = 0;
+ pNewPattern->ClearItems(pItems);
+ pAttrArray->SetPatternArea(nRow1, nRow2, std::move(pNewPattern), true);
+ }
+ }
+
+ if ((nFlags & InsertDeleteFlags::CONTENTS) != InsertDeleteFlags::NONE)
+ {
+ std::vector<sc::CellTextAttr> aTextAttrs(nDestSize, rSrcAttr);
+
+ switch (rSrcCell.meType)
+ {
+ case CELLTYPE_VALUE:
+ {
+ std::vector<double> aVals(nDestSize, rSrcCell.mfValue);
+ pBlockPos->miCellPos =
+ maCells.set(pBlockPos->miCellPos, nRow1, aVals.begin(), aVals.end());
+ pBlockPos->miCellTextAttrPos =
+ maCellTextAttrs.set(pBlockPos->miCellTextAttrPos, nRow1, aTextAttrs.begin(), aTextAttrs.end());
+ CellStorageModified();
+ }
+ break;
+ case CELLTYPE_STRING:
+ {
+ // Compare the ScDocumentPool* to determine if we are copying within the
+ // same document. If not, re-intern shared strings.
+ svl::SharedStringPool* pSharedStringPool = (bSameDocPool ? nullptr : &rDocument.GetSharedStringPool());
+ svl::SharedString aStr = (pSharedStringPool ?
+ pSharedStringPool->intern( rSrcCell.mpString->getString()) :
+ *rSrcCell.mpString);
+
+ std::vector<svl::SharedString> aStrs(nDestSize, aStr);
+ pBlockPos->miCellPos =
+ maCells.set(pBlockPos->miCellPos, nRow1, aStrs.begin(), aStrs.end());
+ pBlockPos->miCellTextAttrPos =
+ maCellTextAttrs.set(pBlockPos->miCellTextAttrPos, nRow1, aTextAttrs.begin(), aTextAttrs.end());
+ CellStorageModified();
+ }
+ break;
+ case CELLTYPE_EDIT:
+ {
+ std::vector<EditTextObject*> aStrs;
+ aStrs.reserve(nDestSize);
+ for (size_t i = 0; i < nDestSize; ++i)
+ aStrs.push_back(rSrcCell.mpEditText->Clone().release());
+
+ pBlockPos->miCellPos =
+ maCells.set(pBlockPos->miCellPos, nRow1, aStrs.begin(), aStrs.end());
+ pBlockPos->miCellTextAttrPos =
+ maCellTextAttrs.set(pBlockPos->miCellTextAttrPos, nRow1, aTextAttrs.begin(), aTextAttrs.end());
+ CellStorageModified();
+ }
+ break;
+ case CELLTYPE_FORMULA:
+ {
+ std::vector<sc::RowSpan> aRanges;
+ aRanges.reserve(1);
+ aRanges.emplace_back(nRow1, nRow2);
+ CloneFormulaCell(*pBlockPos, *rSrcCell.mpFormula, rSrcAttr, aRanges);
+ }
+ break;
+ default:
+ ;
+ }
+ }
+
+ ScAddress aDestPosition(nCol, nRow1, nTab);
+
+ duplicateSparkline(rCxt, pBlockPos, nColOffset, nDestSize, aDestPosition);
+
+ // Notes
+ const ScPostIt* pNote = rCxt.getSingleCellNote(nColOffset);
+ if (!(pNote && (nFlags & (InsertDeleteFlags::NOTE | InsertDeleteFlags::ADDNOTES)) != InsertDeleteFlags::NONE))
+ return;
+
+ // Duplicate the cell note over the whole pasted range.
+
+ ScDocument* pClipDoc = rCxt.getClipDoc();
+ const ScAddress aSrcPos = pClipDoc->GetClipParam().getWholeRange().aStart;
+ std::vector<ScPostIt*> aNotes;
+ aNotes.reserve(nDestSize);
+ for (size_t i = 0; i < nDestSize; ++i)
+ {
+ bool bCloneCaption = (nFlags & InsertDeleteFlags::NOCAPTIONS) == InsertDeleteFlags::NONE;
+ aNotes.push_back(pNote->Clone(aSrcPos, rDocument, aDestPosition, bCloneCaption).release());
+ aDestPosition.IncRow();
+ }
+
+ pBlockPos->miCellNotePos =
+ maCellNotes.set(
+ pBlockPos->miCellNotePos, nRow1, aNotes.begin(), aNotes.end());
+
+ // Notify our LOK clients.
+ aDestPosition.SetRow(nRow1);
+ for (size_t i = 0; i < nDestSize; ++i)
+ {
+ ScDocShell::LOKCommentNotify(LOKCommentNotificationType::Add, &rDocument, aDestPosition, aNotes[i]);
+ aDestPosition.IncRow();
+ }
+}
+
+void ScColumn::duplicateSparkline(sc::CopyFromClipContext& rContext, sc::ColumnBlockPosition* pBlockPos,
+ size_t nColOffset, size_t nDestSize, ScAddress aDestPosition)
+{
+ if ((rContext.getInsertFlag() & InsertDeleteFlags::SPARKLINES) == InsertDeleteFlags::NONE)
+ return;
+
+ auto pSparkline = rContext.getSingleSparkline(nColOffset);
+ if (pSparkline)
+ {
+ auto const& pSparklineGroup = pSparkline->getSparklineGroup();
+
+ auto pDuplicatedGroup = GetDoc().SearchSparklineGroup(pSparklineGroup->getID());
+ if (!pDuplicatedGroup)
+ pDuplicatedGroup = std::make_shared<sc::SparklineGroup>(*pSparklineGroup);
+
+ std::vector<sc::SparklineCell*> aSparklines(nDestSize, nullptr);
+ ScAddress aCurrentPosition = aDestPosition;
+ for (size_t i = 0; i < nDestSize; ++i)
+ {
+ auto pNewSparkline = std::make_shared<sc::Sparkline>(aCurrentPosition.Col(), aCurrentPosition.Row(), pDuplicatedGroup);
+ pNewSparkline->setInputRange(pSparkline->getInputRange());
+ aSparklines[i] = new sc::SparklineCell(pNewSparkline);
+ aCurrentPosition.IncRow();
+ }
+
+ pBlockPos->miSparklinePos = maSparklines.set(pBlockPos->miSparklinePos, aDestPosition.Row(), aSparklines.begin(), aSparklines.end());
+ }
+}
+
+void ScColumn::SetValues( const SCROW nRow, const std::vector<double>& rVals )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return;
+
+ SCROW nLastRow = nRow + rVals.size() - 1;
+ if (nLastRow > GetDoc().MaxRow())
+ // Out of bound. Do nothing.
+ return;
+
+ sc::CellStoreType::position_type aPos = maCells.position(nRow);
+ std::vector<SCROW> aNewSharedRows;
+ DetachFormulaCells(aPos, rVals.size(), &aNewSharedRows);
+
+ maCells.set(nRow, rVals.begin(), rVals.end());
+ std::vector<sc::CellTextAttr> aDefaults(rVals.size());
+ maCellTextAttrs.set(nRow, aDefaults.begin(), aDefaults.end());
+
+ CellStorageModified();
+
+ StartListeningUnshared( aNewSharedRows);
+
+ std::vector<SCROW> aRows;
+ aRows.reserve(rVals.size());
+ for (SCROW i = nRow; i <= nLastRow; ++i)
+ aRows.push_back(i);
+
+ BroadcastCells(aRows, SfxHintId::ScDataChanged);
+}
+
+void ScColumn::TransferCellValuesTo( SCROW nRow, size_t nLen, sc::CellValues& rDest )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return;
+
+ SCROW nLastRow = nRow + nLen - 1;
+ if (nLastRow > GetDoc().MaxRow())
+ // Out of bound. Do nothing.
+ return;
+
+ sc::CellStoreType::position_type aPos = maCells.position(nRow);
+ DetachFormulaCells(aPos, nLen, nullptr);
+
+ rDest.transferFrom(*this, nRow, nLen);
+
+ CellStorageModified();
+
+ std::vector<SCROW> aRows;
+ aRows.reserve(nLen);
+ for (SCROW i = nRow; i <= nLastRow; ++i)
+ aRows.push_back(i);
+
+ BroadcastCells(aRows, SfxHintId::ScDataChanged);
+}
+
+void ScColumn::CopyCellValuesFrom( SCROW nRow, const sc::CellValues& rSrc )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return;
+
+ SCROW nLastRow = nRow + rSrc.size() - 1;
+ if (nLastRow > GetDoc().MaxRow())
+ // Out of bound. Do nothing
+ return;
+
+ sc::CellStoreType::position_type aPos = maCells.position(nRow);
+ DetachFormulaCells(aPos, rSrc.size(), nullptr);
+
+ rSrc.copyTo(*this, nRow);
+
+ CellStorageModified();
+
+ std::vector<SCROW> aRows;
+ aRows.reserve(rSrc.size());
+ for (SCROW i = nRow; i <= nLastRow; ++i)
+ aRows.push_back(i);
+
+ BroadcastCells(aRows, SfxHintId::ScDataChanged);
+}
+
+namespace {
+
+class ConvertFormulaToValueHandler
+{
+ sc::CellValues maResValues;
+ bool mbModified;
+
+public:
+ ConvertFormulaToValueHandler(ScSheetLimits const & rSheetLimits) :
+ mbModified(false)
+ {
+ maResValues.reset(rSheetLimits.GetMaxRowCount());
+ }
+
+ void operator() ( size_t nRow, const ScFormulaCell* pCell )
+ {
+ sc::FormulaResultValue aRes = pCell->GetResult();
+ switch (aRes.meType)
+ {
+ case sc::FormulaResultValue::Value:
+ maResValues.setValue(nRow, aRes.mfValue);
+ break;
+ case sc::FormulaResultValue::String:
+ maResValues.setValue(nRow, aRes.maString);
+ break;
+ case sc::FormulaResultValue::Error:
+ case sc::FormulaResultValue::Invalid:
+ default:
+ maResValues.setValue(nRow, svl::SharedString::getEmptyString());
+ }
+
+ mbModified = true;
+ }
+
+ bool isModified() const { return mbModified; }
+
+ sc::CellValues& getResValues() { return maResValues; }
+};
+
+}
+
+void ScColumn::ConvertFormulaToValue(
+ sc::EndListeningContext& rCxt, SCROW nRow1, SCROW nRow2, sc::TableValues* pUndo )
+{
+ if (!GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2) || nRow1 > nRow2)
+ return;
+
+ std::vector<SCROW> aBounds { nRow1 };
+ if (nRow2 < GetDoc().MaxRow()-1)
+ aBounds.push_back(nRow2+1);
+
+ // Split formula cell groups at top and bottom boundaries (if applicable).
+ sc::SharedFormulaUtil::splitFormulaCellGroups(GetDoc(), maCells, aBounds);
+
+ // Parse all formulas within the range and store their results into temporary storage.
+ ConvertFormulaToValueHandler aFunc(GetDoc().GetSheetLimits());
+ sc::ParseFormula(maCells.begin(), maCells, nRow1, nRow2, aFunc);
+ if (!aFunc.isModified())
+ // No formula cells encountered.
+ return;
+
+ DetachFormulaCells(rCxt, nRow1, nRow2);
+
+ // Undo storage to hold static values which will get swapped to the cell storage later.
+ sc::CellValues aUndoCells;
+ aFunc.getResValues().swap(aUndoCells);
+ aUndoCells.swapNonEmpty(*this);
+ if (pUndo)
+ pUndo->swap(nTab, nCol, aUndoCells);
+}
+
+namespace {
+
+class StartListeningHandler
+{
+ sc::StartListeningContext& mrCxt;
+
+public:
+ explicit StartListeningHandler( sc::StartListeningContext& rCxt ) :
+ mrCxt(rCxt) {}
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+ pCell->StartListeningTo(mrCxt);
+ }
+};
+
+class EndListeningHandler
+{
+ sc::EndListeningContext& mrCxt;
+
+public:
+ explicit EndListeningHandler( sc::EndListeningContext& rCxt ) :
+ mrCxt(rCxt) {}
+
+ void operator() (size_t /*nRow*/, ScFormulaCell* pCell)
+ {
+ pCell->EndListeningTo(mrCxt);
+ }
+};
+
+}
+
+void ScColumn::SwapNonEmpty(
+ sc::TableValues& rValues, sc::StartListeningContext& rStartCxt, sc::EndListeningContext& rEndCxt )
+{
+ const ScRange& rRange = rValues.getRange();
+ std::vector<SCROW> aBounds { rRange.aStart.Row() };
+ if (rRange.aEnd.Row() < GetDoc().MaxRow()-1)
+ aBounds.push_back(rRange.aEnd.Row()+1);
+
+ // Split formula cell groups at top and bottom boundaries (if applicable).
+ sc::SharedFormulaUtil::splitFormulaCellGroups(GetDoc(), maCells, aBounds);
+ std::vector<sc::CellValueSpan> aSpans = rValues.getNonEmptySpans(nTab, nCol);
+
+ // Detach formula cells within the spans (if any).
+ EndListeningHandler aEndLisFunc(rEndCxt);
+ sc::CellStoreType::iterator itPos = maCells.begin();
+ for (const auto& rSpan : aSpans)
+ {
+ SCROW nRow1 = rSpan.mnRow1;
+ SCROW nRow2 = rSpan.mnRow2;
+ itPos = sc::ProcessFormula(itPos, maCells, nRow1, nRow2, aEndLisFunc);
+ }
+
+ rValues.swapNonEmpty(nTab, nCol, *this);
+ RegroupFormulaCells();
+
+ // Attach formula cells within the spans (if any).
+ StartListeningHandler aStartLisFunc(rStartCxt);
+ itPos = maCells.begin();
+ for (const auto& rSpan : aSpans)
+ {
+ SCROW nRow1 = rSpan.mnRow1;
+ SCROW nRow2 = rSpan.mnRow2;
+ itPos = sc::ProcessFormula(itPos, maCells, nRow1, nRow2, aStartLisFunc);
+ }
+
+ CellStorageModified();
+}
+
+void ScColumn::DeleteRanges( const std::vector<sc::RowSpan>& rRanges, InsertDeleteFlags nDelFlag )
+{
+ for (const auto& rSpan : rRanges)
+ DeleteArea(rSpan.mnRow1, rSpan.mnRow2, nDelFlag, false/*bBroadcast*/);
+}
+
+void ScColumn::CloneFormulaCell(
+ sc::ColumnBlockPosition& rBlockPos,
+ const ScFormulaCell& rSrc, const sc::CellTextAttr& rAttr,
+ const std::vector<sc::RowSpan>& rRanges )
+{
+ SCCOL nMatrixCols = 0;
+ SCROW nMatrixRows = 0;
+ ScMatrixMode nMatrixFlag = rSrc.GetMatrixFlag();
+ if (nMatrixFlag == ScMatrixMode::Formula)
+ {
+ rSrc.GetMatColsRows( nMatrixCols, nMatrixRows);
+ SAL_WARN_IF( nMatrixCols != 1 || nMatrixRows != 1, "sc.core",
+ "ScColumn::CloneFormulaCell - cloning array/matrix with not exactly one column or row as single cell");
+ }
+
+ ScDocument& rDocument = GetDoc();
+ std::vector<ScFormulaCell*> aFormulas;
+ for (const auto& rSpan : rRanges)
+ {
+ SCROW nRow1 = rSpan.mnRow1, nRow2 = rSpan.mnRow2;
+ size_t nLen = nRow2 - nRow1 + 1;
+ assert(nLen > 0);
+ aFormulas.clear();
+ aFormulas.reserve(nLen);
+
+ ScAddress aPos(nCol, nRow1, nTab);
+
+ if (nLen == 1 || !rSrc.GetCode()->IsShareable())
+ {
+ // Single, ungrouped formula cell, or create copies for
+ // non-shareable token arrays.
+ for (size_t i = 0; i < nLen; ++i, aPos.IncRow())
+ {
+ ScFormulaCell* pCell = new ScFormulaCell(rSrc, rDocument, aPos);
+ aFormulas.push_back(pCell);
+ }
+ }
+ else
+ {
+ // Create a group of formula cells.
+ ScFormulaCellGroupRef xGroup(new ScFormulaCellGroup);
+ xGroup->setCode(*rSrc.GetCode());
+ xGroup->compileCode(rDocument, aPos, rDocument.GetGrammar());
+ for (size_t i = 0; i < nLen; ++i, aPos.IncRow())
+ {
+ ScFormulaCell* pCell = new ScFormulaCell(rDocument, aPos, xGroup, rDocument.GetGrammar(), nMatrixFlag);
+ if (nMatrixFlag == ScMatrixMode::Formula)
+ pCell->SetMatColsRows( nMatrixCols, nMatrixRows);
+ if (i == 0)
+ {
+ xGroup->mpTopCell = pCell;
+ xGroup->mnLength = nLen;
+ }
+ aFormulas.push_back(pCell);
+ }
+ }
+
+ rBlockPos.miCellPos = maCells.set(rBlockPos.miCellPos, nRow1, aFormulas.begin(), aFormulas.end());
+
+ // Join the top and bottom of the pasted formula cells as needed.
+ sc::CellStoreType::position_type aPosObj = maCells.position(rBlockPos.miCellPos, nRow1);
+
+ assert(aPosObj.first->type == sc::element_type_formula);
+ ScFormulaCell* pCell = sc::formula_block::at(*aPosObj.first->data, aPosObj.second);
+ JoinNewFormulaCell(aPosObj, *pCell);
+
+ aPosObj = maCells.position(aPosObj.first, nRow2);
+ assert(aPosObj.first->type == sc::element_type_formula);
+ pCell = sc::formula_block::at(*aPosObj.first->data, aPosObj.second);
+ JoinNewFormulaCell(aPosObj, *pCell);
+
+ std::vector<sc::CellTextAttr> aTextAttrs(nLen, rAttr);
+ rBlockPos.miCellTextAttrPos = maCellTextAttrs.set(
+ rBlockPos.miCellTextAttrPos, nRow1, aTextAttrs.begin(), aTextAttrs.end());
+ }
+
+ CellStorageModified();
+}
+
+void ScColumn::CloneFormulaCell(
+ const ScFormulaCell& rSrc, const sc::CellTextAttr& rAttr,
+ const std::vector<sc::RowSpan>& rRanges )
+{
+ sc::ColumnBlockPosition aBlockPos;
+ InitBlockPosition(aBlockPos);
+ CloneFormulaCell(aBlockPos, rSrc, rAttr, rRanges);
+}
+
+std::unique_ptr<ScPostIt> ScColumn::ReleaseNote( SCROW nRow )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return nullptr;
+
+ ScPostIt* p = nullptr;
+ maCellNotes.release(nRow, p);
+ return std::unique_ptr<ScPostIt>(p);
+}
+
+size_t ScColumn::GetNoteCount() const
+{
+ return std::accumulate(maCellNotes.begin(), maCellNotes.end(), size_t(0),
+ [](const size_t& rCount, const auto& rCellNote) {
+ if (rCellNote.type != sc::element_type_cellnote)
+ return rCount;
+ return rCount + rCellNote.size;
+ });
+}
+
+namespace {
+
+class NoteCaptionCreator
+{
+ ScAddress maPos;
+public:
+ NoteCaptionCreator( SCTAB nTab, SCCOL nCol ) : maPos(nCol,0,nTab) {}
+
+ void operator() ( size_t nRow, const ScPostIt* p )
+ {
+ maPos.SetRow(nRow);
+ p->GetOrCreateCaption(maPos);
+ }
+};
+
+class NoteCaptionCleaner
+{
+ bool mbPreserveData;
+public:
+ explicit NoteCaptionCleaner( bool bPreserveData ) : mbPreserveData(bPreserveData) {}
+
+ void operator() ( size_t /*nRow*/, ScPostIt* p )
+ {
+ p->ForgetCaption(mbPreserveData);
+ }
+};
+
+}
+
+void ScColumn::CreateAllNoteCaptions()
+{
+ NoteCaptionCreator aFunc(nTab, nCol);
+ sc::ProcessNote(maCellNotes, aFunc);
+}
+
+void ScColumn::ForgetNoteCaptions( SCROW nRow1, SCROW nRow2, bool bPreserveData )
+{
+ if (maCellNotes.empty())
+ return;
+
+ if (!GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2))
+ return;
+
+ NoteCaptionCleaner aFunc(bPreserveData);
+ sc::CellNoteStoreType::iterator it = maCellNotes.begin();
+ sc::ProcessNote(it, maCellNotes, nRow1, nRow2, aFunc);
+}
+
+SCROW ScColumn::GetNotePosition( size_t nIndex ) const
+{
+ // Return the row position of the nth note in the column.
+
+ size_t nCount = 0; // Number of notes encountered so far.
+ for (const auto& rCellNote : maCellNotes)
+ {
+ if (rCellNote.type != sc::element_type_cellnote)
+ // Skip the empty blocks.
+ continue;
+
+ if (nIndex < nCount + rCellNote.size)
+ {
+ // Index falls within this block.
+ size_t nOffset = nIndex - nCount;
+ return rCellNote.position + nOffset;
+ }
+
+ nCount += rCellNote.size;
+ }
+
+ return -1;
+}
+
+namespace {
+
+class NoteEntryCollector
+{
+ std::vector<sc::NoteEntry>& mrNotes;
+ SCTAB mnTab;
+ SCCOL mnCol;
+ SCROW mnStartRow;
+ SCROW mnEndRow;
+public:
+ NoteEntryCollector( std::vector<sc::NoteEntry>& rNotes, SCTAB nTab, SCCOL nCol,
+ SCROW nStartRow, SCROW nEndRow) :
+ mrNotes(rNotes), mnTab(nTab), mnCol(nCol),
+ mnStartRow(nStartRow), mnEndRow(nEndRow) {}
+
+ void operator() (const sc::CellNoteStoreType::value_type& node) const
+ {
+ if (node.type != sc::element_type_cellnote)
+ return;
+
+ size_t nTopRow = node.position;
+ sc::cellnote_block::const_iterator it = sc::cellnote_block::begin(*node.data);
+ sc::cellnote_block::const_iterator itEnd = sc::cellnote_block::end(*node.data);
+ size_t nOffset = 0;
+ if(nTopRow < o3tl::make_unsigned(mnStartRow))
+ {
+ std::advance(it, mnStartRow - nTopRow);
+ nOffset = mnStartRow - nTopRow;
+ }
+
+ for (; it != itEnd && nTopRow + nOffset <= o3tl::make_unsigned(mnEndRow);
+ ++it, ++nOffset)
+ {
+ ScAddress aPos(mnCol, nTopRow + nOffset, mnTab);
+ mrNotes.emplace_back(aPos, *it);
+ }
+ }
+};
+
+}
+
+void ScColumn::GetAllNoteEntries( std::vector<sc::NoteEntry>& rNotes ) const
+{
+ std::for_each(maCellNotes.begin(), maCellNotes.end(), NoteEntryCollector(rNotes, nTab, nCol, 0, GetDoc().MaxRow()));
+}
+
+void ScColumn::GetNotesInRange(SCROW nStartRow, SCROW nEndRow,
+ std::vector<sc::NoteEntry>& rNotes ) const
+{
+ std::pair<sc::CellNoteStoreType::const_iterator,size_t> aPos = maCellNotes.position(nStartRow);
+ sc::CellNoteStoreType::const_iterator it = aPos.first;
+ if (it == maCellNotes.end())
+ // Invalid row number.
+ return;
+
+ std::pair<sc::CellNoteStoreType::const_iterator,size_t> aEndPos =
+ maCellNotes.position(nEndRow);
+ sc::CellNoteStoreType::const_iterator itEnd = aEndPos.first;
+
+ std::for_each(it, ++itEnd, NoteEntryCollector(rNotes, nTab, nCol, nStartRow, nEndRow));
+}
+
+bool ScColumn::HasCellNote(SCROW nStartRow, SCROW nEndRow) const
+{
+ std::pair<sc::CellNoteStoreType::const_iterator,size_t> aStartPos =
+ maCellNotes.position(nStartRow);
+ if (aStartPos.first == maCellNotes.end())
+ // Invalid row number.
+ return false;
+
+ std::pair<sc::CellNoteStoreType::const_iterator,size_t> aEndPos =
+ maCellNotes.position(nEndRow);
+
+ for (sc::CellNoteStoreType::const_iterator it = aStartPos.first; it != aEndPos.first; ++it)
+ {
+ if (it->type != sc::element_type_cellnote)
+ continue;
+ size_t nTopRow = it->position;
+ sc::cellnote_block::const_iterator blockIt = sc::cellnote_block::begin(*(it->data));
+ sc::cellnote_block::const_iterator blockItEnd = sc::cellnote_block::end(*(it->data));
+ size_t nOffset = 0;
+ if(nTopRow < o3tl::make_unsigned(nStartRow))
+ {
+ std::advance(blockIt, nStartRow - nTopRow);
+ nOffset = nStartRow - nTopRow;
+ }
+
+ if (blockIt != blockItEnd && nTopRow + nOffset <= o3tl::make_unsigned(nEndRow))
+ return true;
+ }
+
+ return false;
+}
+
+void ScColumn::GetUnprotectedCells( SCROW nStartRow, SCROW nEndRow, ScRangeList& rRangeList ) const
+{
+ SCROW nTmpStartRow = nStartRow, nTmpEndRow = nEndRow;
+ const ScPatternAttr* pPattern = pAttrArray->GetPatternRange(nTmpStartRow, nTmpEndRow, nStartRow);
+ bool bProtection = pPattern->GetItem(ATTR_PROTECTION).GetProtection();
+ if (!bProtection)
+ {
+ // Limit the span to the range in question.
+ if (nTmpStartRow < nStartRow)
+ nTmpStartRow = nStartRow;
+ if (nTmpEndRow > nEndRow)
+ nTmpEndRow = nEndRow;
+ rRangeList.Join( ScRange( nCol, nTmpStartRow, nTab, nCol, nTmpEndRow, nTab));
+ }
+ while (nEndRow > nTmpEndRow)
+ {
+ nStartRow = nTmpEndRow + 1;
+ pPattern = pAttrArray->GetPatternRange(nTmpStartRow, nTmpEndRow, nStartRow);
+ bool bTmpProtection = pPattern->GetItem(ATTR_PROTECTION).GetProtection();
+ if (!bTmpProtection)
+ {
+ // Limit the span to the range in question.
+ // Only end row needs to be checked as we enter here only for spans
+ // below the original nStartRow.
+ if (nTmpEndRow > nEndRow)
+ nTmpEndRow = nEndRow;
+ rRangeList.Join( ScRange( nCol, nTmpStartRow, nTab, nCol, nTmpEndRow, nTab));
+ }
+ }
+}
+
+namespace {
+
+class RecompileByOpcodeHandler
+{
+ ScDocument* mpDoc;
+ const formula::unordered_opcode_set& mrOps;
+ sc::EndListeningContext& mrEndListenCxt;
+ sc::CompileFormulaContext& mrCompileFormulaCxt;
+
+public:
+ RecompileByOpcodeHandler(
+ ScDocument* pDoc, const formula::unordered_opcode_set& rOps,
+ sc::EndListeningContext& rEndListenCxt, sc::CompileFormulaContext& rCompileCxt ) :
+ mpDoc(pDoc),
+ mrOps(rOps),
+ mrEndListenCxt(rEndListenCxt),
+ mrCompileFormulaCxt(rCompileCxt) {}
+
+ void operator() ( sc::FormulaGroupEntry& rEntry )
+ {
+ // Perform end listening, remove from formula tree, and set them up
+ // for re-compilation.
+
+ ScFormulaCell* pTop = nullptr;
+
+ if (rEntry.mbShared)
+ {
+ // Only inspect the code from the top cell.
+ pTop = *rEntry.mpCells;
+ }
+ else
+ pTop = rEntry.mpCell;
+
+ ScTokenArray* pCode = pTop->GetCode();
+ bool bRecompile = pCode->HasOpCodes(mrOps);
+
+ if (!bRecompile)
+ return;
+
+ // Get the formula string.
+ OUString aFormula = pTop->GetFormula(mrCompileFormulaCxt);
+ sal_Int32 n = aFormula.getLength();
+ if (pTop->GetMatrixFlag() != ScMatrixMode::NONE && n > 0)
+ {
+ if (aFormula[0] == '{' && aFormula[n-1] == '}')
+ aFormula = aFormula.copy(1, n-2);
+ }
+
+ if (rEntry.mbShared)
+ {
+ ScFormulaCell** pp = rEntry.mpCells;
+ ScFormulaCell** ppEnd = pp + rEntry.mnLength;
+ for (; pp != ppEnd; ++pp)
+ {
+ ScFormulaCell* p = *pp;
+ p->EndListeningTo(mrEndListenCxt);
+ mpDoc->RemoveFromFormulaTree(p);
+ }
+ }
+ else
+ {
+ rEntry.mpCell->EndListeningTo(mrEndListenCxt);
+ mpDoc->RemoveFromFormulaTree(rEntry.mpCell);
+ }
+
+ pCode->Clear();
+ pTop->SetHybridFormula(aFormula, mpDoc->GetGrammar());
+ }
+};
+
+class CompileHybridFormulaHandler
+{
+ ScDocument& mrDoc;
+ sc::StartListeningContext& mrStartListenCxt;
+ sc::CompileFormulaContext& mrCompileFormulaCxt;
+
+public:
+ CompileHybridFormulaHandler(ScDocument& rDoc, sc::StartListeningContext& rStartListenCxt, sc::CompileFormulaContext& rCompileCxt ) :
+ mrDoc(rDoc),
+ mrStartListenCxt(rStartListenCxt),
+ mrCompileFormulaCxt(rCompileCxt) {}
+
+ void operator() ( sc::FormulaGroupEntry& rEntry )
+ {
+ if (rEntry.mbShared)
+ {
+ ScFormulaCell* pTop = *rEntry.mpCells;
+ OUString aFormula = pTop->GetHybridFormula();
+
+ if (!aFormula.isEmpty())
+ {
+ // Create a new token array from the hybrid formula string, and
+ // set it to the group.
+ ScCompiler aComp(mrCompileFormulaCxt, pTop->aPos);
+ std::unique_ptr<ScTokenArray> pNewCode = aComp.CompileString(aFormula);
+ ScFormulaCellGroupRef xGroup = pTop->GetCellGroup();
+ assert(xGroup);
+ xGroup->setCode(std::move(*pNewCode));
+ xGroup->compileCode(mrDoc, pTop->aPos, mrDoc.GetGrammar());
+
+ // Propagate the new token array to all formula cells in the group.
+ ScFormulaCell** pp = rEntry.mpCells;
+ ScFormulaCell** ppEnd = pp + rEntry.mnLength;
+ for (; pp != ppEnd; ++pp)
+ {
+ ScFormulaCell* p = *pp;
+ p->SyncSharedCode();
+ p->StartListeningTo(mrStartListenCxt);
+ p->SetDirty();
+ }
+ }
+ }
+ else
+ {
+ ScFormulaCell* pCell = rEntry.mpCell;
+ OUString aFormula = pCell->GetHybridFormula();
+
+ if (!aFormula.isEmpty())
+ {
+ // Create token array from formula string.
+ ScCompiler aComp(mrCompileFormulaCxt, pCell->aPos);
+ std::unique_ptr<ScTokenArray> pNewCode = aComp.CompileString(aFormula);
+
+ // Generate RPN tokens.
+ ScCompiler aComp2(mrDoc, pCell->aPos, *pNewCode, formula::FormulaGrammar::GRAM_UNSPECIFIED,
+ true, pCell->GetMatrixFlag() != ScMatrixMode::NONE);
+ aComp2.CompileTokenArray();
+
+ pCell->SetCode(std::move(pNewCode));
+ pCell->StartListeningTo(mrStartListenCxt);
+ pCell->SetDirty();
+ }
+ }
+ }
+};
+
+}
+
+void ScColumn::PreprocessRangeNameUpdate(
+ sc::EndListeningContext& rEndListenCxt, sc::CompileFormulaContext& rCompileCxt )
+{
+ // Collect all formula groups.
+ std::vector<sc::FormulaGroupEntry> aGroups = GetFormulaGroupEntries();
+
+ formula::unordered_opcode_set aOps;
+ aOps.insert(ocBad);
+ aOps.insert(ocColRowName);
+ aOps.insert(ocName);
+ RecompileByOpcodeHandler aFunc(&GetDoc(), aOps, rEndListenCxt, rCompileCxt);
+ std::for_each(aGroups.begin(), aGroups.end(), aFunc);
+}
+
+void ScColumn::PreprocessDBDataUpdate(
+ sc::EndListeningContext& rEndListenCxt, sc::CompileFormulaContext& rCompileCxt )
+{
+ // Collect all formula groups.
+ std::vector<sc::FormulaGroupEntry> aGroups = GetFormulaGroupEntries();
+
+ formula::unordered_opcode_set aOps;
+ aOps.insert(ocBad);
+ aOps.insert(ocColRowName);
+ aOps.insert(ocDBArea);
+ aOps.insert(ocTableRef);
+ RecompileByOpcodeHandler aFunc(&GetDoc(), aOps, rEndListenCxt, rCompileCxt);
+ std::for_each(aGroups.begin(), aGroups.end(), aFunc);
+}
+
+void ScColumn::CompileHybridFormula(
+ sc::StartListeningContext& rStartListenCxt, sc::CompileFormulaContext& rCompileCxt )
+{
+ // Collect all formula groups.
+ std::vector<sc::FormulaGroupEntry> aGroups = GetFormulaGroupEntries();
+
+ CompileHybridFormulaHandler aFunc(GetDoc(), rStartListenCxt, rCompileCxt);
+ std::for_each(aGroups.begin(), aGroups.end(), aFunc);
+}
+
+namespace {
+
+class ScriptTypeUpdater
+{
+ ScColumn& mrCol;
+ sc::CellTextAttrStoreType& mrTextAttrs;
+ sc::CellTextAttrStoreType::iterator miPosAttr;
+ ScConditionalFormatList* mpCFList;
+ SvNumberFormatter* mpFormatter;
+ ScAddress maPos;
+ bool mbUpdated;
+
+private:
+ void updateScriptType( size_t nRow, ScRefCellValue& rCell )
+ {
+ sc::CellTextAttrStoreType::position_type aAttrPos = mrTextAttrs.position(miPosAttr, nRow);
+ miPosAttr = aAttrPos.first;
+
+ if (aAttrPos.first->type != sc::element_type_celltextattr)
+ return;
+
+ sc::CellTextAttr& rAttr = sc::celltextattr_block::at(*aAttrPos.first->data, aAttrPos.second);
+ if (rAttr.mnScriptType != SvtScriptType::UNKNOWN)
+ // Script type already determined. Skip it.
+ return;
+
+ const ScPatternAttr* pPat = mrCol.GetPattern(nRow);
+ if (!pPat)
+ // In theory this should never return NULL. But let's be safe.
+ return;
+
+ const SfxItemSet* pCondSet = nullptr;
+ if (mpCFList)
+ {
+ maPos.SetRow(nRow);
+ const ScCondFormatItem& rItem = pPat->GetItem(ATTR_CONDITIONAL);
+ const ScCondFormatIndexes& rData = rItem.GetCondFormatData();
+ pCondSet = mrCol.GetDoc().GetCondResult(rCell, maPos, *mpCFList, rData);
+ }
+
+ const Color* pColor;
+ sal_uInt32 nFormat = pPat->GetNumberFormat(mpFormatter, pCondSet);
+ OUString aStr = ScCellFormat::GetString(rCell, nFormat, &pColor, *mpFormatter, mrCol.GetDoc());
+
+ rAttr.mnScriptType = mrCol.GetDoc().GetStringScriptType(aStr);
+ mbUpdated = true;
+ }
+
+public:
+ explicit ScriptTypeUpdater( ScColumn& rCol ) :
+ mrCol(rCol),
+ mrTextAttrs(rCol.GetCellAttrStore()),
+ miPosAttr(mrTextAttrs.begin()),
+ mpCFList(rCol.GetDoc().GetCondFormList(rCol.GetTab())),
+ mpFormatter(rCol.GetDoc().GetFormatTable()),
+ maPos(rCol.GetCol(), 0, rCol.GetTab()),
+ mbUpdated(false)
+ {}
+
+ void operator() ( size_t nRow, double fVal )
+ {
+ ScRefCellValue aCell(fVal);
+ updateScriptType(nRow, aCell);
+ }
+
+ void operator() ( size_t nRow, const svl::SharedString& rStr )
+ {
+ ScRefCellValue aCell(&rStr);
+ updateScriptType(nRow, aCell);
+ }
+
+ void operator() ( size_t nRow, const EditTextObject* pText )
+ {
+ ScRefCellValue aCell(pText);
+ updateScriptType(nRow, aCell);
+ }
+
+ void operator() ( size_t nRow, const ScFormulaCell* pCell )
+ {
+ ScRefCellValue aCell(const_cast<ScFormulaCell*>(pCell));
+ updateScriptType(nRow, aCell);
+ }
+
+ bool isUpdated() const { return mbUpdated; }
+};
+
+}
+
+void ScColumn::UpdateScriptTypes( SCROW nRow1, SCROW nRow2 )
+{
+ if (!GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2) || nRow1 > nRow2)
+ return;
+
+ ScriptTypeUpdater aFunc(*this);
+ sc::ParseAllNonEmpty(maCells.begin(), maCells, nRow1, nRow2, aFunc);
+ if (aFunc.isUpdated())
+ CellStorageModified();
+}
+
+void ScColumn::Swap( ScColumn& rOther, SCROW nRow1, SCROW nRow2, bool bPattern )
+{
+ maCells.swap(nRow1, nRow2, rOther.maCells, nRow1);
+ maCellTextAttrs.swap(nRow1, nRow2, rOther.maCellTextAttrs, nRow1);
+ maCellNotes.swap(nRow1, nRow2, rOther.maCellNotes, nRow1);
+ maBroadcasters.swap(nRow1, nRow2, rOther.maBroadcasters, nRow1);
+
+ // Update draw object anchors
+ ScDrawLayer* pDrawLayer = GetDoc().GetDrawLayer();
+ if (pDrawLayer)
+ {
+ std::map<SCROW, std::vector<SdrObject*>> aThisColRowDrawObjects
+ = pDrawLayer->GetObjectsAnchoredToRange(GetTab(), GetCol(), nRow1, nRow2);
+ std::map<SCROW, std::vector<SdrObject*>> aOtherColRowDrawObjects
+ = pDrawLayer->GetObjectsAnchoredToRange(GetTab(), rOther.GetCol(), nRow1, nRow2);
+ for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
+ {
+ std::vector<SdrObject*>& rThisCellDrawObjects = aThisColRowDrawObjects[nRow];
+ if (!rThisCellDrawObjects.empty())
+ UpdateDrawObjectsForRow(rThisCellDrawObjects, rOther.GetCol(), nRow);
+ std::vector<SdrObject*>& rOtherCellDrawObjects = aOtherColRowDrawObjects[nRow];
+ if (!rOtherCellDrawObjects.empty())
+ rOther.UpdateDrawObjectsForRow(rOtherCellDrawObjects, GetCol(), nRow);
+ }
+ }
+
+ if (bPattern)
+ {
+ for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
+ {
+ const ScPatternAttr* pPat1 = GetPattern(nRow);
+ const ScPatternAttr* pPat2 = rOther.GetPattern(nRow);
+ if (pPat1 != pPat2)
+ {
+ if (pPat1->GetRefCount() == 1)
+ pPat1 = &rOther.GetDoc().GetPool()->Put(*pPat1);
+ SetPattern(nRow, *pPat2);
+ rOther.SetPattern(nRow, *pPat1);
+ }
+ }
+ }
+
+ CellStorageModified();
+ rOther.CellStorageModified();
+}
+
+namespace {
+
+class FormulaColPosSetter
+{
+ SCCOL mnCol;
+ bool mbUpdateRefs;
+public:
+ FormulaColPosSetter( SCCOL nCol, bool bUpdateRefs ) : mnCol(nCol), mbUpdateRefs(bUpdateRefs) {}
+
+ void operator() ( size_t nRow, ScFormulaCell* pCell )
+ {
+ if (!pCell->IsShared() || pCell->IsSharedTop())
+ {
+ // Ensure that the references still point to the same locations
+ // after the position change.
+ ScAddress aOldPos = pCell->aPos;
+ pCell->aPos.SetCol(mnCol);
+ pCell->aPos.SetRow(nRow);
+ if (mbUpdateRefs)
+ pCell->GetCode()->AdjustReferenceOnMovedOrigin(aOldPos, pCell->aPos);
+ else
+ pCell->GetCode()->AdjustReferenceOnMovedOriginIfOtherSheet(aOldPos, pCell->aPos);
+ }
+ else
+ {
+ pCell->aPos.SetCol(mnCol);
+ pCell->aPos.SetRow(nRow);
+ }
+ }
+};
+
+}
+
+void ScColumn::ResetFormulaCellPositions( SCROW nRow1, SCROW nRow2, bool bUpdateRefs )
+{
+ FormulaColPosSetter aFunc(nCol, bUpdateRefs);
+ sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aFunc);
+}
+
+namespace {
+
+class RelativeRefBoundChecker
+{
+ std::vector<SCROW> maBounds;
+ ScRange maBoundRange;
+
+public:
+ explicit RelativeRefBoundChecker( const ScRange& rBoundRange ) :
+ maBoundRange(rBoundRange) {}
+
+ void operator() ( size_t /*nRow*/, ScFormulaCell* pCell )
+ {
+ if (!pCell->IsSharedTop())
+ return;
+
+ pCell->GetCode()->CheckRelativeReferenceBounds(
+ pCell->aPos, pCell->GetSharedLength(), maBoundRange, maBounds);
+ }
+
+ void swapBounds( std::vector<SCROW>& rBounds )
+ {
+ rBounds.swap(maBounds);
+ }
+};
+
+}
+
+void ScColumn::SplitFormulaGroupByRelativeRef( const ScRange& rBoundRange )
+{
+ if (rBoundRange.aStart.Row() >= GetDoc().MaxRow())
+ // Nothing to split.
+ return;
+
+ std::vector<SCROW> aBounds;
+
+ // Cut at row boundaries first.
+ aBounds.push_back(rBoundRange.aStart.Row());
+ if (rBoundRange.aEnd.Row() < GetDoc().MaxRow())
+ aBounds.push_back(rBoundRange.aEnd.Row()+1);
+ sc::SharedFormulaUtil::splitFormulaCellGroups(GetDoc(), maCells, aBounds);
+
+ RelativeRefBoundChecker aFunc(rBoundRange);
+ sc::ProcessFormula(
+ maCells.begin(), maCells, rBoundRange.aStart.Row(), rBoundRange.aEnd.Row(), aFunc);
+ aFunc.swapBounds(aBounds);
+ sc::SharedFormulaUtil::splitFormulaCellGroups(GetDoc(), maCells, aBounds);
+}
+
+namespace {
+
+class ListenerCollector
+{
+ std::vector<SvtListener*>& mrListeners;
+public:
+ explicit ListenerCollector( std::vector<SvtListener*>& rListener ) :
+ mrListeners(rListener) {}
+
+ void operator() ( size_t /*nRow*/, SvtBroadcaster* p )
+ {
+ SvtBroadcaster::ListenersType& rLis = p->GetAllListeners();
+ mrListeners.insert(mrListeners.end(), rLis.begin(), rLis.end());
+ }
+};
+
+class FormulaCellCollector
+{
+ std::vector<ScFormulaCell*>& mrCells;
+public:
+ explicit FormulaCellCollector( std::vector<ScFormulaCell*>& rCells ) : mrCells(rCells) {}
+
+ void operator() ( size_t /*nRow*/, ScFormulaCell* p )
+ {
+ mrCells.push_back(p);
+ }
+};
+
+}
+
+void ScColumn::CollectListeners( std::vector<SvtListener*>& rListeners, SCROW nRow1, SCROW nRow2 )
+{
+ if (nRow2 < nRow1 || !GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2))
+ return;
+
+ ListenerCollector aFunc(rListeners);
+ sc::ProcessBroadcaster(maBroadcasters.begin(), maBroadcasters, nRow1, nRow2, aFunc);
+}
+
+void ScColumn::CollectFormulaCells( std::vector<ScFormulaCell*>& rCells, SCROW nRow1, SCROW nRow2 )
+{
+ if (nRow2 < nRow1 || !GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2))
+ return;
+
+ FormulaCellCollector aFunc(rCells);
+ sc::ProcessFormula(maCells.begin(), maCells, nRow1, nRow2, aFunc);
+}
+
+bool ScColumn::HasFormulaCell() const
+{
+ return mnBlkCountFormula != 0;
+}
+
+namespace {
+
+struct FindAnyFormula
+{
+ bool operator() ( size_t /*nRow*/, const ScFormulaCell* /*pCell*/ ) const
+ {
+ return true;
+ }
+};
+
+}
+
+bool ScColumn::HasFormulaCell( SCROW nRow1, SCROW nRow2 ) const
+{
+ if (!mnBlkCountFormula)
+ return false;
+
+ if (nRow2 < nRow1 || !GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2))
+ return false;
+
+ if (nRow1 == 0 && nRow2 == GetDoc().MaxRow())
+ return HasFormulaCell();
+
+ FindAnyFormula aFunc;
+ std::pair<sc::CellStoreType::const_iterator, size_t> aRet =
+ sc::FindFormula(maCells, nRow1, nRow2, aFunc);
+
+ return aRet.first != maCells.end();
+}
+
+namespace {
+
+void endListening( sc::EndListeningContext& rCxt, ScFormulaCell** pp, ScFormulaCell** ppEnd )
+{
+ for (; pp != ppEnd; ++pp)
+ {
+ ScFormulaCell& rFC = **pp;
+ rFC.EndListeningTo(rCxt);
+ }
+}
+
+class StartListeningFormulaCellsHandler
+{
+ sc::StartListeningContext& mrStartCxt;
+ sc::EndListeningContext& mrEndCxt;
+ SCROW mnStartRow;
+
+public:
+ StartListeningFormulaCellsHandler( sc::StartListeningContext& rStartCxt, sc::EndListeningContext& rEndCxt ) :
+ mrStartCxt(rStartCxt), mrEndCxt(rEndCxt), mnStartRow(-1) {}
+
+ void operator() ( const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize )
+ {
+ if (node.type != sc::element_type_formula)
+ // We are only interested in formulas.
+ return;
+
+ mnStartRow = node.position + nOffset;
+
+ ScFormulaCell** ppBeg = &sc::formula_block::at(*node.data, nOffset);
+ ScFormulaCell** ppEnd = ppBeg + nDataSize;
+
+ ScFormulaCell** pp = ppBeg;
+
+ // If the first formula cell belongs to a group and it's not the top
+ // cell, move up to the top cell of the group, and have all the extra
+ // formula cells stop listening.
+
+ ScFormulaCell* pFC = *pp;
+ if (pFC->IsShared() && !pFC->IsSharedTop())
+ {
+ SCROW nBackTrackSize = pFC->aPos.Row() - pFC->GetSharedTopRow();
+ if (nBackTrackSize > 0)
+ {
+ assert(o3tl::make_unsigned(nBackTrackSize) <= nOffset);
+ for (SCROW i = 0; i < nBackTrackSize; ++i)
+ --pp;
+ endListening(mrEndCxt, pp, ppBeg);
+ mnStartRow -= nBackTrackSize;
+ }
+ }
+
+ for (; pp != ppEnd; ++pp)
+ {
+ pFC = *pp;
+
+ if (!pFC->IsSharedTop())
+ {
+ assert(!pFC->IsShared());
+ pFC->StartListeningTo(mrStartCxt);
+ continue;
+ }
+
+ // If This is the last group in the range, see if the group
+ // extends beyond the range, in which case have the excess
+ // formula cells stop listening.
+ size_t nEndGroupPos = (pp - ppBeg) + pFC->GetSharedLength();
+ if (nEndGroupPos > nDataSize)
+ {
+ size_t nExcessSize = nEndGroupPos - nDataSize;
+ ScFormulaCell** ppGrpEnd = pp + pFC->GetSharedLength();
+ ScFormulaCell** ppGrp = ppGrpEnd - nExcessSize;
+ endListening(mrEndCxt, ppGrp, ppGrpEnd);
+
+ // Register formula cells as a group.
+ sc::SharedFormulaUtil::startListeningAsGroup(mrStartCxt, pp);
+ pp = ppEnd - 1; // Move to the one before the end position.
+ }
+ else
+ {
+ // Register formula cells as a group.
+ sc::SharedFormulaUtil::startListeningAsGroup(mrStartCxt, pp);
+ pp += pFC->GetSharedLength() - 1; // Move to the last one in the group.
+ }
+ }
+ }
+
+};
+
+class EndListeningFormulaCellsHandler
+{
+ sc::EndListeningContext& mrEndCxt;
+ SCROW mnStartRow;
+ SCROW mnEndRow;
+
+public:
+ explicit EndListeningFormulaCellsHandler( sc::EndListeningContext& rEndCxt ) :
+ mrEndCxt(rEndCxt), mnStartRow(-1), mnEndRow(-1) {}
+
+ void operator() ( const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize )
+ {
+ if (node.type != sc::element_type_formula)
+ // We are only interested in formulas.
+ return;
+
+ mnStartRow = node.position + nOffset;
+
+ ScFormulaCell** ppBeg = &sc::formula_block::at(*node.data, nOffset);
+ ScFormulaCell** ppEnd = ppBeg + nDataSize;
+
+ ScFormulaCell** pp = ppBeg;
+
+ // If the first formula cell belongs to a group and it's not the top
+ // cell, move up to the top cell of the group.
+
+ ScFormulaCell* pFC = *pp;
+ if (pFC->IsShared() && !pFC->IsSharedTop())
+ {
+ SCROW nBackTrackSize = pFC->aPos.Row() - pFC->GetSharedTopRow();
+ if (nBackTrackSize > 0)
+ {
+ assert(o3tl::make_unsigned(nBackTrackSize) <= nOffset);
+ for (SCROW i = 0; i < nBackTrackSize; ++i)
+ --pp;
+ mnStartRow -= nBackTrackSize;
+ }
+ }
+
+ for (; pp != ppEnd; ++pp)
+ {
+ pFC = *pp;
+
+ if (!pFC->IsSharedTop())
+ {
+ assert(!pFC->IsShared());
+ pFC->EndListeningTo(mrEndCxt);
+ continue;
+ }
+
+ size_t nEndGroupPos = (pp - ppBeg) + pFC->GetSharedLength();
+ mnEndRow = node.position + nOffset + nEndGroupPos - 1; // absolute row position of the last one in the group.
+
+ ScFormulaCell** ppGrpEnd = pp + pFC->GetSharedLength();
+ endListening(mrEndCxt, pp, ppGrpEnd);
+
+ if (nEndGroupPos > nDataSize)
+ {
+ // The group goes beyond the specified end row. Move to the
+ // one before the end position to finish the loop.
+ pp = ppEnd - 1;
+ }
+ else
+ {
+ // Move to the last one in the group.
+ pp += pFC->GetSharedLength() - 1;
+ }
+ }
+ }
+
+ SCROW getStartRow() const
+ {
+ return mnStartRow;
+ }
+
+ SCROW getEndRow() const
+ {
+ return mnEndRow;
+ }
+};
+
+}
+
+void ScColumn::StartListeningFormulaCells(
+ sc::StartListeningContext& rStartCxt, sc::EndListeningContext& rEndCxt,
+ SCROW nRow1, SCROW nRow2 )
+{
+ if (!HasFormulaCell())
+ return;
+
+ StartListeningFormulaCellsHandler aFunc(rStartCxt, rEndCxt);
+ sc::ProcessBlock(maCells.begin(), maCells, aFunc, nRow1, nRow2);
+}
+
+void ScColumn::EndListeningFormulaCells(
+ sc::EndListeningContext& rCxt, SCROW nRow1, SCROW nRow2,
+ SCROW* pStartRow, SCROW* pEndRow )
+{
+ if (!HasFormulaCell())
+ return;
+
+ EndListeningFormulaCellsHandler aFunc(rCxt);
+ sc::ProcessBlock(maCells.begin(), maCells, aFunc, nRow1, nRow2);
+
+ if (pStartRow)
+ *pStartRow = aFunc.getStartRow();
+
+ if (pEndRow)
+ *pEndRow = aFunc.getEndRow();
+}
+
+void ScColumn::EndListeningIntersectedGroup(
+ sc::EndListeningContext& rCxt, SCROW nRow, std::vector<ScAddress>* pGroupPos )
+{
+ if (!GetDoc().ValidRow(nRow))
+ return;
+
+ sc::CellStoreType::position_type aPos = maCells.position(nRow);
+ sc::CellStoreType::iterator it = aPos.first;
+ if (it->type != sc::element_type_formula)
+ // Only interested in a formula block.
+ return;
+
+ ScFormulaCell* pFC = sc::formula_block::at(*it->data, aPos.second);
+ ScFormulaCellGroupRef xGroup = pFC->GetCellGroup();
+ if (!xGroup)
+ // Not a formula group.
+ return;
+
+ // End listening.
+ pFC->EndListeningTo(rCxt);
+
+ if (pGroupPos)
+ {
+ if (!pFC->IsSharedTop())
+ // Record the position of the top cell of the group.
+ pGroupPos->push_back(xGroup->mpTopCell->aPos);
+
+ SCROW nGrpLastRow = pFC->GetSharedTopRow() + pFC->GetSharedLength() - 1;
+ if (nRow < nGrpLastRow)
+ // Record the last position of the group.
+ pGroupPos->push_back(ScAddress(nCol, nGrpLastRow, nTab));
+ }
+}
+
+void ScColumn::EndListeningIntersectedGroups(
+ sc::EndListeningContext& rCxt, SCROW nRow1, SCROW nRow2, std::vector<ScAddress>* pGroupPos )
+{
+ // Only end the intersected group.
+ sc::CellStoreType::position_type aPos = maCells.position(nRow1);
+ sc::CellStoreType::iterator it = aPos.first;
+ if (it->type == sc::element_type_formula)
+ {
+ ScFormulaCell* pFC = sc::formula_block::at(*it->data, aPos.second);
+ ScFormulaCellGroupRef xGroup = pFC->GetCellGroup();
+ if (xGroup)
+ {
+ if (!pFC->IsSharedTop())
+ // End listening.
+ pFC->EndListeningTo(rCxt);
+
+ if (pGroupPos)
+ // Record the position of the top cell of the group.
+ pGroupPos->push_back(xGroup->mpTopCell->aPos);
+ }
+ }
+
+ aPos = maCells.position(it, nRow2);
+ it = aPos.first;
+ if (it->type != sc::element_type_formula)
+ return;
+
+ ScFormulaCell* pFC = sc::formula_block::at(*it->data, aPos.second);
+ ScFormulaCellGroupRef xGroup = pFC->GetCellGroup();
+ if (!xGroup)
+ return;
+
+ if (!pFC->IsSharedTop())
+ // End listening.
+ pFC->EndListeningTo(rCxt);
+
+ if (pGroupPos)
+ {
+ // Record the position of the bottom cell of the group.
+ ScAddress aPosLast = xGroup->mpTopCell->aPos;
+ aPosLast.IncRow(xGroup->mnLength-1);
+ pGroupPos->push_back(aPosLast);
+ }
+}
+
+void ScColumn::EndListeningGroup( sc::EndListeningContext& rCxt, SCROW nRow )
+{
+ sc::CellStoreType::position_type aPos = maCells.position(nRow);
+ if (aPos.first->type != sc::element_type_formula)
+ // not a formula cell.
+ return;
+
+ ScFormulaCell** pp = &sc::formula_block::at(*aPos.first->data, aPos.second);
+
+ ScFormulaCellGroupRef xGroup = (*pp)->GetCellGroup();
+ if (!xGroup)
+ {
+ // not a formula group.
+ (*pp)->EndListeningTo(rCxt);
+ return;
+ }
+
+ // Move back to the top cell.
+ SCROW nTopDelta = (*pp)->aPos.Row() - xGroup->mpTopCell->aPos.Row();
+ assert(nTopDelta >= 0);
+ if (nTopDelta > 0)
+ pp -= nTopDelta;
+
+ // Set the needs listening flag to all cells in the group.
+ assert(*pp == xGroup->mpTopCell);
+ ScFormulaCell** ppEnd = pp + xGroup->mnLength;
+ for (; pp != ppEnd; ++pp)
+ (*pp)->EndListeningTo(rCxt);
+}
+
+void ScColumn::SetNeedsListeningGroup( SCROW nRow )
+{
+ sc::CellStoreType::position_type aPos = maCells.position(nRow);
+ if (aPos.first->type != sc::element_type_formula)
+ // not a formula cell.
+ return;
+
+ ScFormulaCell** pp = &sc::formula_block::at(*aPos.first->data, aPos.second);
+
+ ScFormulaCellGroupRef xGroup = (*pp)->GetCellGroup();
+ if (!xGroup)
+ {
+ // not a formula group.
+ (*pp)->SetNeedsListening(true);
+ return;
+ }
+
+ // Move back to the top cell.
+ SCROW nTopDelta = (*pp)->aPos.Row() - xGroup->mpTopCell->aPos.Row();
+ assert(nTopDelta >= 0);
+ if (nTopDelta > 0)
+ pp -= nTopDelta;
+
+ // Set the needs listening flag to all cells in the group.
+ assert(*pp == xGroup->mpTopCell);
+ ScFormulaCell** ppEnd = pp + xGroup->mnLength;
+ for (; pp != ppEnd; ++pp)
+ (*pp)->SetNeedsListening(true);
+}
+
+std::optional<sc::ColumnIterator> ScColumn::GetColumnIterator( SCROW nRow1, SCROW nRow2 ) const
+{
+ if (!GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2) || nRow1 > nRow2)
+ return {};
+
+ return sc::ColumnIterator(maCells, nRow1, nRow2);
+}
+
+static bool lcl_InterpretSpan(sc::formula_block::const_iterator& rSpanIter, SCROW nStartOffset, SCROW nEndOffset,
+ const ScFormulaCellGroupRef& mxParentGroup, bool& bAllowThreading, ScDocument& rDoc)
+{
+ bAllowThreading = true;
+ ScFormulaCell* pCellStart = nullptr;
+ SCROW nSpanStart = -1;
+ SCROW nSpanEnd = -1;
+ sc::formula_block::const_iterator itSpanStart;
+ bool bAnyDirty = false;
+ for (SCROW nFGOffset = nStartOffset; nFGOffset <= nEndOffset; ++rSpanIter, ++nFGOffset)
+ {
+ bool bThisDirty = (*rSpanIter)->NeedsInterpret();
+ if (!pCellStart && bThisDirty)
+ {
+ pCellStart = *rSpanIter;
+ itSpanStart = rSpanIter;
+ nSpanStart = nFGOffset;
+ bAnyDirty = true;
+ }
+
+ if (pCellStart && (!bThisDirty || nFGOffset == nEndOffset))
+ {
+ nSpanEnd = bThisDirty ? nFGOffset : nFGOffset - 1;
+ assert(nSpanStart >= nStartOffset && nSpanStart <= nSpanEnd && nSpanEnd <= nEndOffset);
+
+ // Found a completely dirty sub span [nSpanStart, nSpanEnd] inside the required span [nStartOffset, nEndOffset]
+ bool bGroupInterpreted = pCellStart->Interpret(nSpanStart, nSpanEnd);
+
+ if (bGroupInterpreted)
+ for (SCROW nIdx = nSpanStart; nIdx <= nSpanEnd; ++nIdx, ++itSpanStart)
+ assert(!(*itSpanStart)->NeedsInterpret());
+
+ ScRecursionHelper& rRecursionHelper = rDoc.GetRecursionHelper();
+ // child cell's Interpret could result in calling dependency calc
+ // and that could detect a cycle involving mxGroup
+ // and do early exit in that case.
+ // OR
+ // this call resulted from a dependency calculation for a multi-formula-group-threading and
+ // if intergroup dependency is found, return early.
+ if ((mxParentGroup && mxParentGroup->mbPartOfCycle) || !rRecursionHelper.AreGroupsIndependent())
+ {
+ bAllowThreading = false;
+ return bAnyDirty;
+ }
+
+ if (!bGroupInterpreted)
+ {
+ // Evaluate from second cell in non-grouped style (no point in trying group-interpret again).
+ ++itSpanStart;
+ for (SCROW nIdx = nSpanStart+1; nIdx <= nSpanEnd; ++nIdx, ++itSpanStart)
+ {
+ (*itSpanStart)->Interpret(); // We know for sure that this cell is dirty so directly call Interpret().
+ if ((*itSpanStart)->NeedsInterpret())
+ {
+ SAL_WARN("sc.core.formulagroup", "Internal error, cell " << (*itSpanStart)->aPos
+ << " failed running Interpret(), not allowing threading");
+ bAllowThreading = false;
+ return bAnyDirty;
+ }
+
+ // Allow early exit like above.
+ if ((mxParentGroup && mxParentGroup->mbPartOfCycle) || !rRecursionHelper.AreGroupsIndependent())
+ {
+ // Set this cell as dirty as this may be interpreted in InterpretTail()
+ pCellStart->SetDirtyVar();
+ bAllowThreading = false;
+ return bAnyDirty;
+ }
+ }
+ }
+
+ pCellStart = nullptr; // For next sub span start detection.
+ }
+ }
+
+ return bAnyDirty;
+}
+
+static void lcl_EvalDirty(sc::CellStoreType& rCells, SCROW nRow1, SCROW nRow2, ScDocument& rDoc,
+ const ScFormulaCellGroupRef& mxGroup, bool bThreadingDepEval, bool bSkipRunning,
+ bool& bIsDirty, bool& bAllowThreading)
+{
+ ScRecursionHelper& rRecursionHelper = rDoc.GetRecursionHelper();
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = rCells.position(nRow1);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ size_t nOffset = aPos.second;
+ SCROW nRow = nRow1;
+
+ bIsDirty = false;
+
+ for (;it != rCells.end() && nRow <= nRow2; ++it, nOffset = 0)
+ {
+ switch( it->type )
+ {
+ case sc::element_type_edittext:
+ // These require EditEngine (in ScEditUtils::GetString()), which is probably
+ // too complex for use in threads.
+ if (bThreadingDepEval)
+ {
+ bAllowThreading = false;
+ return;
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ size_t nRowsToRead = nRow2 - nRow + 1;
+ const size_t nEnd = std::min(it->size, nOffset+nRowsToRead); // last row + 1
+ sc::formula_block::const_iterator itCell = sc::formula_block::begin(*it->data);
+ std::advance(itCell, nOffset);
+
+ // Loop inside the formula block.
+ size_t nCellIdx = nOffset;
+ while (nCellIdx < nEnd)
+ {
+ const ScFormulaCellGroupRef& mxGroupChild = (*itCell)->GetCellGroup();
+ ScFormulaCell* pChildTopCell = mxGroupChild ? mxGroupChild->mpTopCell : *itCell;
+
+ // Check if itCell is already in path.
+ // If yes use a cycle guard to mark all elements of the cycle
+ // and return false
+ if (bThreadingDepEval && pChildTopCell->GetSeenInPath())
+ {
+ ScFormulaGroupCycleCheckGuard aCycleCheckGuard(rRecursionHelper, pChildTopCell);
+ bAllowThreading = false;
+ return;
+ }
+
+ if (bSkipRunning && (*itCell)->IsRunning())
+ {
+ ++itCell;
+ nCellIdx += 1;
+ nRow += 1;
+ nRowsToRead -= 1;
+ continue;
+ }
+
+ if (mxGroupChild)
+ {
+ // It is a Formula-group, evaluate the necessary parts of it (spans).
+ const SCROW nFGStartOffset = (*itCell)->aPos.Row() - pChildTopCell->aPos.Row();
+ const SCROW nFGEndOffset = std::min(nFGStartOffset + static_cast<SCROW>(nRowsToRead) - 1, mxGroupChild->mnLength - 1);
+ assert(nFGEndOffset >= nFGStartOffset);
+ const SCROW nSpanLen = nFGEndOffset - nFGStartOffset + 1;
+ // The (main) span required to be evaluated is [nFGStartOffset, nFGEndOffset], but this span may contain
+ // non-dirty cells, so split this into sets of completely-dirty spans and try evaluate each of them in grouped-style.
+
+ bool bAnyDirtyInSpan = lcl_InterpretSpan(itCell, nFGStartOffset, nFGEndOffset, mxGroup, bAllowThreading, rDoc);
+ if (!bAllowThreading)
+ return;
+ // itCell will now point to cell just after the end of span [nFGStartOffset, nFGEndOffset].
+ bIsDirty = bIsDirty || bAnyDirtyInSpan;
+
+ // update the counters by nSpanLen.
+ // itCell already got updated.
+ nCellIdx += nSpanLen;
+ nRow += nSpanLen;
+ nRowsToRead -= nSpanLen;
+ }
+ else
+ {
+ // No formula-group here.
+ bool bDirtyFlag = false;
+ if( (*itCell)->NeedsInterpret())
+ {
+ bDirtyFlag = true;
+ (*itCell)->Interpret();
+ if ((*itCell)->NeedsInterpret())
+ {
+ SAL_WARN("sc.core.formulagroup", "Internal error, cell " << (*itCell)->aPos
+ << " failed running Interpret(), not allowing threading");
+ bAllowThreading = false;
+ return;
+ }
+ }
+ bIsDirty = bIsDirty || bDirtyFlag;
+
+ // child cell's Interpret could result in calling dependency calc
+ // and that could detect a cycle involving mxGroup
+ // and do early exit in that case.
+ // OR
+ // we are trying multi-formula-group-threading, but found intergroup dependency.
+ if (bThreadingDepEval && mxGroup &&
+ (mxGroup->mbPartOfCycle || !rRecursionHelper.AreGroupsIndependent()))
+ {
+ // Set itCell as dirty as itCell may be interpreted in InterpretTail()
+ (*itCell)->SetDirtyVar();
+ bAllowThreading = false;
+ return;
+ }
+
+ // update the counters by 1.
+ nCellIdx += 1;
+ nRow += 1;
+ nRowsToRead -= 1;
+ ++itCell;
+ }
+ }
+ break;
+ }
+ default:
+ // Skip this block.
+ nRow += it->size - nOffset;
+ continue;
+ }
+ }
+
+ if (bThreadingDepEval)
+ bAllowThreading = true;
+
+}
+
+// Returns true if at least one FC is dirty.
+bool ScColumn::EnsureFormulaCellResults( SCROW nRow1, SCROW nRow2, bool bSkipRunning )
+{
+ if (!GetDoc().ValidRow(nRow1) || !GetDoc().ValidRow(nRow2) || nRow1 > nRow2)
+ return false;
+
+ if (!HasFormulaCell(nRow1, nRow2))
+ return false;
+
+ bool bAnyDirty = false, bTmp = false;
+ lcl_EvalDirty(maCells, nRow1, nRow2, GetDoc(), nullptr, false, bSkipRunning, bAnyDirty, bTmp);
+ return bAnyDirty;
+}
+
+bool ScColumn::HandleRefArrayForParallelism( SCROW nRow1, SCROW nRow2, const ScFormulaCellGroupRef& mxGroup )
+{
+ if (nRow1 > nRow2)
+ return false;
+
+ bool bAllowThreading = true, bTmp = false;
+ lcl_EvalDirty(maCells, nRow1, nRow2, GetDoc(), mxGroup, true, false, bTmp, bAllowThreading);
+
+ return bAllowThreading;
+}
+
+namespace {
+
+class StoreToCacheFunc
+{
+ SvStream& mrStrm;
+public:
+
+ StoreToCacheFunc(SvStream& rStrm):
+ mrStrm(rStrm)
+ {
+ }
+
+ void operator() ( const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize )
+ {
+ SCROW nStartRow = node.position + nOffset;
+ mrStrm.WriteUInt64(nStartRow);
+ mrStrm.WriteUInt64(nDataSize);
+ switch (node.type)
+ {
+ case sc::element_type_empty:
+ {
+ mrStrm.WriteUChar(0);
+ }
+ break;
+ case sc::element_type_numeric:
+ {
+ mrStrm.WriteUChar(1);
+ sc::numeric_block::const_iterator it = sc::numeric_block::begin(*node.data);
+ std::advance(it, nOffset);
+ sc::numeric_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+
+ for (; it != itEnd; ++it)
+ {
+ mrStrm.WriteDouble(*it);
+ }
+ }
+ break;
+ case sc::element_type_string:
+ {
+ mrStrm.WriteUChar(2);
+ sc::string_block::const_iterator it = sc::string_block::begin(*node.data);
+ std::advance(it, nOffset);
+ sc::string_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+
+ for (; it != itEnd; ++it)
+ {
+ OString aStr = OUStringToOString(it->getString(), RTL_TEXTENCODING_UTF8);
+ sal_Int32 nStrLength = aStr.getLength();
+ mrStrm.WriteInt32(nStrLength);
+ mrStrm.WriteOString(aStr);
+ }
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ mrStrm.WriteUChar(3);
+ sc::formula_block::const_iterator it = sc::formula_block::begin(*node.data);
+ std::advance(it, nOffset);
+ sc::formula_block::const_iterator itEnd = it;
+ std::advance(itEnd, nDataSize);
+
+ for (; it != itEnd; /* incrementing through std::advance*/)
+ {
+ const ScFormulaCell* pCell = *it;
+ OUString aFormula = pCell->GetFormula(formula::FormulaGrammar::GRAM_ENGLISH_XL_R1C1);
+ const auto& xCellGroup = pCell->GetCellGroup();
+ sal_uInt64 nGroupLength = 0;
+ if (xCellGroup)
+ {
+ nGroupLength = xCellGroup->mnLength;
+ }
+ else
+ {
+ nGroupLength = 1;
+ }
+ mrStrm.WriteUInt64(nGroupLength);
+ mrStrm.WriteInt32(aFormula.getLength());
+ mrStrm.WriteOString(OUStringToOString(aFormula, RTL_TEXTENCODING_UTF8));
+
+ // incrementing the iterator
+ std::advance(it, nGroupLength);
+ }
+ }
+ break;
+ }
+ }
+};
+
+}
+
+void ScColumn::StoreToCache(SvStream& rStrm) const
+{
+ rStrm.WriteUInt64(nCol);
+ SCROW nLastRow = GetLastDataPos();
+ rStrm.WriteUInt64(nLastRow + 1); // the rows are zero based
+
+ StoreToCacheFunc aFunc(rStrm);
+ sc::ParseBlock(maCells.begin(), maCells, aFunc, SCROW(0), nLastRow);
+}
+
+void ScColumn::RestoreFromCache(SvStream& rStrm)
+{
+ sal_uInt64 nStoredCol = 0;
+ rStrm.ReadUInt64(nStoredCol);
+ if (nStoredCol != static_cast<sal_uInt64>(nCol))
+ throw std::exception();
+
+ sal_uInt64 nLastRow = 0;
+ rStrm.ReadUInt64(nLastRow);
+ sal_uInt64 nReadRow = 0;
+ ScDocument& rDocument = GetDoc();
+ while (nReadRow < nLastRow)
+ {
+ sal_uInt64 nStartRow = 0;
+ sal_uInt64 nDataSize = 0;
+ rStrm.ReadUInt64(nStartRow);
+ rStrm.ReadUInt64(nDataSize);
+ sal_uInt8 nType = 0;
+ rStrm.ReadUChar(nType);
+ switch (nType)
+ {
+ case 0:
+ // nothing to do
+ maCells.set_empty(nStartRow, nDataSize);
+ break;
+ case 1:
+ {
+ // nDataSize double values
+ std::vector<double> aValues(nDataSize);
+ for (auto& rValue : aValues)
+ {
+ rStrm.ReadDouble(rValue);
+ }
+ maCells.set(nStartRow, aValues.begin(), aValues.end());
+ }
+ break;
+ case 2:
+ {
+ std::vector<svl::SharedString> aStrings(nDataSize);
+ svl::SharedStringPool& rPool = rDocument.GetSharedStringPool();
+ for (auto& rString : aStrings)
+ {
+ sal_Int32 nStrLength = 0;
+ rStrm.ReadInt32(nStrLength);
+ std::unique_ptr<char[]> pStr(new char[nStrLength]);
+ rStrm.ReadBytes(pStr.get(), nStrLength);
+ OString aOStr(pStr.get(), nStrLength);
+ OUString aStr = OStringToOUString(aOStr, RTL_TEXTENCODING_UTF8);
+ rString = rPool.intern(aStr);
+ }
+ maCells.set(nStartRow, aStrings.begin(), aStrings.end());
+
+ }
+ break;
+ case 3:
+ {
+ std::vector<ScFormulaCell*> aFormulaCells(nDataSize);
+
+ ScAddress aAddr(nCol, nStartRow, nTab);
+ const formula::FormulaGrammar::Grammar eGrammar = formula::FormulaGrammar::GRAM_ENGLISH_XL_R1C1;
+ for (SCROW nRow = 0; nRow < static_cast<SCROW>(nDataSize);)
+ {
+ sal_uInt64 nFormulaGroupSize = 0;
+ rStrm.ReadUInt64(nFormulaGroupSize);
+ sal_Int32 nStrLength = 0;
+ rStrm.ReadInt32(nStrLength);
+ std::unique_ptr<char[]> pStr(new char[nStrLength]);
+ rStrm.ReadBytes(pStr.get(), nStrLength);
+ OString aOStr(pStr.get(), nStrLength);
+ OUString aStr = OStringToOUString(aOStr, RTL_TEXTENCODING_UTF8);
+ for (sal_uInt64 i = 0; i < nFormulaGroupSize; ++i)
+ {
+ aFormulaCells[nRow + i] = new ScFormulaCell(rDocument, aAddr, aStr, eGrammar);
+ aAddr.IncRow();
+ }
+
+ nRow += nFormulaGroupSize;
+ }
+
+ maCells.set(nStartRow, aFormulaCells.begin(), aFormulaCells.end());
+ }
+ break;
+ }
+
+ nReadRow += nDataSize;
+ }
+}
+
+void ScColumn::CheckIntegrity() const
+{
+ const ScColumn* pColTest = maCells.event_handler().getColumn();
+
+ if (pColTest != this)
+ {
+ std::ostringstream os;
+ os << "cell store's event handler references wrong column instance (this=" << this
+ << "; stored=" << pColTest << ")";
+ throw std::runtime_error(os.str());
+ }
+
+ size_t nCount = std::count_if(maCells.cbegin(), maCells.cend(),
+ [](const auto& blk) { return blk.type == sc::element_type_formula; }
+ );
+
+ if (mnBlkCountFormula != nCount)
+ {
+ std::ostringstream os;
+ os << "incorrect cached formula block count (expected=" << nCount << "; actual="
+ << mnBlkCountFormula << ")";
+ throw std::runtime_error(os.str());
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/columniterator.cxx b/sc/source/core/data/columniterator.cxx
new file mode 100644
index 000000000..cec8f7a20
--- /dev/null
+++ b/sc/source/core/data/columniterator.cxx
@@ -0,0 +1,209 @@
+/* -*- 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 <columniterator.hxx>
+#include <column.hxx>
+#include <document.hxx>
+#include <table.hxx>
+
+#include <osl/diagnose.h>
+
+ScColumnTextWidthIterator::ScColumnTextWidthIterator(const ScDocument& rDoc, ScColumn& rCol, SCROW nStartRow, SCROW nEndRow) :
+ mnEnd(static_cast<size_t>(nEndRow)),
+ mnCurPos(0)
+{
+ miBlockCur = rCol.maCellTextAttrs.begin();
+ miBlockEnd = rCol.maCellTextAttrs.end();
+ init(rDoc, nStartRow, nEndRow);
+}
+
+ScColumnTextWidthIterator::ScColumnTextWidthIterator(const ScDocument& rDoc, const ScAddress& rStartPos, SCROW nEndRow) :
+ mnEnd(static_cast<size_t>(nEndRow)),
+ mnCurPos(0)
+{
+ auto & rCellTextAttrs = rDoc.maTabs[rStartPos.Tab()]->aCol[rStartPos.Col()].maCellTextAttrs;
+ miBlockCur = rCellTextAttrs.begin();
+ miBlockEnd = rCellTextAttrs.end();
+ init(rDoc, rStartPos.Row(), nEndRow);
+}
+
+void ScColumnTextWidthIterator::next()
+{
+ ++miDataCur;
+ ++mnCurPos;
+
+ if (miDataCur != miDataEnd)
+ {
+ // Still in the same block. We're good.
+ checkEndRow();
+ return;
+ }
+
+ // Move to the next block.
+ for (++miBlockCur; miBlockCur != miBlockEnd; ++miBlockCur)
+ {
+ if (miBlockCur->type != sc::element_type_celltextattr)
+ {
+ // We don't iterator over this block.
+ mnCurPos += miBlockCur->size;
+ continue;
+ }
+
+ getDataIterators(0);
+ checkEndRow();
+ return;
+ }
+
+ // Reached the end.
+ assert(miBlockCur == miBlockEnd);
+}
+
+bool ScColumnTextWidthIterator::hasCell() const
+{
+ return miBlockCur != miBlockEnd;
+}
+
+SCROW ScColumnTextWidthIterator::getPos() const
+{
+ assert(miBlockCur != miBlockEnd && miDataCur != miDataEnd);
+ return static_cast<SCROW>(mnCurPos);
+}
+
+sal_uInt16 ScColumnTextWidthIterator::getValue() const
+{
+ assert(miBlockCur != miBlockEnd && miDataCur != miDataEnd);
+ return miDataCur->mnTextWidth;
+}
+
+void ScColumnTextWidthIterator::setValue(sal_uInt16 nVal)
+{
+ assert(miBlockCur != miBlockEnd && miDataCur != miDataEnd);
+ miDataCur->mnTextWidth = nVal;
+}
+
+void ScColumnTextWidthIterator::init(const ScDocument& rDoc, SCROW nStartRow, SCROW nEndRow)
+{
+ if (!rDoc.ValidRow(nStartRow) || !rDoc.ValidRow(nEndRow))
+ miBlockCur = miBlockEnd;
+
+ size_t nStart = static_cast<size_t>(nStartRow);
+
+ // Locate the start row position.
+ size_t nBlockStart = 0, nBlockEnd = 0;
+ for (; miBlockCur != miBlockEnd; ++miBlockCur, nBlockStart = nBlockEnd)
+ {
+ nBlockEnd = nBlockStart + miBlockCur->size; // non-inclusive end point.
+ if (nBlockStart <= nStart && nStart < nBlockEnd)
+ {
+ // Initial block is found!
+ break;
+ }
+ }
+
+ if (miBlockCur == miBlockEnd)
+ // Initial block not found for whatever reason... Bail out.
+ return;
+
+ // Locate the initial row position within this block.
+ if (miBlockCur->type == sc::element_type_celltextattr)
+ {
+ // This block stores text widths for non-empty cells.
+ size_t nOffsetInBlock = nStart - nBlockStart;
+ mnCurPos = nStart;
+ getDataIterators(nOffsetInBlock);
+ checkEndRow();
+ return;
+ }
+
+ // Current block is not of ushort type. Skip to the next block.
+ nBlockStart = nBlockEnd;
+ ++miBlockCur;
+
+ // Look for the first ushort block.
+ for (; miBlockCur != miBlockEnd; ++miBlockCur, nBlockStart = nBlockEnd)
+ {
+ nBlockEnd = nBlockStart + miBlockCur->size; // non-inclusive end point.
+ if (miBlockCur->type != sc::element_type_celltextattr)
+ continue;
+
+ // Found!
+ mnCurPos = nBlockStart;
+ getDataIterators(0);
+ checkEndRow();
+ return;
+ }
+
+ // Not found.
+ assert(miBlockCur == miBlockEnd);
+}
+
+void ScColumnTextWidthIterator::getDataIterators(size_t nOffsetInBlock)
+{
+ OSL_ENSURE(miBlockCur != miBlockEnd, "block is at end position");
+#if 0
+ // Does not compile
+ OSL_ENSURE(miBlockCur->type == sc::celltextattr_block,
+ "wrong block type - unsigned short block expected.");
+#endif
+ miDataCur = sc::celltextattr_block::begin(*miBlockCur->data);
+ miDataEnd = sc::celltextattr_block::end(*miBlockCur->data);
+
+ std::advance(miDataCur, nOffsetInBlock);
+}
+
+void ScColumnTextWidthIterator::checkEndRow()
+{
+ if (mnCurPos <= mnEnd)
+ // We're still good.
+ return;
+
+ // We're below the end position. End the iteration.
+ miBlockCur = miBlockEnd;
+}
+
+namespace sc {
+
+ColumnIterator::ColumnIterator( const CellStoreType& rCells, SCROW nRow1, SCROW nRow2 ) :
+ maPos(rCells.position(nRow1)),
+ maPosEnd(rCells.position(maPos.first, nRow2)),
+ mbComplete(false)
+{
+}
+
+void ColumnIterator::next()
+{
+ if ( maPos == maPosEnd)
+ mbComplete = true;
+ else
+ maPos = CellStoreType::next_position(maPos);
+}
+
+SCROW ColumnIterator::getRow() const
+{
+ return CellStoreType::logical_position(maPos);
+}
+
+bool ColumnIterator::hasCell() const
+{
+ return !mbComplete;
+}
+
+mdds::mtv::element_t ColumnIterator::getType() const
+{
+ return maPos.first->type;
+}
+
+ScRefCellValue ColumnIterator::getCell() const
+{
+ return toRefCell(maPos.first, maPos.second);
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/columnset.cxx b/sc/source/core/data/columnset.cxx
new file mode 100644
index 000000000..5f91dd8c6
--- /dev/null
+++ b/sc/source/core/data/columnset.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/.
+ */
+
+#include <columnset.hxx>
+#include <algorithm>
+
+namespace sc {
+
+void ColumnSet::set(SCTAB nTab, SCCOL nCol)
+{
+ TabsType::iterator itTab = maTabs.find(nTab);
+ if (itTab == maTabs.end())
+ {
+ std::pair<TabsType::iterator,bool> r =
+ maTabs.emplace(nTab, ColsType());
+
+ if (!r.second)
+ // insertion failed.
+ return;
+
+ itTab = r.first;
+ }
+
+ ColsType& rCols = itTab->second;
+ rCols.insert(nCol);
+}
+
+void ColumnSet::getColumns(SCTAB nTab, std::vector<SCCOL>& rCols) const
+{
+ std::vector<SCCOL> aCols;
+ TabsType::const_iterator itTab = maTabs.find(nTab);
+ if (itTab == maTabs.end())
+ {
+ rCols.swap(aCols); // empty it.
+ return;
+ }
+
+ const ColsType& rTabCols = itTab->second;
+ aCols.assign(rTabCols.begin(), rTabCols.end());
+
+ // Sort and remove duplicates.
+ std::sort(aCols.begin(), aCols.end());
+ std::vector<SCCOL>::iterator itCol = std::unique(aCols.begin(), aCols.end());
+ aCols.erase(itCol, aCols.end());
+
+ rCols.swap(aCols);
+}
+
+bool ColumnSet::hasTab(SCTAB nTab) const
+{
+ return maTabs.find(nTab) != maTabs.end();
+}
+
+bool ColumnSet::empty() const
+{
+ return maTabs.empty();
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/columnspanset.cxx b/sc/source/core/data/columnspanset.cxx
new file mode 100644
index 000000000..eb09ea26b
--- /dev/null
+++ b/sc/source/core/data/columnspanset.cxx
@@ -0,0 +1,369 @@
+/* -*- 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 <columnspanset.hxx>
+#include <column.hxx>
+#include <table.hxx>
+#include <document.hxx>
+#include <mtvfunctions.hxx>
+#include <markdata.hxx>
+#include <rangelst.hxx>
+#include <fstalgorithm.hxx>
+
+#include <algorithm>
+#include <memory>
+
+#include <o3tl/safeint.hxx>
+
+namespace sc {
+
+namespace {
+
+class ColumnNonEmptyRangesScanner
+{
+ ColumnSpanSet::ColumnSpansType& mrRanges;
+ bool mbVal;
+public:
+ ColumnNonEmptyRangesScanner(ColumnSpanSet::ColumnSpansType& rRanges, bool bVal) :
+ mrRanges(rRanges), mbVal(bVal) {}
+
+ void operator() (const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize)
+ {
+ if (node.type == sc::element_type_empty)
+ return;
+
+ size_t nRow = node.position + nOffset;
+ size_t nEndRow = nRow + nDataSize; // Last row of current block plus 1
+ mrRanges.insert_back(nRow, nEndRow, mbVal);
+ }
+};
+
+}
+
+RowSpan::RowSpan(SCROW nRow1, SCROW nRow2) : mnRow1(nRow1), mnRow2(nRow2) {}
+
+ColRowSpan::ColRowSpan(SCCOLROW nStart, SCCOLROW nEnd) : mnStart(nStart), mnEnd(nEnd) {}
+
+ColumnSpanSet::ColumnType::ColumnType(SCROW nStart, SCROW nEnd, bool bInit) :
+ maSpans(nStart, nEnd+1, bInit), miPos(maSpans.begin()) {}
+
+ColumnSpanSet::Action::~Action() {}
+void ColumnSpanSet::Action::startColumn(SCTAB /*nTab*/, SCCOL /*nCol*/) {}
+
+ColumnSpanSet::ColumnAction::~ColumnAction() {}
+
+ColumnSpanSet::ColumnSpanSet() {}
+
+ColumnSpanSet::~ColumnSpanSet()
+{
+}
+
+ColumnSpanSet::ColumnType& ColumnSpanSet::getColumn(const ScDocument& rDoc, SCTAB nTab, SCCOL nCol)
+{
+ if (o3tl::make_unsigned(nTab) >= maTables.size())
+ maTables.resize(nTab+1);
+
+ TableType& rTab = maTables[nTab];
+ if (o3tl::make_unsigned(nCol) >= rTab.size())
+ rTab.resize(nCol+1);
+
+ if (!rTab[nCol])
+ rTab[nCol].emplace(0, rDoc.MaxRow(), /*bInit*/false);
+
+ return *rTab[nCol];
+}
+
+void ColumnSpanSet::set(const ScDocument& rDoc, SCTAB nTab, SCCOL nCol, SCROW nRow, bool bVal)
+{
+ if (!ValidTab(nTab) || !rDoc.ValidCol(nCol) || !rDoc.ValidRow(nRow))
+ return;
+
+ ColumnType& rCol = getColumn(rDoc, nTab, nCol);
+ rCol.miPos = rCol.maSpans.insert(rCol.miPos, nRow, nRow+1, bVal).first;
+}
+
+void ColumnSpanSet::set(const ScDocument& rDoc, SCTAB nTab, SCCOL nCol, SCROW nRow1, SCROW nRow2, bool bVal)
+{
+ if (!ValidTab(nTab) || !rDoc.ValidCol(nCol) || !rDoc.ValidRow(nRow1) || !rDoc.ValidRow(nRow2))
+ return;
+
+ ColumnType& rCol = getColumn(rDoc, nTab, nCol);
+ rCol.miPos = rCol.maSpans.insert(rCol.miPos, nRow1, nRow2+1, bVal).first;
+}
+
+void ColumnSpanSet::set(const ScDocument& rDoc, const ScRange& rRange, bool bVal)
+{
+ for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab)
+ {
+ for (SCCOL nCol = rRange.aStart.Col(); nCol <= rRange.aEnd.Col(); ++nCol)
+ {
+ ColumnType& rCol = getColumn(rDoc, nTab, nCol);
+ rCol.miPos = rCol.maSpans.insert(rCol.miPos, rRange.aStart.Row(), rRange.aEnd.Row()+1, bVal).first;
+ }
+ }
+}
+
+void ColumnSpanSet::set( const ScDocument& rDoc, SCTAB nTab, SCCOL nCol, const SingleColumnSpanSet& rSingleSet, bool bVal )
+{
+ SingleColumnSpanSet::SpansType aSpans;
+ rSingleSet.getSpans(aSpans);
+ for (const auto& rSpan : aSpans)
+ set(rDoc, nTab, nCol, rSpan.mnRow1, rSpan.mnRow2, bVal);
+}
+
+void ColumnSpanSet::scan(
+ const ScDocument& rDoc, SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, bool bVal)
+{
+ if (!rDoc.ValidColRow(nCol1, nRow1) || !rDoc.ValidColRow(nCol2, nRow2))
+ return;
+
+ if (nCol1 > nCol2 || nRow1 > nRow2)
+ return;
+
+ const ScTable* pTab = rDoc.FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ nCol2 = pTab->ClampToAllocatedColumns(nCol2);
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ {
+ ColumnType& rCol = getColumn(rDoc, nTab, nCol);
+
+ const CellStoreType& rSrcCells = pTab->aCol[nCol].maCells;
+
+ if( nRow1 > pTab->aCol[nCol].GetLastDataPos())
+ continue;
+
+ ColumnNonEmptyRangesScanner aScanner(rCol.maSpans, bVal);
+ ParseBlock(rSrcCells.begin(), rSrcCells, aScanner, nRow1, nRow2);
+ }
+}
+
+void ColumnSpanSet::executeAction(ScDocument& rDoc, Action& ac) const
+{
+ for (size_t nTab = 0; nTab < maTables.size(); ++nTab)
+ {
+ if (maTables[nTab].empty())
+ continue;
+
+ ScTable* pTab = rDoc.FetchTable(nTab);
+ if (!pTab)
+ continue;
+
+ const TableType& rTab = maTables[nTab];
+ for (SCCOL nCol = 0; nCol < static_cast<SCCOL>(rTab.size()); ++nCol)
+ {
+ if (!rTab[nCol])
+ continue;
+ if (nCol >= pTab->GetAllocatedColumnsCount())
+ break;
+
+ ac.startColumn(nTab, nCol);
+ const ColumnType& rCol = *rTab[nCol];
+ ColumnSpansType::const_iterator it = rCol.maSpans.begin(), itEnd = rCol.maSpans.end();
+ SCROW nRow1, nRow2;
+ nRow1 = it->first;
+ bool bVal = it->second;
+ for (++it; it != itEnd; ++it)
+ {
+ nRow2 = it->first-1;
+ ac.execute(ScAddress(nCol, nRow1, nTab), nRow2-nRow1+1, bVal);
+
+ nRow1 = nRow2+1; // for the next iteration.
+ bVal = it->second;
+ }
+ }
+ }
+}
+
+void ColumnSpanSet::executeColumnAction(ScDocument& rDoc, ColumnAction& ac) const
+{
+ for (size_t nTab = 0; nTab < maTables.size(); ++nTab)
+ {
+ if (maTables[nTab].empty())
+ continue;
+
+ ScTable* pTab = rDoc.FetchTable(nTab);
+ if (!pTab)
+ continue;
+
+ const TableType& rTab = maTables[nTab];
+ for (SCCOL nCol = 0; nCol < static_cast<SCCOL>(rTab.size()); ++nCol)
+ {
+ if (!rTab[nCol])
+ continue;
+ if (nCol >= pTab->GetAllocatedColumnsCount())
+ break;
+
+ ScColumn& rColumn = pTab->aCol[nCol];
+ ac.startColumn(&rColumn);
+ const ColumnType& rCol = *rTab[nCol];
+ ColumnSpansType::const_iterator it = rCol.maSpans.begin(), itEnd = rCol.maSpans.end();
+ SCROW nRow1, nRow2;
+ nRow1 = it->first;
+ bool bVal = it->second;
+ for (++it; it != itEnd; ++it)
+ {
+ nRow2 = it->first-1;
+ ac.execute(nRow1, nRow2, bVal);
+
+ nRow1 = nRow2+1; // for the next iteration.
+ bVal = it->second;
+ }
+ }
+ }
+}
+
+namespace {
+
+class NonEmptyRangesScanner
+{
+ SingleColumnSpanSet::ColumnSpansType& mrRanges;
+public:
+ explicit NonEmptyRangesScanner(SingleColumnSpanSet::ColumnSpansType& rRanges) : mrRanges(rRanges) {}
+
+ void operator() (const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize)
+ {
+ if (node.type == sc::element_type_empty)
+ return;
+
+ size_t nRow = node.position + nOffset;
+ size_t nEndRow = nRow + nDataSize; // Last row of current block plus 1
+ mrRanges.insert_back(nRow, nEndRow, true);
+ }
+};
+
+}
+
+SingleColumnSpanSet::SingleColumnSpanSet(ScSheetLimits const & rSheetLimits)
+ : mrSheetLimits(rSheetLimits),
+ maSpans(0, rSheetLimits.GetMaxRowCount(), false) {}
+
+void SingleColumnSpanSet::scan(const ScColumn& rColumn)
+{
+ const CellStoreType& rCells = rColumn.maCells;
+ SCROW nCurRow = 0;
+ for (const auto& rCell : rCells)
+ {
+ SCROW nEndRow = nCurRow + rCell.size; // Last row of current block plus 1.
+ if (rCell.type != sc::element_type_empty)
+ maSpans.insert_back(nCurRow, nEndRow, true);
+
+ nCurRow = nEndRow;
+ }
+}
+
+void SingleColumnSpanSet::scan(const ScColumn& rColumn, SCROW nStart, SCROW nEnd)
+{
+ if( nStart > rColumn.GetLastDataPos())
+ return;
+ const CellStoreType& rCells = rColumn.maCells;
+ NonEmptyRangesScanner aScanner(maSpans);
+ sc::ParseBlock(rCells.begin(), rCells, aScanner, nStart, nEnd);
+}
+
+void SingleColumnSpanSet::scan(
+ ColumnBlockConstPosition& rBlockPos, const ScColumn& rColumn, SCROW nStart, SCROW nEnd)
+{
+ if( nStart > rColumn.GetLastDataPos())
+ return;
+ const CellStoreType& rCells = rColumn.maCells;
+ NonEmptyRangesScanner aScanner(maSpans);
+ rBlockPos.miCellPos = sc::ParseBlock(rBlockPos.miCellPos, rCells, aScanner, nStart, nEnd);
+}
+
+void SingleColumnSpanSet::scan(const ScMarkData& rMark, SCTAB nTab, SCCOL nCol)
+{
+ if (!rMark.GetTableSelect(nTab))
+ // This table is not selected. Nothing to scan.
+ return;
+
+ ScRangeList aRanges = rMark.GetMarkedRangesForTab(nTab);
+ scan(aRanges, nTab, nCol);
+}
+
+void SingleColumnSpanSet::scan(const ScRangeList& rRanges, SCTAB nTab, SCCOL nCol)
+{
+ for (size_t i = 0, n = rRanges.size(); i < n; ++i)
+ {
+ const ScRange & rRange = rRanges[i];
+ if (nTab < rRange.aStart.Tab() || rRange.aEnd.Tab() < nTab)
+ continue;
+
+ if (nCol < rRange.aStart.Col() || rRange.aEnd.Col() < nCol)
+ // This column is not in this range. Skip it.
+ continue;
+
+ maSpans.insert_back(rRange.aStart.Row(), rRange.aEnd.Row()+1, true);
+ }
+}
+
+void SingleColumnSpanSet::set(SCROW nRow1, SCROW nRow2, bool bVal)
+{
+ maSpans.insert_back(nRow1, nRow2+1, bVal);
+}
+
+void SingleColumnSpanSet::getRows(std::vector<SCROW> &rRows) const
+{
+ std::vector<SCROW> aRows;
+
+ SpansType aRanges;
+ getSpans(aRanges);
+ for (const auto& rRange : aRanges)
+ {
+ for (SCROW nRow = rRange.mnRow1; nRow <= rRange.mnRow2; ++nRow)
+ aRows.push_back(nRow);
+ }
+
+ rRows.swap(aRows);
+}
+
+void SingleColumnSpanSet::getSpans(SpansType& rSpans) const
+{
+ SpansType aSpans = toSpanArray<SCROW,RowSpan>(maSpans);
+ rSpans.swap(aSpans);
+}
+
+void SingleColumnSpanSet::swap( SingleColumnSpanSet& r )
+{
+ maSpans.swap(r.maSpans);
+}
+
+bool SingleColumnSpanSet::empty() const
+{
+ // Empty if there's only the 0..rDoc.MaxRow() span with false.
+ ColumnSpansType::const_iterator it = maSpans.begin();
+ return (it->first == 0) && !(it->second) && (++it != maSpans.end()) && (it->first == mrSheetLimits.GetMaxRowCount());
+}
+
+
+void RangeColumnSpanSet::executeColumnAction(ScDocument& rDoc, sc::ColumnSpanSet::ColumnAction& ac) const
+{
+ for (SCTAB nTab = range.aStart.Tab(); nTab <= range.aEnd.Tab(); ++nTab)
+ {
+ ScTable* pTab = rDoc.FetchTable(nTab);
+ if (!pTab)
+ continue;
+
+ SCCOL nEndCol = pTab->ClampToAllocatedColumns(range.aEnd.Col());
+ for (SCCOL nCol = range.aStart.Col(); nCol <= nEndCol; ++nCol)
+ {
+ if (!rDoc.ValidCol(nCol))
+ break;
+
+ ScColumn& rColumn = pTab->aCol[nCol];
+ ac.startColumn(&rColumn);
+ ac.execute( range.aStart.Row(), range.aEnd.Row(), true );
+ }
+ }
+}
+
+} // namespace sc
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/compressedarray.cxx b/sc/source/core/data/compressedarray.cxx
new file mode 100644
index 000000000..793b43b43
--- /dev/null
+++ b/sc/source/core/data/compressedarray.cxx
@@ -0,0 +1,418 @@
+/* -*- 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 <compressedarray.hxx>
+#include <global.hxx>
+
+template< typename A, typename D >
+ScCompressedArray<A,D>::ScCompressedArray( A nMaxAccessP, const D& rValue )
+ : nCount(1)
+ , nLimit(1)
+ , pData( new DataEntry[1])
+ , nMaxAccess( nMaxAccessP)
+{
+ pData[0].aValue = rValue;
+ pData[0].nEnd = nMaxAccess;
+}
+
+template< typename A, typename D >
+size_t ScCompressedArray<A,D>::Search( A nAccess ) const
+{
+ if (nAccess == 0)
+ return 0;
+
+ tools::Long nLo = 0;
+ tools::Long nHi = static_cast<tools::Long>(nCount) - 1;
+ tools::Long nStart = 0;
+ tools::Long i = 0;
+ bool bFound = (nCount == 1);
+ while (!bFound && nLo <= nHi)
+ {
+ i = (nLo + nHi) / 2;
+ if (i > 0)
+ nStart = static_cast<tools::Long>(pData[i - 1].nEnd);
+ else
+ nStart = -1;
+ tools::Long nEnd = static_cast<tools::Long>(pData[i].nEnd);
+ if (nEnd < static_cast<tools::Long>(nAccess))
+ nLo = ++i;
+ else
+ if (nStart >= static_cast<tools::Long>(nAccess))
+ nHi = --i;
+ else
+ bFound = true;
+ }
+ return (bFound ? static_cast<size_t>(i) : (nAccess < 0 ? 0 : nCount-1));
+}
+
+template< typename A, typename D >
+void ScCompressedArray<A,D>::SetValue( A nStart, A nEnd, const D& rValue )
+{
+ if (!(0 <= nStart && nStart <= nMaxAccess && 0 <= nEnd && nEnd <= nMaxAccess
+ && nStart <= nEnd))
+ return;
+
+ if ((nStart == 0) && (nEnd == nMaxAccess))
+ Reset( rValue);
+ else
+ {
+ // Create a temporary copy in case we got a reference passed that
+ // points to a part of the array to be reallocated.
+ D aNewVal( rValue);
+ size_t nNeeded = nCount + 2;
+ if (nLimit < nNeeded)
+ {
+ nLimit *= 1.5;
+ if (nLimit < nNeeded)
+ nLimit = nNeeded;
+ std::unique_ptr<DataEntry[]> pNewData(new DataEntry[nLimit]);
+ memcpy( pNewData.get(), pData.get(), nCount*sizeof(DataEntry));
+ pData = std::move(pNewData);
+ }
+
+ size_t ni; // number of leading entries
+ size_t nInsert; // insert position (nMaxAccess+1 := no insert)
+ bool bCombined = false;
+ bool bSplit = false;
+ if (nStart > 0)
+ {
+ // skip leading
+ ni = this->Search( nStart);
+
+ nInsert = nMaxAccess+1;
+ if (!(pData[ni].aValue == aNewVal))
+ {
+ if (ni == 0 || (pData[ni-1].nEnd < nStart - 1))
+ { // may be a split or a simple insert or just a shrink,
+ // row adjustment is done further down
+ if (pData[ni].nEnd > nEnd)
+ bSplit = true;
+ ni++;
+ nInsert = ni;
+ }
+ else if (ni > 0 && pData[ni-1].nEnd == nStart - 1)
+ nInsert = ni;
+ }
+ if (ni > 0 && pData[ni-1].aValue == aNewVal)
+ { // combine
+ pData[ni-1].nEnd = nEnd;
+ nInsert = nMaxAccess+1;
+ bCombined = true;
+ }
+ }
+ else
+ {
+ nInsert = 0;
+ ni = 0;
+ }
+
+ size_t nj = ni; // stop position of range to replace
+ while (nj < nCount && pData[nj].nEnd <= nEnd)
+ nj++;
+ if (!bSplit)
+ {
+ if (nj < nCount && pData[nj].aValue == aNewVal)
+ { // combine
+ if (ni > 0)
+ {
+ if (pData[ni-1].aValue == aNewVal)
+ { // adjacent entries
+ pData[ni-1].nEnd = pData[nj].nEnd;
+ nj++;
+ }
+ else if (ni == nInsert)
+ pData[ni-1].nEnd = nStart - 1; // shrink
+ }
+ nInsert = nMaxAccess+1;
+ bCombined = true;
+ }
+ else if (ni > 0 && ni == nInsert)
+ pData[ni-1].nEnd = nStart - 1; // shrink
+ }
+ if (ni < nj)
+ { // remove middle entries
+ if (!bCombined)
+ { // replace one entry
+ pData[ni].nEnd = nEnd;
+ pData[ni].aValue = aNewVal;
+ ni++;
+ nInsert = nMaxAccess+1;
+ }
+ if (ni < nj)
+ { // remove entries
+ memmove( pData.get() + ni, pData.get() + nj,
+ (nCount - nj) * sizeof(DataEntry));
+ nCount -= nj - ni;
+ }
+ }
+
+ if (nInsert < static_cast<size_t>(nMaxAccess+1))
+ { // insert or append new entry
+ if (nInsert <= nCount)
+ {
+ if (!bSplit)
+ memmove( pData.get() + nInsert + 1, pData.get() + nInsert,
+ (nCount - nInsert) * sizeof(DataEntry));
+ else
+ {
+ memmove( pData.get() + nInsert + 2, pData.get() + nInsert,
+ (nCount - nInsert) * sizeof(DataEntry));
+ pData[nInsert+1] = pData[nInsert-1];
+ nCount++;
+ }
+ }
+ if (nInsert)
+ pData[nInsert-1].nEnd = nStart - 1;
+ pData[nInsert].nEnd = nEnd;
+ pData[nInsert].aValue = aNewVal;
+ nCount++;
+ }
+ }
+}
+
+template< typename A, typename D >
+void ScCompressedArray<A,D>::CopyFrom( const ScCompressedArray<A,D>& rArray, A nDestStart,
+ A nDestEnd, A nSrcStart )
+{
+ assert( this != &rArray && "cannot copy self->self" );
+ size_t nIndex = 0;
+ A nRegionEnd;
+ for (A j=nDestStart; j<=nDestEnd; ++j)
+ {
+ const D& rValue = (j==nDestStart ?
+ rArray.GetValue( j - nDestStart + nSrcStart, nIndex, nRegionEnd) :
+ rArray.GetNextValue( nIndex, nRegionEnd));
+ nRegionEnd = nRegionEnd - nSrcStart + nDestStart;
+ if (nRegionEnd > nDestEnd)
+ nRegionEnd = nDestEnd;
+ this->SetValue( j, nRegionEnd, rValue);
+ j = nRegionEnd;
+ }
+}
+
+template< typename A, typename D >
+const D& ScCompressedArray<A,D>::Insert( A nStart, size_t nAccessCount )
+{
+ size_t nIndex = this->Search( nStart);
+ // No real insertion is needed, simply extend the one entry and adapt all
+ // following. In case nStart points to the start row of an entry, extend
+ // the previous entry (inserting before nStart).
+ if (nIndex > 0 && pData[nIndex-1].nEnd+1 == nStart)
+ --nIndex;
+ const D& rValue = pData[nIndex].aValue; // the value "copied"
+ do
+ {
+ pData[nIndex].nEnd += nAccessCount;
+ if (pData[nIndex].nEnd >= nMaxAccess)
+ {
+ pData[nIndex].nEnd = nMaxAccess;
+ nCount = nIndex + 1; // discard trailing entries
+ }
+ } while (++nIndex < nCount);
+ return rValue;
+}
+
+template< typename A, typename D >
+void ScCompressedArray<A,D>::InsertPreservingSize( A nStart, size_t nAccessCount, const D& rFillValue )
+{
+ const A nPrevLastPos = GetLastPos();
+
+ Insert(nStart, nAccessCount);
+ for (A i = nStart; i < A(nStart + nAccessCount); ++i)
+ SetValue(i, rFillValue);
+
+ const A nNewLastPos = GetLastPos();
+ Remove(nPrevLastPos, nNewLastPos - nPrevLastPos);
+}
+
+template< typename A, typename D >
+void ScCompressedArray<A,D>::Remove( A nStart, size_t nAccessCount )
+{
+ A nEnd = nStart + nAccessCount - 1;
+ size_t nIndex = this->Search( nStart);
+ // equalize/combine/remove all entries in between
+ if (nEnd > pData[nIndex].nEnd)
+ this->SetValue( nStart, nEnd, pData[nIndex].aValue);
+ // remove an exactly matching entry by shifting up all following by one
+ if ((nStart == 0 || (nIndex > 0 && nStart == pData[nIndex-1].nEnd+1)) &&
+ pData[nIndex].nEnd == nEnd && nIndex < nCount-1)
+ {
+ // In case removing an entry results in two adjacent entries with
+ // identical data, combine them into one. This is also necessary to
+ // make the algorithm used in SetValue() work correctly, it relies on
+ // the fact that consecutive values actually differ.
+ size_t nRemove;
+ if (nIndex > 0 && pData[nIndex-1].aValue == pData[nIndex+1].aValue)
+ {
+ nRemove = 2;
+ --nIndex;
+ }
+ else
+ nRemove = 1;
+ memmove( pData.get() + nIndex, pData.get() + nIndex + nRemove, (nCount - (nIndex +
+ nRemove)) * sizeof(DataEntry));
+ nCount -= nRemove;
+ }
+ // adjust end rows, nIndex still being valid
+ do
+ {
+ pData[nIndex].nEnd -= nAccessCount;
+ } while (++nIndex < nCount);
+ pData[nCount-1].nEnd = nMaxAccess;
+}
+
+template< typename A, typename D >
+void ScCompressedArray<A,D>::RemovePreservingSize( A nStart, size_t nAccessCount, const D& rFillValue )
+{
+ const A nPrevLastPos = GetLastPos();
+
+ Remove(nStart, nAccessCount);
+
+ const A nNewLastPos = GetLastPos();
+ InsertPreservingSize(nNewLastPos, nNewLastPos - nPrevLastPos, rFillValue);
+}
+
+template< typename A, typename D >
+void ScCompressedArray<A,D>::Iterator::operator++()
+{
+ ++mnRegion;
+ if (mnRegion > mrArray.pData[mnIndex].nEnd)
+ ++mnIndex;
+}
+
+template< typename A, typename D >
+typename ScCompressedArray<A,D>::Iterator ScCompressedArray<A,D>::Iterator::operator+(size_t nAccessCount) const
+{
+ A nRegion = mnRegion + nAccessCount;
+ auto nIndex = mnIndex;
+ while (nRegion > mrArray.pData[nIndex].nEnd)
+ ++nIndex;
+ return Iterator(mrArray, nIndex, nRegion);
+}
+
+// === ScBitMaskCompressedArray ==============================================
+
+template< typename A, typename D >
+void ScBitMaskCompressedArray<A,D>::AndValue( A nStart, A nEnd,
+ const D& rValueToAnd )
+{
+ if (nStart > nEnd)
+ return;
+
+ size_t nIndex = this->Search( nStart);
+ do
+ {
+ if ((this->pData[nIndex].aValue & rValueToAnd) != this->pData[nIndex].aValue)
+ {
+ A nS = ::std::max<A>( (nIndex>0 ? this->pData[nIndex-1].nEnd+1 : 0), nStart);
+ A nE = ::std::min( this->pData[nIndex].nEnd, nEnd);
+ this->SetValue( nS, nE, this->pData[nIndex].aValue & rValueToAnd);
+ if (nE >= nEnd)
+ break; // while
+ nIndex = this->Search( nE + 1);
+ }
+ else if (this->pData[nIndex].nEnd >= nEnd)
+ break; // while
+ else
+ ++nIndex;
+ } while (nIndex < this->nCount);
+}
+
+template< typename A, typename D >
+void ScBitMaskCompressedArray<A,D>::OrValue( A nStart, A nEnd,
+ const D& rValueToOr )
+{
+ if (nStart > nEnd)
+ return;
+
+ size_t nIndex = this->Search( nStart);
+ do
+ {
+ if ((this->pData[nIndex].aValue | rValueToOr) != this->pData[nIndex].aValue)
+ {
+ A nS = ::std::max<A>( (nIndex>0 ? this->pData[nIndex-1].nEnd+1 : 0), nStart);
+ A nE = ::std::min( this->pData[nIndex].nEnd, nEnd);
+ this->SetValue( nS, nE, this->pData[nIndex].aValue | rValueToOr);
+ if (nE >= nEnd)
+ break; // while
+ nIndex = this->Search( nE + 1);
+ }
+ else if (this->pData[nIndex].nEnd >= nEnd)
+ break; // while
+ else
+ ++nIndex;
+ } while (nIndex < this->nCount);
+}
+
+template< typename A, typename D >
+void ScBitMaskCompressedArray<A,D>::CopyFromAnded(
+ const ScBitMaskCompressedArray<A,D>& rArray, A nStart, A nEnd,
+ const D& rValueToAnd )
+{
+ size_t nIndex = 0;
+ A nRegionEnd;
+ for (A j=nStart; j<=nEnd; ++j)
+ {
+ const D& rValue = (j==nStart ?
+ rArray.GetValue( j, nIndex, nRegionEnd) :
+ rArray.GetNextValue( nIndex, nRegionEnd));
+ if (nRegionEnd > nEnd)
+ nRegionEnd = nEnd;
+ this->SetValue( j, nRegionEnd, rValue & rValueToAnd);
+ j = nRegionEnd;
+ }
+}
+
+template< typename A, typename D >
+A ScBitMaskCompressedArray<A,D>::GetLastAnyBitAccess( const D& rBitMask ) const
+{
+ A nEnd = ::std::numeric_limits<A>::max();
+ size_t nIndex = this->nCount-1;
+ while (true)
+ {
+ if (this->pData[nIndex].aValue & rBitMask)
+ {
+ nEnd = this->pData[nIndex].nEnd;
+ break; // while
+ }
+ else
+ {
+ if (nIndex > 0)
+ {
+ --nIndex;
+ if (this->pData[nIndex].nEnd < 0)
+ break; // while
+ }
+ else
+ break; // while
+ }
+ }
+ return nEnd;
+}
+
+// === Force instantiation of specializations ================================
+
+template class ScCompressedArray< SCROW, CRFlags>; // flags, base class
+template class ScBitMaskCompressedArray< SCROW, CRFlags>; // flags
+template class ScCompressedArray< SCCOL, sal_uInt16>;
+template class ScCompressedArray< SCCOL, CRFlags>;
+template class ScCompressedArray< SCROW, sal_uInt16>;
+template class ScBitMaskCompressedArray< SCCOL, CRFlags>;
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/conditio.cxx b/sc/source/core/data/conditio.cxx
new file mode 100644
index 000000000..dae08455b
--- /dev/null
+++ b/sc/source/core/data/conditio.cxx
@@ -0,0 +1,2335 @@
+/* -*- 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 <svl/numformat.hxx>
+#include <rtl/math.hxx>
+#include <sal/log.hxx>
+#include <unotools/collatorwrapper.hxx>
+
+#include <com/sun/star/sheet/ConditionOperator2.hpp>
+
+#include <attrib.hxx>
+#include <conditio.hxx>
+#include <formulacell.hxx>
+#include <document.hxx>
+#include <compiler.hxx>
+#include <rangelst.hxx>
+#include <rangenam.hxx>
+#include <rangeutl.hxx>
+#include <colorscale.hxx>
+#include <cellvalue.hxx>
+#include <editutil.hxx>
+#include <tokenarray.hxx>
+#include <fillinfo.hxx>
+#include <refupdatecontext.hxx>
+#include <formula/errorcodes.hxx>
+#include <svl/sharedstring.hxx>
+#include <svl/sharedstringpool.hxx>
+#include <memory>
+#include <numeric>
+
+using namespace formula;
+
+ScFormatEntry::ScFormatEntry(ScDocument* pDoc):
+ mpDoc(pDoc)
+{
+}
+
+bool ScFormatEntry::operator==( const ScFormatEntry& r ) const
+{
+ return IsEqual(r, false);
+}
+
+// virtual
+bool ScFormatEntry::IsEqual( const ScFormatEntry& /*r*/, bool /*bIgnoreSrcPos*/ ) const
+{
+ // By default, return false; this makes sense for all cases except ScConditionEntry
+ // As soon as databar and color scale are tested we need to think about the range
+ return false;
+}
+
+void ScFormatEntry::startRendering()
+{
+}
+
+void ScFormatEntry::endRendering()
+{
+}
+
+static bool lcl_HasRelRef( ScDocument* pDoc, const ScTokenArray* pFormula, sal_uInt16 nRecursion = 0 )
+{
+ if (pFormula)
+ {
+ FormulaTokenArrayPlainIterator aIter( *pFormula );
+ FormulaToken* t;
+ for( t = aIter.Next(); t; t = aIter.Next() )
+ {
+ switch( t->GetType() )
+ {
+ case svDoubleRef:
+ {
+ ScSingleRefData& rRef2 = t->GetDoubleRef()->Ref2;
+ if ( rRef2.IsColRel() || rRef2.IsRowRel() || rRef2.IsTabRel() )
+ return true;
+ [[fallthrough]];
+ }
+
+ case svSingleRef:
+ {
+ ScSingleRefData& rRef1 = *t->GetSingleRef();
+ if ( rRef1.IsColRel() || rRef1.IsRowRel() || rRef1.IsTabRel() )
+ return true;
+ }
+ break;
+
+ case svIndex:
+ {
+ if( t->GetOpCode() == ocName ) // DB areas always absolute
+ if( ScRangeData* pRangeData = pDoc->FindRangeNameBySheetAndIndex( t->GetSheet(), t->GetIndex()) )
+ if( (nRecursion < 42) && lcl_HasRelRef( pDoc, pRangeData->GetCode(), nRecursion + 1 ) )
+ return true;
+ }
+ break;
+
+ // #i34474# function result dependent on cell position
+ case svByte:
+ {
+ switch( t->GetOpCode() )
+ {
+ case ocRow: // ROW() returns own row index
+ case ocColumn: // COLUMN() returns own column index
+ case ocSheet: // SHEET() returns own sheet index
+ case ocCell: // CELL() may return own cell address
+ return true;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+ }
+ break;
+
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+ }
+ }
+ return false;
+}
+
+namespace {
+
+void start_listen_to(ScFormulaListener& rListener, const ScTokenArray* pTokens, const ScRangeList& rRangeList)
+{
+ size_t n = rRangeList.size();
+ for (size_t i = 0; i < n; ++i)
+ {
+ const ScRange & rRange = rRangeList[i];
+ rListener.addTokenArray(pTokens, rRange);
+ }
+}
+
+}
+
+void ScConditionEntry::StartListening()
+{
+ if (!pCondFormat)
+ return;
+
+ const ScRangeList& rRanges = pCondFormat->GetRange();
+ mpListener->stopListening();
+ start_listen_to(*mpListener, pFormula1.get(), rRanges);
+ start_listen_to(*mpListener, pFormula2.get(), rRanges);
+
+ mpListener->setCallback([&]() { pCondFormat->DoRepaint();});
+}
+
+void ScConditionEntry::SetParent(ScConditionalFormat* pParent)
+{
+ pCondFormat = pParent;
+ StartListening();
+}
+
+ScConditionEntry::ScConditionEntry( const ScConditionEntry& r ) :
+ ScFormatEntry(r.mpDoc),
+ eOp(r.eOp),
+ nOptions(r.nOptions),
+ nVal1(r.nVal1),
+ nVal2(r.nVal2),
+ aStrVal1(r.aStrVal1),
+ aStrVal2(r.aStrVal2),
+ aStrNmsp1(r.aStrNmsp1),
+ aStrNmsp2(r.aStrNmsp2),
+ eTempGrammar1(r.eTempGrammar1),
+ eTempGrammar2(r.eTempGrammar2),
+ bIsStr1(r.bIsStr1),
+ bIsStr2(r.bIsStr2),
+ aSrcPos(r.aSrcPos),
+ aSrcString(r.aSrcString),
+ bRelRef1(r.bRelRef1),
+ bRelRef2(r.bRelRef2),
+ bFirstRun(true),
+ mpListener(new ScFormulaListener(*r.mpDoc)),
+ eConditionType( r.eConditionType ),
+ pCondFormat(r.pCondFormat)
+{
+ // ScTokenArray copy ctor creates a flat copy
+ if (r.pFormula1)
+ pFormula1.reset( new ScTokenArray( *r.pFormula1 ) );
+ if (r.pFormula2)
+ pFormula2.reset( new ScTokenArray( *r.pFormula2 ) );
+
+ StartListening();
+ // Formula cells are created at IsValid
+}
+
+ScConditionEntry::ScConditionEntry( ScDocument& rDocument, const ScConditionEntry& r ) :
+ ScFormatEntry(&rDocument),
+ eOp(r.eOp),
+ nOptions(r.nOptions),
+ nVal1(r.nVal1),
+ nVal2(r.nVal2),
+ aStrVal1(r.aStrVal1),
+ aStrVal2(r.aStrVal2),
+ aStrNmsp1(r.aStrNmsp1),
+ aStrNmsp2(r.aStrNmsp2),
+ eTempGrammar1(r.eTempGrammar1),
+ eTempGrammar2(r.eTempGrammar2),
+ bIsStr1(r.bIsStr1),
+ bIsStr2(r.bIsStr2),
+ aSrcPos(r.aSrcPos),
+ aSrcString(r.aSrcString),
+ bRelRef1(r.bRelRef1),
+ bRelRef2(r.bRelRef2),
+ bFirstRun(true),
+ mpListener(new ScFormulaListener(rDocument)),
+ eConditionType( r.eConditionType),
+ pCondFormat(r.pCondFormat)
+{
+ // Real copy of the formulas (for Ref Undo)
+ if (r.pFormula1)
+ pFormula1 = r.pFormula1->Clone();
+ if (r.pFormula2)
+ pFormula2 = r.pFormula2->Clone();
+
+ // Formula cells are created at IsValid
+ // TODO: But not in the Clipboard! So interpret beforehand!
+}
+
+ScConditionEntry::ScConditionEntry( ScConditionMode eOper,
+ const OUString& rExpr1, const OUString& rExpr2, ScDocument& rDocument, const ScAddress& rPos,
+ const OUString& rExprNmsp1, const OUString& rExprNmsp2,
+ FormulaGrammar::Grammar eGrammar1, FormulaGrammar::Grammar eGrammar2,
+ Type eType ) :
+ ScFormatEntry(&rDocument),
+ eOp(eOper),
+ nOptions(0),
+ nVal1(0.0),
+ nVal2(0.0),
+ aStrNmsp1(rExprNmsp1),
+ aStrNmsp2(rExprNmsp2),
+ eTempGrammar1(eGrammar1),
+ eTempGrammar2(eGrammar2),
+ bIsStr1(false),
+ bIsStr2(false),
+ aSrcPos(rPos),
+ bRelRef1(false),
+ bRelRef2(false),
+ bFirstRun(true),
+ mpListener(new ScFormulaListener(rDocument)),
+ eConditionType(eType),
+ pCondFormat(nullptr)
+{
+ Compile( rExpr1, rExpr2, rExprNmsp1, rExprNmsp2, eGrammar1, eGrammar2, false );
+
+ // Formula cells are created at IsValid
+}
+
+ScConditionEntry::ScConditionEntry( ScConditionMode eOper,
+ const ScTokenArray* pArr1, const ScTokenArray* pArr2,
+ ScDocument& rDocument, const ScAddress& rPos ) :
+ ScFormatEntry(&rDocument),
+ eOp(eOper),
+ nOptions(0),
+ nVal1(0.0),
+ nVal2(0.0),
+ eTempGrammar1(FormulaGrammar::GRAM_DEFAULT),
+ eTempGrammar2(FormulaGrammar::GRAM_DEFAULT),
+ bIsStr1(false),
+ bIsStr2(false),
+ aSrcPos(rPos),
+ bRelRef1(false),
+ bRelRef2(false),
+ bFirstRun(true),
+ mpListener(new ScFormulaListener(rDocument)),
+ eConditionType(ScFormatEntry::Type::Condition),
+ pCondFormat(nullptr)
+{
+ if ( pArr1 )
+ {
+ pFormula1.reset( new ScTokenArray( *pArr1 ) );
+ SimplifyCompiledFormula( pFormula1, nVal1, bIsStr1, aStrVal1 );
+ bRelRef1 = lcl_HasRelRef( mpDoc, pFormula1.get() );
+ }
+ if ( pArr2 )
+ {
+ pFormula2.reset( new ScTokenArray( *pArr2 ) );
+ SimplifyCompiledFormula( pFormula2, nVal2, bIsStr2, aStrVal2 );
+ bRelRef2 = lcl_HasRelRef( mpDoc, pFormula2.get() );
+ }
+
+ StartListening();
+
+ // Formula cells are created at IsValid
+}
+
+ScConditionEntry::~ScConditionEntry()
+{
+}
+
+void ScConditionEntry::SimplifyCompiledFormula( std::unique_ptr<ScTokenArray>& rFormula,
+ double& rVal,
+ bool& rIsStr,
+ OUString& rStrVal )
+{
+ if ( rFormula->GetLen() != 1 )
+ return;
+
+ // Single (constant number)?
+ FormulaToken* pToken = rFormula->FirstToken();
+ if ( pToken->GetOpCode() != ocPush )
+ return;
+
+ if ( pToken->GetType() == svDouble )
+ {
+ rVal = pToken->GetDouble();
+ rFormula.reset(); // Do not remember as formula
+ }
+ else if ( pToken->GetType() == svString )
+ {
+ rIsStr = true;
+ rStrVal = pToken->GetString().getString();
+ rFormula.reset(); // Do not remember as formula
+ }
+}
+
+void ScConditionEntry::SetOperation(ScConditionMode eMode)
+{
+ eOp = eMode;
+}
+
+void ScConditionEntry::Compile( const OUString& rExpr1, const OUString& rExpr2,
+ const OUString& rExprNmsp1, const OUString& rExprNmsp2,
+ FormulaGrammar::Grammar eGrammar1, FormulaGrammar::Grammar eGrammar2, bool bTextToReal )
+{
+ if ( !rExpr1.isEmpty() || !rExpr2.isEmpty() )
+ {
+ ScCompiler aComp( *mpDoc, aSrcPos );
+
+ if ( !rExpr1.isEmpty() )
+ {
+ pFormula1.reset();
+ aComp.SetGrammar( eGrammar1 );
+ if ( mpDoc->IsImportingXML() && !bTextToReal )
+ {
+ // temporary formula string as string tokens
+ pFormula1.reset( new ScTokenArray(*mpDoc) );
+ pFormula1->AssignXMLString( rExpr1, rExprNmsp1 );
+ // bRelRef1 is set when the formula is compiled again (CompileXML)
+ }
+ else
+ {
+ pFormula1 = aComp.CompileString( rExpr1, rExprNmsp1 );
+ SimplifyCompiledFormula( pFormula1, nVal1, bIsStr1, aStrVal1 );
+ bRelRef1 = lcl_HasRelRef( mpDoc, pFormula1.get() );
+ }
+ }
+
+ if ( !rExpr2.isEmpty() )
+ {
+ pFormula2.reset();
+ aComp.SetGrammar( eGrammar2 );
+ if ( mpDoc->IsImportingXML() && !bTextToReal )
+ {
+ // temporary formula string as string tokens
+ pFormula2.reset( new ScTokenArray(*mpDoc) );
+ pFormula2->AssignXMLString( rExpr2, rExprNmsp2 );
+ // bRelRef2 is set when the formula is compiled again (CompileXML)
+ }
+ else
+ {
+ pFormula2 = aComp.CompileString( rExpr2, rExprNmsp2 );
+ SimplifyCompiledFormula( pFormula2, nVal2, bIsStr2, aStrVal2 );
+ bRelRef2 = lcl_HasRelRef( mpDoc, pFormula2.get() );
+ }
+ }
+ }
+
+ StartListening();
+}
+
+/**
+ * Create formula cells
+ */
+void ScConditionEntry::MakeCells( const ScAddress& rPos )
+{
+ if ( mpDoc->IsClipOrUndo() ) // Never calculate in the Clipboard!
+ return;
+
+ if ( pFormula1 && !pFCell1 && !bRelRef1 )
+ {
+ // pFCell1 will hold a flat-copied ScTokenArray sharing ref-counted
+ // code tokens with pFormula1
+ pFCell1.reset( new ScFormulaCell(*mpDoc, rPos, *pFormula1) );
+ pFCell1->SetFreeFlying(true);
+ pFCell1->StartListeningTo( *mpDoc );
+ }
+
+ if ( pFormula2 && !pFCell2 && !bRelRef2 )
+ {
+ // pFCell2 will hold a flat-copied ScTokenArray sharing ref-counted
+ // code tokens with pFormula2
+ pFCell2.reset( new ScFormulaCell(*mpDoc, rPos, *pFormula2) );
+ pFCell2->SetFreeFlying(true);
+ pFCell2->StartListeningTo( *mpDoc );
+ }
+}
+
+void ScConditionEntry::SetIgnoreBlank(bool bSet)
+{
+ // The bit SC_COND_NOBLANKS is set if blanks are not ignored
+ // (only of valid)
+ if (bSet)
+ nOptions &= ~SC_COND_NOBLANKS;
+ else
+ nOptions |= SC_COND_NOBLANKS;
+}
+
+/**
+ * Delete formula cells, so we re-compile at the next IsValid
+ */
+void ScConditionEntry::CompileAll()
+{
+ pFCell1.reset();
+ pFCell2.reset();
+}
+
+void ScConditionEntry::CompileXML()
+{
+ // First parse the formula source position if it was stored as text
+ if ( !aSrcString.isEmpty() )
+ {
+ ScAddress aNew;
+ /* XML is always in OOo:A1 format, although R1C1 would be more amenable
+ * to compression */
+ if ( aNew.Parse( aSrcString, *mpDoc ) & ScRefFlags::VALID )
+ aSrcPos = aNew;
+ // if the position is invalid, there isn't much we can do at this time
+ aSrcString.clear();
+ }
+
+ // Convert the text tokens that were created during XML import into real tokens.
+ Compile( GetExpression(aSrcPos, 0, 0, eTempGrammar1),
+ GetExpression(aSrcPos, 1, 0, eTempGrammar2),
+ aStrNmsp1, aStrNmsp2, eTempGrammar1, eTempGrammar2, true );
+
+ // Importing ocDde/ocWebservice?
+ if (pFormula1)
+ mpDoc->CheckLinkFormulaNeedingCheck(*pFormula1);
+ if (pFormula2)
+ mpDoc->CheckLinkFormulaNeedingCheck(*pFormula2);
+}
+
+void ScConditionEntry::SetSrcString( const OUString& rNew )
+{
+ // aSrcString is only evaluated in CompileXML
+ SAL_WARN_IF( !mpDoc->IsImportingXML(), "sc", "SetSrcString is only valid for XML import" );
+
+ aSrcString = rNew;
+}
+
+void ScConditionEntry::SetFormula1( const ScTokenArray& rArray )
+{
+ pFormula1.reset();
+ if( rArray.GetLen() > 0 )
+ {
+ pFormula1.reset( new ScTokenArray( rArray ) );
+ bRelRef1 = lcl_HasRelRef( mpDoc, pFormula1.get() );
+ }
+
+ StartListening();
+}
+
+void ScConditionEntry::SetFormula2( const ScTokenArray& rArray )
+{
+ pFormula2.reset();
+ if( rArray.GetLen() > 0 )
+ {
+ pFormula2.reset( new ScTokenArray( rArray ) );
+ bRelRef2 = lcl_HasRelRef( mpDoc, pFormula2.get() );
+ }
+
+ StartListening();
+}
+
+void ScConditionEntry::UpdateReference( sc::RefUpdateContext& rCxt )
+{
+ if(pCondFormat)
+ aSrcPos = pCondFormat->GetRange().Combine().aStart;
+ ScAddress aOldSrcPos = aSrcPos;
+ bool bChangedPos = false;
+ if (rCxt.meMode == URM_INSDEL && rCxt.maRange.Contains(aSrcPos))
+ {
+ ScAddress aErrorPos( ScAddress::UNINITIALIZED );
+ if (!aSrcPos.Move(rCxt.mnColDelta, rCxt.mnRowDelta, rCxt.mnTabDelta, aErrorPos, *mpDoc))
+ {
+ assert(!"can't move ScConditionEntry");
+ }
+ bChangedPos = aSrcPos != aOldSrcPos;
+ }
+
+ if (pFormula1)
+ {
+ sc::RefUpdateResult aRes;
+ switch (rCxt.meMode)
+ {
+ case URM_INSDEL:
+ aRes = pFormula1->AdjustReferenceOnShift(rCxt, aOldSrcPos);
+ break;
+ case URM_MOVE:
+ aRes = pFormula1->AdjustReferenceOnMove(rCxt, aOldSrcPos, aSrcPos);
+ break;
+ default:
+ ;
+ }
+
+ if (aRes.mbReferenceModified || bChangedPos)
+ pFCell1.reset(); // is created again in IsValid
+ }
+
+ if (pFormula2)
+ {
+ sc::RefUpdateResult aRes;
+ switch (rCxt.meMode)
+ {
+ case URM_INSDEL:
+ aRes = pFormula2->AdjustReferenceOnShift(rCxt, aOldSrcPos);
+ break;
+ case URM_MOVE:
+ aRes = pFormula2->AdjustReferenceOnMove(rCxt, aOldSrcPos, aSrcPos);
+ break;
+ default:
+ ;
+ }
+
+ if (aRes.mbReferenceModified || bChangedPos)
+ pFCell2.reset(); // is created again in IsValid
+ }
+
+ StartListening();
+}
+
+void ScConditionEntry::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt )
+{
+ if (pFormula1)
+ {
+ pFormula1->AdjustReferenceOnInsertedTab(rCxt, aSrcPos);
+ pFCell1.reset();
+ }
+
+ if (pFormula2)
+ {
+ pFormula2->AdjustReferenceOnInsertedTab(rCxt, aSrcPos);
+ pFCell2.reset();
+ }
+
+ ScRangeUpdater::UpdateInsertTab(aSrcPos, rCxt);
+}
+
+void ScConditionEntry::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt )
+{
+ if (pFormula1)
+ {
+ pFormula1->AdjustReferenceOnDeletedTab(rCxt, aSrcPos);
+ pFCell1.reset();
+ }
+
+ if (pFormula2)
+ {
+ pFormula2->AdjustReferenceOnDeletedTab(rCxt, aSrcPos);
+ pFCell2.reset();
+ }
+
+ ScRangeUpdater::UpdateDeleteTab(aSrcPos, rCxt);
+ StartListening();
+}
+
+void ScConditionEntry::UpdateMoveTab( sc::RefUpdateMoveTabContext& rCxt )
+{
+ if (pFormula1)
+ {
+ pFormula1->AdjustReferenceOnMovedTab(rCxt, aSrcPos);
+ pFCell1.reset();
+ }
+
+ if (pFormula2)
+ {
+ pFormula2->AdjustReferenceOnMovedTab(rCxt, aSrcPos);
+ pFCell2.reset();
+ }
+
+ StartListening();
+}
+
+static bool lcl_IsEqual( const std::unique_ptr<ScTokenArray>& pArr1, const std::unique_ptr<ScTokenArray>& pArr2 )
+{
+ // We only compare the non-RPN array
+ if ( pArr1 && pArr2 )
+ return pArr1->EqualTokens( pArr2.get() );
+ else
+ return !pArr1 && !pArr2; // Both 0? -> the same
+}
+
+// virtual
+bool ScConditionEntry::IsEqual( const ScFormatEntry& rOther, bool bIgnoreSrcPos ) const
+{
+ if (GetType() != rOther.GetType())
+ return false;
+
+ const ScConditionEntry& r = static_cast<const ScConditionEntry&>(rOther);
+
+ bool bEq = (eOp == r.eOp && nOptions == r.nOptions &&
+ lcl_IsEqual( pFormula1, r.pFormula1 ) &&
+ lcl_IsEqual( pFormula2, r.pFormula2 ));
+
+ if (!bIgnoreSrcPos)
+ {
+ // for formulas, the reference positions must be compared, too
+ // (including aSrcString, for inserting the entries during XML import)
+ if ( bEq && ( pFormula1 || pFormula2 ) && ( aSrcPos != r.aSrcPos || aSrcString != r.aSrcString ) )
+ bEq = false;
+ }
+
+ // If not formulas, compare values
+ if ( bEq && !pFormula1 && ( nVal1 != r.nVal1 || aStrVal1 != r.aStrVal1 || bIsStr1 != r.bIsStr1 ) )
+ bEq = false;
+ if ( bEq && !pFormula2 && ( nVal2 != r.nVal2 || aStrVal2 != r.aStrVal2 || bIsStr2 != r.bIsStr2 ) )
+ bEq = false;
+
+ return bEq;
+}
+
+void ScConditionEntry::Interpret( const ScAddress& rPos )
+{
+ // Create formula cells
+ // Note: New Broadcaster (Note cells) may be inserted into the document!
+ if ( ( pFormula1 && !pFCell1 ) || ( pFormula2 && !pFCell2 ) )
+ MakeCells( rPos );
+
+ // Evaluate formulas
+ bool bDirty = false; // 1 and 2 separate?
+
+ std::unique_ptr<ScFormulaCell> pTemp1;
+ ScFormulaCell* pEff1 = pFCell1.get();
+ if ( bRelRef1 )
+ {
+ pTemp1.reset(pFormula1 ? new ScFormulaCell(*mpDoc, rPos, *pFormula1) : new ScFormulaCell(*mpDoc, rPos));
+ pEff1 = pTemp1.get();
+ pEff1->SetFreeFlying(true);
+ }
+ if ( pEff1 )
+ {
+ if (!pEff1->IsRunning()) // Don't create 522
+ {
+ //TODO: Query Changed instead of Dirty!
+ if (pEff1->GetDirty() && !bRelRef1 && mpDoc->GetAutoCalc())
+ bDirty = true;
+ if (pEff1->IsValue())
+ {
+ bIsStr1 = false;
+ nVal1 = pEff1->GetValue();
+ aStrVal1.clear();
+ }
+ else
+ {
+ bIsStr1 = true;
+ aStrVal1 = pEff1->GetString().getString();
+ nVal1 = 0.0;
+ }
+ }
+ }
+ pTemp1.reset();
+
+ std::unique_ptr<ScFormulaCell> pTemp2;
+ ScFormulaCell* pEff2 = pFCell2.get(); //@ 1!=2
+ if ( bRelRef2 )
+ {
+ pTemp2.reset(pFormula2 ? new ScFormulaCell(*mpDoc, rPos, *pFormula2) : new ScFormulaCell(*mpDoc, rPos));
+ pEff2 = pTemp2.get();
+ pEff2->SetFreeFlying(true);
+ }
+ if ( pEff2 )
+ {
+ if (!pEff2->IsRunning()) // Don't create 522
+ {
+ if (pEff2->GetDirty() && !bRelRef2 && mpDoc->GetAutoCalc())
+ bDirty = true;
+ if (pEff2->IsValue())
+ {
+ bIsStr2 = false;
+ nVal2 = pEff2->GetValue();
+ aStrVal2.clear();
+ }
+ else
+ {
+ bIsStr2 = true;
+ aStrVal2 = pEff2->GetString().getString();
+ nVal2 = 0.0;
+ }
+ }
+ }
+ pTemp2.reset();
+
+ // If IsRunning, the last values remain
+ if (bDirty && !bFirstRun)
+ {
+ // Repaint everything for dependent formats
+ DataChanged();
+ }
+
+ bFirstRun = false;
+}
+
+static bool lcl_GetCellContent( ScRefCellValue& rCell, bool bIsStr1, double& rArg, OUString& rArgStr,
+ const ScDocument* pDoc )
+{
+
+ if (rCell.isEmpty())
+ return !bIsStr1;
+
+ bool bVal = true;
+
+ switch (rCell.meType)
+ {
+ case CELLTYPE_VALUE:
+ rArg = rCell.mfValue;
+ break;
+ case CELLTYPE_FORMULA:
+ {
+ bVal = rCell.mpFormula->IsValue();
+ if (bVal)
+ rArg = rCell.mpFormula->GetValue();
+ else
+ rArgStr = rCell.mpFormula->GetString().getString();
+ }
+ break;
+ case CELLTYPE_STRING:
+ case CELLTYPE_EDIT:
+ bVal = false;
+ if (rCell.meType == CELLTYPE_STRING)
+ rArgStr = rCell.mpString->getString();
+ else if (rCell.mpEditText)
+ rArgStr = ScEditUtil::GetString(*rCell.mpEditText, pDoc);
+ break;
+ default:
+ ;
+ }
+
+ return bVal;
+}
+
+void ScConditionEntry::FillCache() const
+{
+ if(mpCache)
+ return;
+
+ const ScRangeList& rRanges = pCondFormat->GetRange();
+ mpCache.reset(new ScConditionEntryCache);
+ size_t nListCount = rRanges.size();
+ for( size_t i = 0; i < nListCount; i++ )
+ {
+ const ScRange & rRange = rRanges[i];
+ SCROW nRow = rRange.aEnd.Row();
+ SCCOL nCol = rRange.aEnd.Col();
+ SCCOL nColStart = rRange.aStart.Col();
+ SCROW nRowStart = rRange.aStart.Row();
+ SCTAB nTab = rRange.aStart.Tab();
+
+ // temporary fix to workaround slow duplicate entry
+ // conditions, prevent to use a whole row
+ if(nRow == mpDoc->MaxRow())
+ {
+ bool bShrunk = false;
+ mpDoc->ShrinkToUsedDataArea(bShrunk, nTab, nColStart, nRowStart,
+ nCol, nRow, false);
+ }
+
+ for( SCROW r = nRowStart; r <= nRow; r++ )
+ for( SCCOL c = nColStart; c <= nCol; c++ )
+ {
+ ScRefCellValue aCell(*mpDoc, ScAddress(c, r, nTab));
+ if (aCell.isEmpty())
+ continue;
+
+ double nVal = 0.0;
+ OUString aStr;
+ if (!lcl_GetCellContent(aCell, false, nVal, aStr, mpDoc))
+ {
+ std::pair<ScConditionEntryCache::StringCacheType::iterator, bool> aResult =
+ mpCache->maStrings.emplace(aStr, 1);
+
+ if(!aResult.second)
+ aResult.first->second++;
+ }
+ else
+ {
+ std::pair<ScConditionEntryCache::ValueCacheType::iterator, bool> aResult =
+ mpCache->maValues.emplace(nVal, 1);
+
+ if(!aResult.second)
+ aResult.first->second++;
+
+ ++(mpCache->nValueItems);
+ }
+ }
+ }
+}
+
+bool ScConditionEntry::IsDuplicate( double nArg, const OUString& rStr ) const
+{
+ FillCache();
+
+ if(rStr.isEmpty())
+ {
+ ScConditionEntryCache::ValueCacheType::iterator itr = mpCache->maValues.find(nArg);
+ if(itr == mpCache->maValues.end())
+ return false;
+ else
+ {
+ return itr->second > 1;
+ }
+ }
+ else
+ {
+ ScConditionEntryCache::StringCacheType::iterator itr = mpCache->maStrings.find(rStr);
+ if(itr == mpCache->maStrings.end())
+ return false;
+ else
+ {
+ return itr->second > 1;
+ }
+ }
+}
+
+bool ScConditionEntry::IsTopNElement( double nArg ) const
+{
+ FillCache();
+
+ if(mpCache->nValueItems <= nVal1)
+ return true;
+
+ size_t nCells = 0;
+ for(ScConditionEntryCache::ValueCacheType::const_reverse_iterator itr = mpCache->maValues.rbegin(),
+ itrEnd = mpCache->maValues.rend(); itr != itrEnd; ++itr)
+ {
+ if(nCells >= nVal1)
+ return false;
+ if(itr->first <= nArg)
+ return true;
+ nCells += itr->second;
+ }
+
+ return true;
+}
+
+bool ScConditionEntry::IsBottomNElement( double nArg ) const
+{
+ FillCache();
+
+ if(mpCache->nValueItems <= nVal1)
+ return true;
+
+ size_t nCells = 0;
+ for(const auto& [rVal, rCount] : mpCache->maValues)
+ {
+ if(nCells >= nVal1)
+ return false;
+ if(rVal >= nArg)
+ return true;
+ nCells += rCount;
+ }
+
+ return true;
+}
+
+bool ScConditionEntry::IsTopNPercent( double nArg ) const
+{
+ FillCache();
+
+ size_t nCells = 0;
+ size_t nLimitCells = static_cast<size_t>(mpCache->nValueItems*nVal1/100);
+ for(ScConditionEntryCache::ValueCacheType::const_reverse_iterator itr = mpCache->maValues.rbegin(),
+ itrEnd = mpCache->maValues.rend(); itr != itrEnd; ++itr)
+ {
+ if(nCells >= nLimitCells)
+ return false;
+ if(itr->first <= nArg)
+ return true;
+ nCells += itr->second;
+ }
+
+ return true;
+}
+
+bool ScConditionEntry::IsBottomNPercent( double nArg ) const
+{
+ FillCache();
+
+ size_t nCells = 0;
+ size_t nLimitCells = static_cast<size_t>(mpCache->nValueItems*nVal1/100);
+ for(const auto& [rVal, rCount] : mpCache->maValues)
+ {
+ if(nCells >= nLimitCells)
+ return false;
+ if(rVal >= nArg)
+ return true;
+ nCells += rCount;
+ }
+
+ return true;
+}
+
+bool ScConditionEntry::IsBelowAverage( double nArg, bool bEqual ) const
+{
+ FillCache();
+
+ double nSum = std::accumulate(mpCache->maValues.begin(), mpCache->maValues.end(), double(0),
+ [](const double& rSum, const ScConditionEntryCache::ValueCacheType::value_type& rEntry) {
+ return rSum + rEntry.first * rEntry.second; });
+
+ if(bEqual)
+ return (nArg <= nSum/mpCache->nValueItems);
+ else
+ return (nArg < nSum/mpCache->nValueItems);
+}
+
+bool ScConditionEntry::IsAboveAverage( double nArg, bool bEqual ) const
+{
+ FillCache();
+
+ double nSum = std::accumulate(mpCache->maValues.begin(), mpCache->maValues.end(), double(0),
+ [](const double& rSum, const ScConditionEntryCache::ValueCacheType::value_type& rEntry) {
+ return rSum + rEntry.first * rEntry.second; });
+
+ if(bEqual)
+ return (nArg >= nSum/mpCache->nValueItems);
+ else
+ return (nArg > nSum/mpCache->nValueItems);
+}
+
+bool ScConditionEntry::IsError( const ScAddress& rPos ) const
+{
+ ScRefCellValue rCell(*mpDoc, rPos);
+
+ if (rCell.meType == CELLTYPE_FORMULA)
+ {
+ if (rCell.mpFormula->GetErrCode() != FormulaError::NONE)
+ return true;
+ }
+
+ return false;
+}
+
+bool ScConditionEntry::IsValid( double nArg, const ScAddress& rPos ) const
+{
+ // Interpret must already have been called
+ if ( bIsStr1 )
+ {
+ switch( eOp )
+ {
+ case ScConditionMode::BeginsWith:
+ case ScConditionMode::EndsWith:
+ case ScConditionMode::ContainsText:
+ case ScConditionMode::NotContainsText:
+ break;
+ case ScConditionMode::NotEqual:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ if ( eOp == ScConditionMode::Between || eOp == ScConditionMode::NotBetween )
+ if ( bIsStr2 )
+ return false;
+
+ double nComp1 = nVal1; // Copy, so that it can be changed
+ double nComp2 = nVal2;
+
+ if ( eOp == ScConditionMode::Between || eOp == ScConditionMode::NotBetween )
+ if ( nComp1 > nComp2 )
+ {
+ // Right order for value range
+ double nTemp = nComp1; nComp1 = nComp2; nComp2 = nTemp;
+ }
+
+ // All corner cases need to be tested with ::rtl::math::approxEqual!
+ bool bValid = false;
+ switch (eOp)
+ {
+ case ScConditionMode::NONE:
+ break; // Always sal_False
+ case ScConditionMode::Equal:
+ bValid = ::rtl::math::approxEqual( nArg, nComp1 );
+ break;
+ case ScConditionMode::NotEqual:
+ bValid = !::rtl::math::approxEqual( nArg, nComp1 );
+ break;
+ case ScConditionMode::Greater:
+ bValid = ( nArg > nComp1 ) && !::rtl::math::approxEqual( nArg, nComp1 );
+ break;
+ case ScConditionMode::EqGreater:
+ bValid = ( nArg >= nComp1 ) || ::rtl::math::approxEqual( nArg, nComp1 );
+ break;
+ case ScConditionMode::Less:
+ bValid = ( nArg < nComp1 ) && !::rtl::math::approxEqual( nArg, nComp1 );
+ break;
+ case ScConditionMode::EqLess:
+ bValid = ( nArg <= nComp1 ) || ::rtl::math::approxEqual( nArg, nComp1 );
+ break;
+ case ScConditionMode::Between:
+ bValid = ( nArg >= nComp1 && nArg <= nComp2 ) ||
+ ::rtl::math::approxEqual( nArg, nComp1 ) || ::rtl::math::approxEqual( nArg, nComp2 );
+ break;
+ case ScConditionMode::NotBetween:
+ bValid = ( nArg < nComp1 || nArg > nComp2 ) &&
+ !::rtl::math::approxEqual( nArg, nComp1 ) && !::rtl::math::approxEqual( nArg, nComp2 );
+ break;
+ case ScConditionMode::Duplicate:
+ case ScConditionMode::NotDuplicate:
+ if( pCondFormat )
+ {
+ bValid = IsDuplicate( nArg, OUString() );
+ if( eOp == ScConditionMode::NotDuplicate )
+ bValid = !bValid;
+ }
+ break;
+ case ScConditionMode::Direct:
+ bValid = nComp1 != 0.0;
+ break;
+ case ScConditionMode::Top10:
+ bValid = IsTopNElement( nArg );
+ break;
+ case ScConditionMode::Bottom10:
+ bValid = IsBottomNElement( nArg );
+ break;
+ case ScConditionMode::TopPercent:
+ bValid = IsTopNPercent( nArg );
+ break;
+ case ScConditionMode::BottomPercent:
+ bValid = IsBottomNPercent( nArg );
+ break;
+ case ScConditionMode::AboveAverage:
+ case ScConditionMode::AboveEqualAverage:
+ bValid = IsAboveAverage( nArg, eOp == ScConditionMode::AboveEqualAverage );
+ break;
+ case ScConditionMode::BelowAverage:
+ case ScConditionMode::BelowEqualAverage:
+ bValid = IsBelowAverage( nArg, eOp == ScConditionMode::BelowEqualAverage );
+ break;
+ case ScConditionMode::Error:
+ case ScConditionMode::NoError:
+ bValid = IsError( rPos );
+ if( eOp == ScConditionMode::NoError )
+ bValid = !bValid;
+ break;
+ case ScConditionMode::BeginsWith:
+ if(aStrVal1.isEmpty())
+ {
+ OUString aStr = OUString::number(nVal1);
+ OUString aStr2 = OUString::number(nArg);
+ bValid = aStr2.startsWith(aStr);
+ }
+ else
+ {
+ OUString aStr2 = OUString::number(nArg);
+ bValid = aStr2.startsWith(aStrVal1);
+ }
+ break;
+ case ScConditionMode::EndsWith:
+ if(aStrVal1.isEmpty())
+ {
+ OUString aStr = OUString::number(nVal1);
+ OUString aStr2 = OUString::number(nArg);
+ bValid = aStr2.endsWith(aStr);
+ }
+ else
+ {
+ OUString aStr2 = OUString::number(nArg);
+ bValid = aStr2.endsWith(aStrVal1);
+ }
+ break;
+ case ScConditionMode::ContainsText:
+ case ScConditionMode::NotContainsText:
+ if(aStrVal1.isEmpty())
+ {
+ OUString aStr = OUString::number(nVal1);
+ OUString aStr2 = OUString::number(nArg);
+ bValid = aStr2.indexOf(aStr) != -1;
+ }
+ else
+ {
+ OUString aStr2 = OUString::number(nArg);
+ bValid = aStr2.indexOf(aStrVal1) != -1;
+ }
+
+ if( eOp == ScConditionMode::NotContainsText )
+ bValid = !bValid;
+ break;
+ default:
+ SAL_WARN("sc", "unknown operation at ScConditionEntry");
+ break;
+ }
+ return bValid;
+}
+
+bool ScConditionEntry::IsValidStr( const OUString& rArg, const ScAddress& rPos ) const
+{
+ bool bValid = false;
+ // Interpret must already have been called
+ if ( eOp == ScConditionMode::Direct ) // Formula is independent from the content
+ return nVal1 != 0.0;
+
+ if ( eOp == ScConditionMode::Duplicate || eOp == ScConditionMode::NotDuplicate )
+ {
+ if( pCondFormat && !rArg.isEmpty() )
+ {
+ bValid = IsDuplicate( 0.0, rArg );
+ if( eOp == ScConditionMode::NotDuplicate )
+ bValid = !bValid;
+ return bValid;
+ }
+ }
+
+ // If number contains condition, always false, except for "not equal".
+ if ( !bIsStr1 && (eOp != ScConditionMode::Error && eOp != ScConditionMode::NoError) )
+ return ( eOp == ScConditionMode::NotEqual );
+ if ( eOp == ScConditionMode::Between || eOp == ScConditionMode::NotBetween )
+ if ( !bIsStr2 )
+ return false;
+
+ OUString aUpVal1( aStrVal1 ); //TODO: As a member? (Also set in Interpret)
+ OUString aUpVal2( aStrVal2 );
+
+ if ( eOp == ScConditionMode::Between || eOp == ScConditionMode::NotBetween )
+ if (ScGlobal::GetCollator().compareString( aUpVal1, aUpVal2 ) > 0)
+ {
+ // Right order for value range
+ OUString aTemp( aUpVal1 ); aUpVal1 = aUpVal2; aUpVal2 = aTemp;
+ }
+
+ switch ( eOp )
+ {
+ case ScConditionMode::Equal:
+ bValid = (ScGlobal::GetCollator().compareString(
+ rArg, aUpVal1 ) == 0);
+ break;
+ case ScConditionMode::NotEqual:
+ bValid = (ScGlobal::GetCollator().compareString(
+ rArg, aUpVal1 ) != 0);
+ break;
+ case ScConditionMode::TopPercent:
+ case ScConditionMode::BottomPercent:
+ case ScConditionMode::Top10:
+ case ScConditionMode::Bottom10:
+ case ScConditionMode::AboveAverage:
+ case ScConditionMode::BelowAverage:
+ return false;
+ case ScConditionMode::Error:
+ case ScConditionMode::NoError:
+ bValid = IsError( rPos );
+ if(eOp == ScConditionMode::NoError)
+ bValid = !bValid;
+ break;
+ case ScConditionMode::BeginsWith:
+ bValid = ScGlobal::GetTransliteration().isMatch(aUpVal1, rArg);
+ break;
+ case ScConditionMode::EndsWith:
+ {
+ sal_Int32 nStart = rArg.getLength();
+ const sal_Int32 nLen = aUpVal1.getLength();
+ if (nLen > nStart)
+ bValid = false;
+ else
+ {
+ nStart = nStart - nLen;
+ sal_Int32 nMatch1(0), nMatch2(0);
+ bValid = ScGlobal::GetTransliteration().equals(rArg, nStart, nLen, nMatch1,
+ aUpVal1, 0, nLen, nMatch2);
+ }
+ }
+ break;
+ case ScConditionMode::ContainsText:
+ case ScConditionMode::NotContainsText:
+ {
+ const OUString aArgStr(ScGlobal::getCharClass().lowercase(rArg));
+ const OUString aValStr(ScGlobal::getCharClass().lowercase(aUpVal1));
+ bValid = aArgStr.indexOf(aValStr) != -1;
+
+ if(eOp == ScConditionMode::NotContainsText)
+ bValid = !bValid;
+ }
+ break;
+ default:
+ {
+ sal_Int32 nCompare = ScGlobal::GetCollator().compareString(
+ rArg, aUpVal1 );
+ switch ( eOp )
+ {
+ case ScConditionMode::Greater:
+ bValid = ( nCompare > 0 );
+ break;
+ case ScConditionMode::EqGreater:
+ bValid = ( nCompare >= 0 );
+ break;
+ case ScConditionMode::Less:
+ bValid = ( nCompare < 0 );
+ break;
+ case ScConditionMode::EqLess:
+ bValid = ( nCompare <= 0 );
+ break;
+ case ScConditionMode::Between:
+ case ScConditionMode::NotBetween:
+ // Test for NOTBETWEEN:
+ bValid = ( nCompare < 0 ||
+ ScGlobal::GetCollator().compareString( rArg,
+ aUpVal2 ) > 0 );
+ if ( eOp == ScConditionMode::Between )
+ bValid = !bValid;
+ break;
+ // ScConditionMode::Direct already handled above
+ default:
+ SAL_WARN("sc", "unknown operation in ScConditionEntry");
+ bValid = false;
+ break;
+ }
+ }
+ }
+ return bValid;
+}
+
+bool ScConditionEntry::IsCellValid( ScRefCellValue& rCell, const ScAddress& rPos ) const
+{
+ const_cast<ScConditionEntry*>(this)->Interpret(rPos); // Evaluate formula
+
+ if ( eOp == ScConditionMode::Direct )
+ return nVal1 != 0.0;
+
+ double nArg = 0.0;
+ OUString aArgStr;
+ bool bVal = lcl_GetCellContent( rCell, bIsStr1, nArg, aArgStr, mpDoc );
+ if (bVal)
+ return IsValid( nArg, rPos );
+ else
+ return IsValidStr( aArgStr, rPos );
+}
+
+OUString ScConditionEntry::GetExpression( const ScAddress& rCursor, sal_uInt16 nIndex,
+ sal_uInt32 nNumFmt,
+ const FormulaGrammar::Grammar eGrammar ) const
+{
+ assert( nIndex <= 1);
+ OUString aRet;
+
+ if ( FormulaGrammar::isEnglish( eGrammar) && nNumFmt == 0 )
+ nNumFmt = mpDoc->GetFormatTable()->GetStandardIndex( LANGUAGE_ENGLISH_US );
+
+ if ( nIndex==0 )
+ {
+ if ( pFormula1 )
+ {
+ ScCompiler aComp(*mpDoc, rCursor, *pFormula1, eGrammar);
+ OUStringBuffer aBuffer;
+ aComp.CreateStringFromTokenArray( aBuffer );
+ aRet = aBuffer.makeStringAndClear();
+ }
+ else if (bIsStr1)
+ {
+ aRet = "\"" + aStrVal1 + "\"";
+ }
+ else
+ mpDoc->GetFormatTable()->GetInputLineString(nVal1, nNumFmt, aRet);
+ }
+ else if ( nIndex==1 )
+ {
+ if ( pFormula2 )
+ {
+ ScCompiler aComp(*mpDoc, rCursor, *pFormula2, eGrammar);
+ OUStringBuffer aBuffer;
+ aComp.CreateStringFromTokenArray( aBuffer );
+ aRet = aBuffer.makeStringAndClear();
+ }
+ else if (bIsStr2)
+ {
+ aRet = "\"" + aStrVal2 + "\"";
+ }
+ else
+ mpDoc->GetFormatTable()->GetInputLineString(nVal2, nNumFmt, aRet);
+ }
+
+ return aRet;
+}
+
+std::unique_ptr<ScTokenArray> ScConditionEntry::CreateFlatCopiedTokenArray( sal_uInt16 nIndex ) const
+{
+ assert(nIndex <= 1);
+ std::unique_ptr<ScTokenArray> pRet;
+
+ if ( nIndex==0 )
+ {
+ if ( pFormula1 )
+ pRet.reset(new ScTokenArray( *pFormula1 ));
+ else
+ {
+ pRet.reset(new ScTokenArray(*mpDoc));
+ if (bIsStr1)
+ {
+ svl::SharedStringPool& rSPool = mpDoc->GetSharedStringPool();
+ pRet->AddString(rSPool.intern(aStrVal1));
+ }
+ else
+ pRet->AddDouble( nVal1 );
+ }
+ }
+ else if ( nIndex==1 )
+ {
+ if ( pFormula2 )
+ pRet.reset(new ScTokenArray( *pFormula2 ));
+ else
+ {
+ pRet.reset(new ScTokenArray(*mpDoc));
+ if (bIsStr2)
+ {
+ svl::SharedStringPool& rSPool = mpDoc->GetSharedStringPool();
+ pRet->AddString(rSPool.intern(aStrVal2));
+ }
+ else
+ pRet->AddDouble( nVal2 );
+ }
+ }
+
+ return pRet;
+}
+
+/**
+ * Return a position that's adjusted to allow textual representation
+ * of expressions if possible
+ */
+ScAddress ScConditionEntry::GetValidSrcPos() const
+{
+ SCTAB nMinTab = aSrcPos.Tab();
+ SCTAB nMaxTab = nMinTab;
+
+ for (sal_uInt16 nPass = 0; nPass < 2; nPass++)
+ {
+ ScTokenArray* pFormula = nPass ? pFormula2.get() : pFormula1.get();
+ if (pFormula)
+ {
+ for ( auto t: pFormula->References() )
+ {
+ ScSingleRefData& rRef1 = *t->GetSingleRef();
+ ScAddress aAbs = rRef1.toAbs(*mpDoc, aSrcPos);
+ if (!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(*mpDoc, aSrcPos);
+ if (!rRef2.IsTabDeleted())
+ {
+ if (aAbs.Tab() < nMinTab)
+ nMinTab = aAbs.Tab();
+ if (aAbs.Tab() > nMaxTab)
+ nMaxTab = aAbs.Tab();
+ }
+ }
+ }
+ }
+ }
+
+ ScAddress aValidPos = aSrcPos;
+ SCTAB nTabCount = mpDoc->GetTableCount();
+ if ( nMaxTab >= nTabCount && nMinTab > 0 )
+ aValidPos.SetTab( aSrcPos.Tab() - nMinTab ); // so the lowest tab ref will be on 0
+
+ if ( aValidPos.Tab() >= nTabCount )
+ aValidPos.SetTab( nTabCount - 1 ); // ensure a valid position even if some references will be invalid
+
+ return aValidPos;
+}
+
+void ScConditionEntry::DataChanged() const
+{
+ //FIXME: Nothing so far
+}
+
+bool ScConditionEntry::MarkUsedExternalReferences() const
+{
+ bool bAllMarked = false;
+ for (sal_uInt16 nPass = 0; !bAllMarked && nPass < 2; nPass++)
+ {
+ ScTokenArray* pFormula = nPass ? pFormula2.get() : pFormula1.get();
+ if (pFormula)
+ bAllMarked = mpDoc->MarkUsedExternalReferences(*pFormula, aSrcPos);
+ }
+ return bAllMarked;
+}
+
+ScFormatEntry* ScConditionEntry::Clone(ScDocument* pDoc) const
+{
+ return new ScConditionEntry(*pDoc, *this);
+}
+
+ScConditionMode ScConditionEntry::GetModeFromApi(css::sheet::ConditionOperator nOperation)
+{
+ ScConditionMode eMode = ScConditionMode::NONE;
+ switch (static_cast<sal_Int32>(nOperation))
+ {
+ case css::sheet::ConditionOperator2::EQUAL:
+ eMode = ScConditionMode::Equal;
+ break;
+ case css::sheet::ConditionOperator2::LESS:
+ eMode = ScConditionMode::Less;
+ break;
+ case css::sheet::ConditionOperator2::GREATER:
+ eMode = ScConditionMode::Greater;
+ break;
+ case css::sheet::ConditionOperator2::LESS_EQUAL:
+ eMode = ScConditionMode::EqLess;
+ break;
+ case css::sheet::ConditionOperator2::GREATER_EQUAL:
+ eMode = ScConditionMode::EqGreater;
+ break;
+ case css::sheet::ConditionOperator2::NOT_EQUAL:
+ eMode = ScConditionMode::NotEqual;
+ break;
+ case css::sheet::ConditionOperator2::BETWEEN:
+ eMode = ScConditionMode::Between;
+ break;
+ case css::sheet::ConditionOperator2::NOT_BETWEEN:
+ eMode = ScConditionMode::NotBetween;
+ break;
+ case css::sheet::ConditionOperator2::FORMULA:
+ eMode = ScConditionMode::Direct;
+ break;
+ case css::sheet::ConditionOperator2::DUPLICATE:
+ eMode = ScConditionMode::Duplicate;
+ break;
+ case css::sheet::ConditionOperator2::NOT_DUPLICATE:
+ eMode = ScConditionMode::NotDuplicate;
+ break;
+ default:
+ break;
+ }
+ return eMode;
+}
+
+void ScConditionEntry::startRendering()
+{
+ mpCache.reset();
+}
+
+void ScConditionEntry::endRendering()
+{
+ mpCache.reset();
+}
+
+bool ScConditionEntry::NeedsRepaint() const
+{
+ return mpListener->NeedsRepaint();
+}
+
+ScCondFormatEntry::ScCondFormatEntry( ScConditionMode eOper,
+ const OUString& rExpr1, const OUString& rExpr2,
+ ScDocument& rDocument, const ScAddress& rPos,
+ const OUString& rStyle,
+ const OUString& rExprNmsp1, const OUString& rExprNmsp2,
+ FormulaGrammar::Grammar eGrammar1,
+ FormulaGrammar::Grammar eGrammar2,
+ ScFormatEntry::Type eType ) :
+ ScConditionEntry( eOper, rExpr1, rExpr2, rDocument, rPos, rExprNmsp1, rExprNmsp2, eGrammar1, eGrammar2, eType ),
+ aStyleName( rStyle ),
+ eCondFormatType( eType )
+{
+}
+
+ScCondFormatEntry::ScCondFormatEntry( ScConditionMode eOper,
+ const ScTokenArray* pArr1, const ScTokenArray* pArr2,
+ ScDocument& rDocument, const ScAddress& rPos,
+ const OUString& rStyle ) :
+ ScConditionEntry( eOper, pArr1, pArr2, rDocument, rPos ),
+ aStyleName( rStyle )
+{
+}
+
+ScCondFormatEntry::ScCondFormatEntry( const ScCondFormatEntry& r ) :
+ ScConditionEntry( r ),
+ aStyleName( r.aStyleName ),
+ eCondFormatType( r.eCondFormatType)
+{
+}
+
+ScCondFormatEntry::ScCondFormatEntry( ScDocument& rDocument, const ScCondFormatEntry& r ) :
+ ScConditionEntry( rDocument, r ),
+ aStyleName( r.aStyleName ),
+ eCondFormatType( r.eCondFormatType)
+{
+}
+
+// virtual
+bool ScCondFormatEntry::IsEqual( const ScFormatEntry& r, bool bIgnoreSrcPos ) const
+{
+ return ScConditionEntry::IsEqual(r, bIgnoreSrcPos) &&
+ (aStyleName == static_cast<const ScCondFormatEntry&>(r).aStyleName);
+}
+
+ScCondFormatEntry::~ScCondFormatEntry()
+{
+}
+
+void ScCondFormatEntry::DataChanged() const
+{
+ if ( pCondFormat )
+ pCondFormat->DoRepaint();
+}
+
+ScFormatEntry* ScCondFormatEntry::Clone( ScDocument* pDoc ) const
+{
+ return new ScCondFormatEntry( *pDoc, *this );
+}
+
+void ScConditionEntry::CalcAll()
+{
+ if (pFCell1 || pFCell2)
+ {
+ if (pFCell1)
+ pFCell1->SetDirty();
+ if (pFCell2)
+ pFCell2->SetDirty();
+ pCondFormat->DoRepaint();
+ }
+}
+
+ScCondDateFormatEntry::ScCondDateFormatEntry( ScDocument* pDoc )
+ : ScFormatEntry( pDoc )
+ , meType(condformat::TODAY)
+{
+}
+
+ScCondDateFormatEntry::ScCondDateFormatEntry( ScDocument* pDoc, const ScCondDateFormatEntry& rFormat ):
+ ScFormatEntry( pDoc ),
+ meType( rFormat.meType ),
+ maStyleName( rFormat.maStyleName )
+{
+}
+
+bool ScCondDateFormatEntry::IsValid( const ScAddress& rPos ) const
+{
+ ScRefCellValue rCell(*mpDoc, rPos);
+
+ if (!rCell.hasNumeric())
+ // non-numerical cell.
+ return false;
+
+ if( !mpCache )
+ mpCache.reset( new Date( Date::SYSTEM ) );
+
+ const Date& rActDate = *mpCache;
+ SvNumberFormatter* pFormatter = mpDoc->GetFormatTable();
+ sal_Int32 nCurrentDate = rActDate - pFormatter->GetNullDate();
+
+ double nVal = rCell.getValue();
+ sal_Int32 nCellDate = static_cast<sal_Int32>(::rtl::math::approxFloor(nVal));
+ Date aCellDate = pFormatter->GetNullDate();
+ aCellDate.AddDays(nCellDate);
+
+ switch(meType)
+ {
+ case condformat::TODAY:
+ if( nCurrentDate == nCellDate )
+ return true;
+ break;
+ case condformat::TOMORROW:
+ if( nCurrentDate == nCellDate -1 )
+ return true;
+ break;
+ case condformat::YESTERDAY:
+ if( nCurrentDate == nCellDate + 1)
+ return true;
+ break;
+ case condformat::LAST7DAYS:
+ if( nCurrentDate >= nCellDate && nCurrentDate - 7 < nCellDate )
+ return true;
+ break;
+ case condformat::LASTWEEK:
+ {
+ const DayOfWeek eDay = rActDate.GetDayOfWeek();
+ if( eDay != SUNDAY )
+ {
+ Date aBegin(rActDate - (8 + static_cast<sal_Int32>(eDay)));
+ Date aEnd(rActDate - (2 + static_cast<sal_Int32>(eDay)));
+ return aCellDate.IsBetween( aBegin, aEnd );
+ }
+ else
+ {
+ Date aBegin(rActDate - 8);
+ Date aEnd(rActDate - 1);
+ return aCellDate.IsBetween( aBegin, aEnd );
+ }
+ }
+ break;
+ case condformat::THISWEEK:
+ {
+ const DayOfWeek eDay = rActDate.GetDayOfWeek();
+ if( eDay != SUNDAY )
+ {
+ Date aBegin(rActDate - (1 + static_cast<sal_Int32>(eDay)));
+ Date aEnd(rActDate + (5 - static_cast<sal_Int32>(eDay)));
+ return aCellDate.IsBetween( aBegin, aEnd );
+ }
+ else
+ {
+ Date aEnd( rActDate + 6);
+ return aCellDate.IsBetween( rActDate, aEnd );
+ }
+ }
+ break;
+ case condformat::NEXTWEEK:
+ {
+ const DayOfWeek eDay = rActDate.GetDayOfWeek();
+ if( eDay != SUNDAY )
+ {
+ return aCellDate.IsBetween( rActDate + (6 - static_cast<sal_Int32>(eDay)),
+ rActDate + (12 - static_cast<sal_Int32>(eDay)) );
+ }
+ else
+ {
+ return aCellDate.IsBetween( rActDate + 7, rActDate + 13 );
+ }
+ }
+ break;
+ case condformat::LASTMONTH:
+ if( rActDate.GetMonth() == 1 )
+ {
+ if( aCellDate.GetMonth() == 12 && rActDate.GetYear() == aCellDate.GetNextYear() )
+ return true;
+ }
+ else if( rActDate.GetYear() == aCellDate.GetYear() )
+ {
+ if( rActDate.GetMonth() == aCellDate.GetMonth() + 1)
+ return true;
+ }
+ break;
+ case condformat::THISMONTH:
+ if( rActDate.GetYear() == aCellDate.GetYear() )
+ {
+ if( rActDate.GetMonth() == aCellDate.GetMonth() )
+ return true;
+ }
+ break;
+ case condformat::NEXTMONTH:
+ if( rActDate.GetMonth() == 12 )
+ {
+ if( aCellDate.GetMonth() == 1 && rActDate.GetYear() == aCellDate.GetYear() - 1 )
+ return true;
+ }
+ else if( rActDate.GetYear() == aCellDate.GetYear() )
+ {
+ if( rActDate.GetMonth() == aCellDate.GetMonth() - 1)
+ return true;
+ }
+ break;
+ case condformat::LASTYEAR:
+ if( rActDate.GetYear() == aCellDate.GetNextYear() )
+ return true;
+ break;
+ case condformat::THISYEAR:
+ if( rActDate.GetYear() == aCellDate.GetYear() )
+ return true;
+ break;
+ case condformat::NEXTYEAR:
+ if( rActDate.GetYear() == aCellDate.GetYear() - 1 )
+ return true;
+ break;
+ }
+
+ return false;
+}
+
+void ScCondDateFormatEntry::SetDateType( condformat::ScCondFormatDateType eType )
+{
+ meType = eType;
+}
+
+void ScCondDateFormatEntry::SetStyleName( const OUString& rStyleName )
+{
+ maStyleName = rStyleName;
+}
+
+ScFormatEntry* ScCondDateFormatEntry::Clone( ScDocument* pDoc ) const
+{
+ return new ScCondDateFormatEntry( pDoc, *this );
+}
+
+void ScCondDateFormatEntry::startRendering()
+{
+ mpCache.reset();
+}
+
+void ScCondDateFormatEntry::endRendering()
+{
+ mpCache.reset();
+}
+
+ScConditionalFormat::ScConditionalFormat(sal_uInt32 nNewKey, ScDocument* pDocument) :
+ pDoc( pDocument ),
+ nKey( nNewKey )
+{
+}
+
+std::unique_ptr<ScConditionalFormat> ScConditionalFormat::Clone(ScDocument* pNewDoc) const
+{
+ // Real copy of the formula (for Ref Undo/between documents)
+ if (!pNewDoc)
+ pNewDoc = pDoc;
+
+ std::unique_ptr<ScConditionalFormat> pNew(new ScConditionalFormat(nKey, pNewDoc));
+ pNew->SetRange( maRanges ); // prerequisite for listeners
+
+ for (const auto& rxEntry : maEntries)
+ {
+ ScFormatEntry* pNewEntry = rxEntry->Clone(pNewDoc);
+ pNew->maEntries.push_back( std::unique_ptr<ScFormatEntry>(pNewEntry) );
+ pNewEntry->SetParent(pNew.get());
+ }
+
+ return pNew;
+}
+
+bool ScConditionalFormat::EqualEntries( const ScConditionalFormat& r, bool bIgnoreSrcPos ) const
+{
+ if( size() != r.size())
+ return false;
+
+ //TODO: Test for same entries in reverse order?
+ if (! std::equal(maEntries.begin(), maEntries.end(), r.maEntries.begin(),
+ [&bIgnoreSrcPos](const std::unique_ptr<ScFormatEntry>& p1, const std::unique_ptr<ScFormatEntry>& p2) -> bool
+ {
+ return p1->IsEqual(*p2, bIgnoreSrcPos);
+ }))
+ return false;
+
+ // right now don't check for same range
+ // we only use this method to merge same conditional formats from
+ // old ODF data structure
+ return true;
+}
+
+void ScConditionalFormat::SetRange( const ScRangeList& rRanges )
+{
+ maRanges = rRanges;
+ SAL_WARN_IF(maRanges.empty(), "sc", "the conditional format range is empty! will result in a crash later!");
+}
+
+void ScConditionalFormat::AddEntry( ScFormatEntry* pNew )
+{
+ maEntries.push_back( std::unique_ptr<ScFormatEntry>(pNew));
+ pNew->SetParent(this);
+}
+
+void ScConditionalFormat::RemoveEntry(size_t n)
+{
+ if (n < maEntries.size())
+ {
+ maEntries.erase(maEntries.begin() + n);
+ DoRepaint();
+ }
+}
+
+bool ScConditionalFormat::IsEmpty() const
+{
+ return maEntries.empty();
+}
+
+size_t ScConditionalFormat::size() const
+{
+ return maEntries.size();
+}
+
+ScDocument* ScConditionalFormat::GetDocument()
+{
+ return pDoc;
+}
+
+ScConditionalFormat::~ScConditionalFormat()
+{
+}
+
+const ScFormatEntry* ScConditionalFormat::GetEntry( sal_uInt16 nPos ) const
+{
+ if ( nPos < size() )
+ return maEntries[nPos].get();
+ else
+ return nullptr;
+}
+
+OUString ScConditionalFormat::GetCellStyle( ScRefCellValue& rCell, const ScAddress& rPos ) const
+{
+ for (const auto& rxEntry : maEntries)
+ {
+ if(rxEntry->GetType() == ScFormatEntry::Type::Condition ||
+ rxEntry->GetType() == ScFormatEntry::Type::ExtCondition)
+ {
+ const ScCondFormatEntry& rEntry = static_cast<const ScCondFormatEntry&>(*rxEntry);
+ if (rEntry.IsCellValid(rCell, rPos))
+ return rEntry.GetStyle();
+ }
+ else if(rxEntry->GetType() == ScFormatEntry::Type::Date)
+ {
+ const ScCondDateFormatEntry& rEntry = static_cast<const ScCondDateFormatEntry&>(*rxEntry);
+ if (rEntry.IsValid( rPos ))
+ return rEntry.GetStyleName();
+ }
+ }
+
+ return OUString();
+}
+
+ScCondFormatData ScConditionalFormat::GetData( ScRefCellValue& rCell, const ScAddress& rPos ) const
+{
+ ScCondFormatData aData;
+ for(const auto& rxEntry : maEntries)
+ {
+ if( (rxEntry->GetType() == ScFormatEntry::Type::Condition ||
+ rxEntry->GetType() == ScFormatEntry::Type::ExtCondition) &&
+ aData.aStyleName.isEmpty())
+ {
+ const ScCondFormatEntry& rEntry = static_cast<const ScCondFormatEntry&>(*rxEntry);
+ if (rEntry.IsCellValid(rCell, rPos))
+ aData.aStyleName = rEntry.GetStyle();
+ }
+ else if(rxEntry->GetType() == ScFormatEntry::Type::Colorscale && !aData.mxColorScale)
+ {
+ const ScColorScaleFormat& rEntry = static_cast<const ScColorScaleFormat&>(*rxEntry);
+ aData.mxColorScale = rEntry.GetColor(rPos);
+ }
+ else if(rxEntry->GetType() == ScFormatEntry::Type::Databar && !aData.pDataBar)
+ {
+ const ScDataBarFormat& rEntry = static_cast<const ScDataBarFormat&>(*rxEntry);
+ aData.pDataBar = rEntry.GetDataBarInfo(rPos);
+ }
+ else if(rxEntry->GetType() == ScFormatEntry::Type::Iconset && !aData.pIconSet)
+ {
+ const ScIconSetFormat& rEntry = static_cast<const ScIconSetFormat&>(*rxEntry);
+ aData.pIconSet = rEntry.GetIconSetInfo(rPos);
+ }
+ else if(rxEntry->GetType() == ScFormatEntry::Type::Date && aData.aStyleName.isEmpty())
+ {
+ const ScCondDateFormatEntry& rEntry = static_cast<const ScCondDateFormatEntry&>(*rxEntry);
+ if ( rEntry.IsValid( rPos ) )
+ aData.aStyleName = rEntry.GetStyleName();
+ }
+ }
+ return aData;
+}
+
+void ScConditionalFormat::DoRepaint()
+{
+ // all conditional format cells
+ pDoc->RepaintRange( maRanges );
+}
+
+void ScConditionalFormat::CompileAll()
+{
+ for(auto& rxEntry : maEntries)
+ if(rxEntry->GetType() == ScFormatEntry::Type::Condition ||
+ rxEntry->GetType() == ScFormatEntry::Type::ExtCondition)
+ static_cast<ScCondFormatEntry&>(*rxEntry).CompileAll();
+}
+
+void ScConditionalFormat::CompileXML()
+{
+ for(auto& rxEntry : maEntries)
+ if(rxEntry->GetType() == ScFormatEntry::Type::Condition ||
+ rxEntry->GetType() == ScFormatEntry::Type::ExtCondition)
+ static_cast<ScCondFormatEntry&>(*rxEntry).CompileXML();
+}
+
+void ScConditionalFormat::UpdateReference( sc::RefUpdateContext& rCxt, bool bCopyAsMove )
+{
+ if (rCxt.meMode == URM_COPY && bCopyAsMove)
+ {
+ // ScConditionEntry::UpdateReference() obtains its aSrcPos from
+ // maRanges and does not update it on URM_COPY, but it's needed later
+ // for the moved position, so update maRanges beforehand.
+ maRanges.UpdateReference(URM_MOVE, pDoc, rCxt.maRange, rCxt.mnColDelta, rCxt.mnRowDelta, rCxt.mnTabDelta);
+ for (auto& rxEntry : maEntries)
+ rxEntry->UpdateReference(rCxt);
+ }
+ else
+ {
+ for (auto& rxEntry : maEntries)
+ rxEntry->UpdateReference(rCxt);
+ maRanges.UpdateReference(rCxt.meMode, pDoc, rCxt.maRange, rCxt.mnColDelta, rCxt.mnRowDelta, rCxt.mnTabDelta);
+ }
+}
+
+void ScConditionalFormat::InsertRow(SCTAB nTab, SCCOL nColStart, SCCOL nColEnd, SCROW nRowPos, SCSIZE nSize)
+{
+ maRanges.InsertRow(nTab, nColStart, nColEnd, nRowPos, nSize);
+}
+
+void ScConditionalFormat::InsertCol(SCTAB nTab, SCROW nRowStart, SCROW nRowEnd, SCCOL nColPos, SCSIZE nSize)
+{
+ maRanges.InsertCol(nTab, nRowStart, nRowEnd, nColPos, nSize);
+}
+
+void ScConditionalFormat::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt )
+{
+ for (size_t i = 0, n = maRanges.size(); i < n; ++i)
+ {
+ // We assume that the start and end sheet indices are equal.
+ ScRange & rRange = maRanges[i];
+ SCTAB nTab = rRange.aStart.Tab();
+
+ if (nTab < rCxt.mnInsertPos)
+ // Unaffected.
+ continue;
+
+ rRange.aStart.IncTab(rCxt.mnSheets);
+ rRange.aEnd.IncTab(rCxt.mnSheets);
+ }
+
+ for (auto& rxEntry : maEntries)
+ rxEntry->UpdateInsertTab(rCxt);
+}
+
+void ScConditionalFormat::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt )
+{
+ for (size_t i = 0, n = maRanges.size(); i < n; ++i)
+ {
+ // We assume that the start and end sheet indices are equal.
+ ScRange & rRange = maRanges[i];
+ SCTAB nTab = rRange.aStart.Tab();
+
+ if (nTab < rCxt.mnDeletePos)
+ // Left of the deleted sheet(s). Unaffected.
+ continue;
+
+ if (nTab <= rCxt.mnDeletePos+rCxt.mnSheets-1)
+ {
+ // On the deleted sheet(s).
+ rRange.aStart.SetTab(-1);
+ rRange.aEnd.SetTab(-1);
+ continue;
+ }
+
+ // Right of the deleted sheet(s). Adjust the sheet indices.
+ rRange.aStart.IncTab(-1*rCxt.mnSheets);
+ rRange.aEnd.IncTab(-1*rCxt.mnSheets);
+ }
+
+ for (auto& rxEntry : maEntries)
+ rxEntry->UpdateDeleteTab(rCxt);
+}
+
+void ScConditionalFormat::UpdateMoveTab( sc::RefUpdateMoveTabContext& rCxt )
+{
+ size_t n = maRanges.size();
+ SCTAB nMinTab = std::min<SCTAB>(rCxt.mnOldPos, rCxt.mnNewPos);
+ SCTAB nMaxTab = std::max<SCTAB>(rCxt.mnOldPos, rCxt.mnNewPos);
+ for(size_t i = 0; i < n; ++i)
+ {
+ ScRange & rRange = maRanges[i];
+ SCTAB nTab = rRange.aStart.Tab();
+ if(nTab < nMinTab || nTab > nMaxTab)
+ {
+ continue;
+ }
+
+ if (nTab == rCxt.mnOldPos)
+ {
+ rRange.aStart.SetTab(rCxt.mnNewPos);
+ rRange.aEnd.SetTab(rCxt.mnNewPos);
+ continue;
+ }
+
+ if (rCxt.mnNewPos < rCxt.mnOldPos)
+ {
+ rRange.aStart.IncTab();
+ rRange.aEnd.IncTab();
+ }
+ else
+ {
+ rRange.aStart.IncTab(-1);
+ rRange.aEnd.IncTab(-1);
+ }
+ }
+
+ for (auto& rxEntry : maEntries)
+ rxEntry->UpdateMoveTab(rCxt);
+}
+
+void ScConditionalFormat::DeleteArea( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
+{
+ if (maRanges.empty())
+ return;
+
+ SCTAB nTab = maRanges[0].aStart.Tab();
+ maRanges.DeleteArea( nCol1, nRow1, nTab, nCol2, nRow2, nTab );
+}
+
+void ScConditionalFormat::RenameCellStyle(std::u16string_view rOld, const OUString& rNew)
+{
+ for(const auto& rxEntry : maEntries)
+ if(rxEntry->GetType() == ScFormatEntry::Type::Condition ||
+ rxEntry->GetType() == ScFormatEntry::Type::ExtCondition)
+ {
+ ScCondFormatEntry& rFormat = static_cast<ScCondFormatEntry&>(*rxEntry);
+ if(rFormat.GetStyle() == rOld)
+ rFormat.UpdateStyleName( rNew );
+ }
+}
+
+bool ScConditionalFormat::MarkUsedExternalReferences() const
+{
+ bool bAllMarked = false;
+ for(const auto& rxEntry : maEntries)
+ if(rxEntry->GetType() == ScFormatEntry::Type::Condition ||
+ rxEntry->GetType() == ScFormatEntry::Type::ExtCondition)
+ {
+ const ScCondFormatEntry& rFormat = static_cast<const ScCondFormatEntry&>(*rxEntry);
+ bAllMarked = rFormat.MarkUsedExternalReferences();
+ if (bAllMarked)
+ break;
+ }
+
+ return bAllMarked;
+}
+
+void ScConditionalFormat::startRendering()
+{
+ for(auto& rxEntry : maEntries)
+ {
+ rxEntry->startRendering();
+ }
+}
+
+void ScConditionalFormat::endRendering()
+{
+ for(auto& rxEntry : maEntries)
+ {
+ rxEntry->endRendering();
+ }
+}
+
+void ScConditionalFormat::CalcAll()
+{
+ for(const auto& rxEntry : maEntries)
+ {
+ if (rxEntry->GetType() == ScFormatEntry::Type::Condition ||
+ rxEntry->GetType() == ScFormatEntry::Type::ExtCondition)
+ {
+ ScCondFormatEntry& rFormat = static_cast<ScCondFormatEntry&>(*rxEntry);
+ rFormat.CalcAll();
+ }
+ }
+}
+
+ScConditionalFormatList::ScConditionalFormatList(const ScConditionalFormatList& rList)
+{
+ for(const auto& rxFormat : rList)
+ InsertNew( rxFormat->Clone() );
+}
+
+ScConditionalFormatList::ScConditionalFormatList(ScDocument& rDoc, const ScConditionalFormatList& rList)
+{
+ for(const auto& rxFormat : rList)
+ InsertNew( rxFormat->Clone(&rDoc) );
+}
+
+void ScConditionalFormatList::InsertNew( std::unique_ptr<ScConditionalFormat> pNew )
+{
+ m_ConditionalFormats.insert(std::move(pNew));
+}
+
+ScConditionalFormat* ScConditionalFormatList::GetFormat( sal_uInt32 nKey )
+{
+ auto itr = m_ConditionalFormats.find(nKey);
+ if (itr != m_ConditionalFormats.end())
+ return itr->get();
+
+ SAL_WARN("sc", "ScConditionalFormatList: Entry not found");
+ return nullptr;
+}
+
+const ScConditionalFormat* ScConditionalFormatList::GetFormat( sal_uInt32 nKey ) const
+{
+ auto itr = m_ConditionalFormats.find(nKey);
+ if (itr != m_ConditionalFormats.end())
+ return itr->get();
+
+ SAL_WARN("sc", "ScConditionalFormatList: Entry not found");
+ return nullptr;
+}
+
+void ScConditionalFormatList::CompileAll()
+{
+ for (auto const& it : m_ConditionalFormats)
+ {
+ it->CompileAll();
+ }
+}
+
+void ScConditionalFormatList::CompileXML()
+{
+ for (auto const& it : m_ConditionalFormats)
+ {
+ it->CompileXML();
+ }
+}
+
+void ScConditionalFormatList::UpdateReference( sc::RefUpdateContext& rCxt )
+{
+ for (auto const& it : m_ConditionalFormats)
+ {
+ it->UpdateReference(rCxt);
+ }
+
+ if (rCxt.meMode == URM_INSDEL)
+ {
+ // need to check which must be deleted
+ CheckAllEntries();
+ }
+}
+
+void ScConditionalFormatList::InsertRow(SCTAB nTab, SCCOL nColStart, SCCOL nColEnd, SCROW nRowPos, SCSIZE nSize)
+{
+ for (auto const& it : m_ConditionalFormats)
+ {
+ it->InsertRow(nTab, nColStart, nColEnd, nRowPos, nSize);
+ }
+}
+
+void ScConditionalFormatList::InsertCol(SCTAB nTab, SCROW nRowStart, SCROW nRowEnd, SCCOL nColPos, SCSIZE nSize)
+{
+ for (auto const& it : m_ConditionalFormats)
+ {
+ it->InsertCol(nTab, nRowStart, nRowEnd, nColPos, nSize);
+ }
+}
+
+void ScConditionalFormatList::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt )
+{
+ for (auto const& it : m_ConditionalFormats)
+ {
+ it->UpdateInsertTab(rCxt);
+ }
+}
+
+void ScConditionalFormatList::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt )
+{
+ for (auto const& it : m_ConditionalFormats)
+ {
+ it->UpdateDeleteTab(rCxt);
+ }
+}
+
+void ScConditionalFormatList::UpdateMoveTab( sc::RefUpdateMoveTabContext& rCxt )
+{
+ for (auto const& it : m_ConditionalFormats)
+ {
+ it->UpdateMoveTab(rCxt);
+ }
+}
+
+void ScConditionalFormatList::RenameCellStyle( std::u16string_view rOld, const OUString& rNew )
+{
+ for (auto const& it : m_ConditionalFormats)
+ {
+ it->RenameCellStyle(rOld, rNew);
+ }
+}
+
+bool ScConditionalFormatList::CheckAllEntries(const Link<ScConditionalFormat*,void>& rLink)
+{
+ bool bValid = true;
+
+ // need to check which must be deleted
+ iterator itr = m_ConditionalFormats.begin();
+ while(itr != m_ConditionalFormats.end())
+ {
+ if ((*itr)->GetRange().empty())
+ {
+ bValid = false;
+ if (rLink.IsSet())
+ rLink.Call(itr->get());
+ itr = m_ConditionalFormats.erase(itr);
+ }
+ else
+ ++itr;
+ }
+
+ return bValid;
+}
+
+void ScConditionalFormatList::DeleteArea( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
+{
+ for (auto& rxFormat : m_ConditionalFormats)
+ rxFormat->DeleteArea( nCol1, nRow1, nCol2, nRow2 );
+
+ CheckAllEntries();
+}
+
+ScConditionalFormatList::iterator ScConditionalFormatList::begin()
+{
+ return m_ConditionalFormats.begin();
+}
+
+ScConditionalFormatList::const_iterator ScConditionalFormatList::begin() const
+{
+ return m_ConditionalFormats.begin();
+}
+
+ScConditionalFormatList::iterator ScConditionalFormatList::end()
+{
+ return m_ConditionalFormats.end();
+}
+
+ScConditionalFormatList::const_iterator ScConditionalFormatList::end() const
+{
+ return m_ConditionalFormats.end();
+}
+
+ScRangeList ScConditionalFormatList::GetCombinedRange() const
+{
+ ScRangeList aRange;
+ for (auto& itr: m_ConditionalFormats)
+ {
+ const ScRangeList& rRange = itr->GetRange();
+ for (size_t i = 0, n = rRange.size(); i < n; ++i)
+ {
+ aRange.Join(rRange[i]);
+ }
+ }
+ return aRange;
+}
+
+void ScConditionalFormatList::RemoveFromDocument(ScDocument& rDoc) const
+{
+ ScRangeList aRange = GetCombinedRange();
+ ScMarkData aMark(rDoc.GetSheetLimits());
+ aMark.MarkFromRangeList(aRange, true);
+ sal_uInt16 const pItems[2] = { sal_uInt16(ATTR_CONDITIONAL),0};
+ rDoc.ClearSelectionItems(pItems, aMark);
+}
+
+void ScConditionalFormatList::AddToDocument(ScDocument& rDoc) const
+{
+ for (auto& itr: m_ConditionalFormats)
+ {
+ const ScRangeList& rRange = itr->GetRange();
+ if (rRange.empty())
+ continue;
+
+ SCTAB nTab = rRange.front().aStart.Tab();
+ rDoc.AddCondFormatData(rRange, nTab, itr->GetKey());
+ }
+}
+
+size_t ScConditionalFormatList::size() const
+{
+ return m_ConditionalFormats.size();
+}
+
+bool ScConditionalFormatList::empty() const
+{
+ return m_ConditionalFormats.empty();
+}
+
+void ScConditionalFormatList::erase( sal_uLong nIndex )
+{
+ auto itr = m_ConditionalFormats.find(nIndex);
+ if (itr != end())
+ m_ConditionalFormats.erase(itr);
+}
+
+void ScConditionalFormatList::startRendering()
+{
+ for (auto const& it : m_ConditionalFormats)
+ {
+ it->startRendering();
+ }
+}
+
+void ScConditionalFormatList::endRendering()
+{
+ for (auto const& it : m_ConditionalFormats)
+ {
+ it->endRendering();
+ }
+}
+
+void ScConditionalFormatList::clear()
+{
+ m_ConditionalFormats.clear();
+}
+
+sal_uInt32 ScConditionalFormatList::getMaxKey() const
+{
+ if (m_ConditionalFormats.empty())
+ return 0;
+ return (*m_ConditionalFormats.rbegin())->GetKey();
+}
+
+void ScConditionalFormatList::CalcAll()
+{
+ for (const auto& aEntry : m_ConditionalFormats)
+ {
+ aEntry->CalcAll();
+ }
+
+}
+
+ScCondFormatData::ScCondFormatData() {}
+
+ScCondFormatData::ScCondFormatData(ScCondFormatData&&) = default;
+
+ScCondFormatData::~ScCondFormatData() {}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dbdocutl.cxx b/sc/source/core/data/dbdocutl.cxx
new file mode 100644
index 000000000..db4517097
--- /dev/null
+++ b/sc/source/core/data/dbdocutl.cxx
@@ -0,0 +1,201 @@
+/* -*- 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/sdbc/DataType.hpp>
+#include <com/sun/star/sdbc/XRow.hpp>
+
+#include <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+
+#include <dbdocutl.hxx>
+#include <document.hxx>
+#include <formula/errorcodes.hxx>
+#include <stringutil.hxx>
+
+using namespace ::com::sun::star;
+
+ScDatabaseDocUtil::StrData::StrData() :
+ mbSimpleText(true), mnStrLength(0)
+{
+}
+
+void ScDatabaseDocUtil::PutData(ScDocument& rDoc, SCCOL nCol, SCROW nRow, SCTAB nTab,
+ const uno::Reference<sdbc::XRow>& xRow, sal_Int32 nRowPos,
+ tools::Long nType, bool bCurrency, StrData* pStrData)
+{
+ OUString aString;
+ double nVal = 0.0;
+ bool bValue = false;
+ bool bEmptyFlag = false;
+ bool bError = false;
+ sal_uLong nFormatIndex = 0;
+
+ // wasNull calls only if null value was found?
+
+ try
+ {
+ switch ( nType )
+ {
+ case sdbc::DataType::BIT:
+ case sdbc::DataType::BOOLEAN:
+ //TODO: use language from doc (here, date/time and currency)?
+ nFormatIndex = rDoc.GetFormatTable()->GetStandardFormat(
+ SvNumFormatType::LOGICAL, ScGlobal::eLnge );
+ nVal = (xRow->getBoolean(nRowPos) ? 1 : 0);
+ bEmptyFlag = ( nVal == 0.0 ) && xRow->wasNull();
+ bValue = true;
+ break;
+
+ case sdbc::DataType::TINYINT:
+ case sdbc::DataType::SMALLINT:
+ case sdbc::DataType::INTEGER:
+ case sdbc::DataType::BIGINT:
+ case sdbc::DataType::FLOAT:
+ case sdbc::DataType::REAL:
+ case sdbc::DataType::DOUBLE:
+ case sdbc::DataType::NUMERIC:
+ case sdbc::DataType::DECIMAL:
+ //TODO: do the conversion here?
+ nVal = xRow->getDouble(nRowPos);
+ bEmptyFlag = ( nVal == 0.0 ) && xRow->wasNull();
+ bValue = true;
+ break;
+
+ case sdbc::DataType::CHAR:
+ case sdbc::DataType::VARCHAR:
+ case sdbc::DataType::LONGVARCHAR:
+ aString = xRow->getString(nRowPos);
+ bEmptyFlag = ( aString.isEmpty() ) && xRow->wasNull();
+ break;
+
+ case sdbc::DataType::DATE:
+ {
+ util::Date aDate = xRow->getDate(nRowPos);
+ bEmptyFlag = xRow->wasNull();
+ if (bEmptyFlag)
+ nVal = 0.0;
+ else
+ {
+ SvNumberFormatter* pFormTable = rDoc.GetFormatTable();
+ nFormatIndex = pFormTable->GetStandardFormat(
+ SvNumFormatType::DATE, ScGlobal::eLnge );
+ nVal = Date( aDate ) - pFormTable->GetNullDate();
+ }
+ bValue = true;
+ }
+ break;
+
+ case sdbc::DataType::TIME:
+ {
+ SvNumberFormatter* pFormTable = rDoc.GetFormatTable();
+ nFormatIndex = pFormTable->GetStandardFormat(
+ SvNumFormatType::TIME, ScGlobal::eLnge );
+
+ util::Time aTime = xRow->getTime(nRowPos);
+ nVal = aTime.Hours / static_cast<double>(::tools::Time::hourPerDay) +
+ aTime.Minutes / static_cast<double>(::tools::Time::minutePerDay) +
+ aTime.Seconds / static_cast<double>(::tools::Time::secondPerDay) +
+ aTime.NanoSeconds / static_cast<double>(::tools::Time::nanoSecPerDay);
+ bEmptyFlag = xRow->wasNull();
+ bValue = true;
+ }
+ break;
+
+ case sdbc::DataType::TIMESTAMP:
+ {
+ SvNumberFormatter* pFormTable = rDoc.GetFormatTable();
+ nFormatIndex = pFormTable->GetStandardFormat(
+ SvNumFormatType::DATETIME, ScGlobal::eLnge );
+
+ util::DateTime aStamp = xRow->getTimestamp(nRowPos);
+ if (aStamp.Year != 0)
+ {
+ nVal = ( Date( aStamp.Day, aStamp.Month, aStamp.Year ) -
+ pFormTable->GetNullDate() ) +
+ aStamp.Hours / static_cast<double>(::tools::Time::hourPerDay) +
+ aStamp.Minutes / static_cast<double>(::tools::Time::minutePerDay) +
+ aStamp.Seconds / static_cast<double>(::tools::Time::secondPerDay) +
+ aStamp.NanoSeconds / static_cast<double>(::tools::Time::nanoSecPerDay);
+ bEmptyFlag = xRow->wasNull();
+ bValue = true;
+ }
+ }
+ break;
+
+ case sdbc::DataType::SQLNULL:
+ bEmptyFlag = true;
+ break;
+
+ case sdbc::DataType::BINARY:
+ case sdbc::DataType::VARBINARY:
+ case sdbc::DataType::LONGVARBINARY:
+ default:
+ bError = true; // unknown type
+ }
+ }
+ catch ( uno::Exception& )
+ {
+ bError = true;
+ }
+
+ if ( bValue && bCurrency )
+ nFormatIndex = rDoc.GetFormatTable()->GetStandardFormat(
+ SvNumFormatType::CURRENCY, ScGlobal::eLnge );
+
+ ScAddress aPos(nCol, nRow, nTab);
+ if (bEmptyFlag)
+ rDoc.SetEmptyCell(aPos);
+ else if (bError)
+ {
+ rDoc.SetError( nCol, nRow, nTab, FormulaError::NotAvailable );
+ }
+ else if (bValue)
+ {
+ rDoc.SetValue(aPos, nVal);
+ if (nFormatIndex)
+ rDoc.SetNumberFormat(aPos, nFormatIndex);
+ }
+ else
+ {
+ if (!aString.isEmpty())
+ {
+ if (ScStringUtil::isMultiline(aString))
+ {
+ rDoc.SetEditText(aPos, aString);
+ if (pStrData)
+ pStrData->mbSimpleText = false;
+ }
+ else
+ {
+ ScSetStringParam aParam;
+ aParam.setTextInput();
+ rDoc.SetString(aPos, aString, &aParam);
+ if (pStrData)
+ pStrData->mbSimpleText = true;
+ }
+
+ if (pStrData)
+ pStrData->mnStrLength = aString.getLength();
+ }
+ else
+ rDoc.SetEmptyCell(aPos);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dociter.cxx b/sc/source/core/data/dociter.cxx
new file mode 100644
index 000000000..38a4a218e
--- /dev/null
+++ b/sc/source/core/data/dociter.cxx
@@ -0,0 +1,1779 @@
+/* -*- 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 <global.hxx>
+#include <dociter.hxx>
+#include <document.hxx>
+#include <table.hxx>
+#include <column.hxx>
+#include <formulacell.hxx>
+#include <attarray.hxx>
+#include <patattr.hxx>
+#include <docoptio.hxx>
+#include <cellform.hxx>
+#include <segmenttree.hxx>
+#include <progress.hxx>
+#include <queryparam.hxx>
+#include <queryentry.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <cellvalue.hxx>
+#include <scmatrix.hxx>
+#include <rowheightcontext.hxx>
+#include <queryevaluator.hxx>
+
+#include <o3tl/safeint.hxx>
+#include <tools/fract.hxx>
+#include <editeng/editobj.hxx>
+#include <svl/sharedstring.hxx>
+#include <unotools/collatorwrapper.hxx>
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+
+#include <algorithm>
+#include <limits>
+#include <vector>
+
+using ::rtl::math::approxEqual;
+using ::std::vector;
+using ::std::set;
+
+// iterators have very high frequency use -> custom debug.
+// #define debugiter(...) fprintf(stderr, __VA_ARGS__)
+#define debugiter(...)
+
+static void ScAttrArray_IterGetNumberFormat( sal_uInt32& nFormat, const ScAttrArray*& rpArr,
+ SCROW& nAttrEndRow, const ScAttrArray* pNewArr, SCROW nRow,
+ const ScDocument& rDoc, const ScInterpreterContext* pContext = nullptr )
+{
+ if ( rpArr == pNewArr && nAttrEndRow >= nRow )
+ return;
+
+ SCROW nRowStart = 0;
+ SCROW nRowEnd = rDoc.MaxRow();
+ const ScPatternAttr* pPattern = pNewArr->GetPatternRange( nRowStart, nRowEnd, nRow );
+ if( !pPattern )
+ {
+ pPattern = rDoc.GetDefPattern();
+ nRowEnd = rDoc.MaxRow();
+ }
+
+ nFormat = pPattern->GetNumberFormat( pContext ? pContext->GetFormatTable() : rDoc.GetFormatTable() );
+ rpArr = pNewArr;
+ nAttrEndRow = nRowEnd;
+}
+
+ScValueIterator::ScValueIterator(ScInterpreterContext& rContext, ScDocument& rDocument, const ScRange& rRange,
+ SubtotalFlags nSubTotalFlags, bool bTextZero )
+ : mrDoc(rDocument)
+ , mrContext(rContext)
+ , pAttrArray(nullptr)
+ , nNumFormat(0) // Initialized in GetNumberFormat
+ , nNumFmtIndex(0)
+ , maStartPos(rRange.aStart)
+ , maEndPos(rRange.aEnd)
+ , mnCol(0)
+ , mnTab(0)
+ , nAttrEndRow(0)
+ , mnSubTotalFlags(nSubTotalFlags)
+ , nNumFmtType(SvNumFormatType::UNDEFINED)
+ , bNumValid(false)
+ , bCalcAsShown(rDocument.GetDocOptions().IsCalcAsShown())
+ , bTextAsZero(bTextZero)
+ , mpCells(nullptr)
+{
+ SCTAB nDocMaxTab = rDocument.GetTableCount() - 1;
+
+ if (!rDocument.ValidCol(maStartPos.Col())) maStartPos.SetCol(mrDoc.MaxCol());
+ if (!rDocument.ValidCol(maEndPos.Col())) maEndPos.SetCol(mrDoc.MaxCol());
+ if (!rDocument.ValidRow(maStartPos.Row())) maStartPos.SetRow(mrDoc.MaxRow());
+ if (!rDocument.ValidRow(maEndPos.Row())) maEndPos.SetRow(mrDoc.MaxRow());
+ if (!ValidTab(maStartPos.Tab()) || maStartPos.Tab() > nDocMaxTab) maStartPos.SetTab(nDocMaxTab);
+ if (!ValidTab(maEndPos.Tab()) || maEndPos.Tab() > nDocMaxTab) maEndPos.SetTab(nDocMaxTab);
+}
+
+SCROW ScValueIterator::GetRow() const
+{
+ // Position of the head of the current block + offset within the block
+ // equals the logical element position.
+ return maCurPos.first->position + maCurPos.second;
+}
+
+void ScValueIterator::IncBlock()
+{
+ ++maCurPos.first;
+ maCurPos.second = 0;
+}
+
+void ScValueIterator::IncPos()
+{
+ if (maCurPos.second + 1 < maCurPos.first->size)
+ // Move within the same block.
+ ++maCurPos.second;
+ else
+ // Move to the next block.
+ IncBlock();
+}
+
+bool ScValueIterator::GetThis(double& rValue, FormulaError& rErr)
+{
+ while (true)
+ {
+ bool bNextColumn = !mpCells || maCurPos.first == mpCells->end();
+ if (!bNextColumn)
+ {
+ if (GetRow() > maEndPos.Row())
+ bNextColumn = true;
+ }
+
+ ScColumn* pCol;
+ if (!bNextColumn)
+ pCol = &(mrDoc.maTabs[mnTab])->aCol[mnCol];
+ else
+ {
+ // Find the next available column.
+ do
+ {
+ ++mnCol;
+ while (mnCol > maEndPos.Col() || mnCol >= mrDoc.maTabs[mnTab]->GetAllocatedColumnsCount())
+ {
+ mnCol = maStartPos.Col();
+ ++mnTab;
+ if (mnTab > maEndPos.Tab())
+ {
+ rErr = FormulaError::NONE;
+ return false;
+ }
+ }
+ pCol = &(mrDoc.maTabs[mnTab])->aCol[mnCol];
+ }
+ while (pCol->IsEmptyData());
+
+ mpCells = &pCol->maCells;
+ maCurPos = mpCells->position(maStartPos.Row());
+ }
+
+ SCROW nCurRow = GetRow();
+ SCROW nLastRow;
+ // Skip all filtered or hidden rows, depending on mnSubTotalFlags
+ if ( ( ( mnSubTotalFlags & SubtotalFlags::IgnoreFiltered ) &&
+ mrDoc.RowFiltered( nCurRow, mnTab, nullptr, &nLastRow ) ) ||
+ ( ( mnSubTotalFlags & SubtotalFlags::IgnoreHidden ) &&
+ mrDoc.RowHidden( nCurRow, mnTab, nullptr, &nLastRow ) ) )
+ {
+ maCurPos = mpCells->position(maCurPos.first, nLastRow+1);
+ continue;
+ }
+
+ switch (maCurPos.first->type)
+ {
+ case sc::element_type_numeric:
+ {
+ bNumValid = false;
+ rValue = sc::numeric_block::at(*maCurPos.first->data, maCurPos.second);
+ rErr = FormulaError::NONE;
+ if (bCalcAsShown)
+ {
+ ScAttrArray_IterGetNumberFormat(nNumFormat, pAttrArray,
+ nAttrEndRow, pCol->pAttrArray.get(), nCurRow, mrDoc, &mrContext);
+ rValue = mrDoc.RoundValueAsShown(rValue, nNumFormat, &mrContext);
+ }
+ return true; // Found it!
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ ScFormulaCell& rCell = *sc::formula_block::at(*maCurPos.first->data, maCurPos.second);
+ if ( ( mnSubTotalFlags & SubtotalFlags::IgnoreNestedStAg ) && rCell.IsSubTotal() )
+ {
+ // Skip subtotal formula cells.
+ IncPos();
+ break;
+ }
+
+ if (rCell.GetErrorOrValue(rErr, rValue))
+ {
+ if ( rErr != FormulaError::NONE && ( mnSubTotalFlags & SubtotalFlags::IgnoreErrVal ) )
+ {
+ IncPos();
+ break;
+ }
+ bNumValid = false;
+ return true; // Found it!
+ }
+ else if (bTextAsZero)
+ {
+ rValue = 0.0;
+ bNumValid = false;
+ return true;
+ }
+ IncPos();
+ }
+ break;
+ case sc::element_type_string :
+ case sc::element_type_edittext :
+ {
+ if (bTextAsZero)
+ {
+ rErr = FormulaError::NONE;
+ rValue = 0.0;
+ nNumFmtType = SvNumFormatType::NUMBER;
+ nNumFmtIndex = 0;
+ bNumValid = true;
+ return true;
+ }
+ IncBlock();
+ }
+ break;
+ case sc::element_type_empty:
+ default:
+ // Skip the whole block.
+ IncBlock();
+ }
+ }
+}
+
+void ScValueIterator::GetCurNumFmtInfo( const ScInterpreterContext& rContext, SvNumFormatType& nType, sal_uInt32& nIndex )
+{
+ if (!bNumValid && mnTab < mrDoc.GetTableCount())
+ {
+ SCROW nCurRow = GetRow();
+ const ScColumn* pCol = &(mrDoc.maTabs[mnTab])->aCol[mnCol];
+ nNumFmtIndex = pCol->GetNumberFormat(rContext, nCurRow);
+ nNumFmtType = rContext.GetNumberFormatType( nNumFmtIndex );
+ bNumValid = true;
+ }
+
+ nType = nNumFmtType;
+ nIndex = nNumFmtIndex;
+}
+
+bool ScValueIterator::GetFirst(double& rValue, FormulaError& rErr)
+{
+ mnCol = maStartPos.Col();
+ mnTab = maStartPos.Tab();
+
+ ScTable* pTab = mrDoc.FetchTable(mnTab);
+ if (!pTab)
+ return false;
+
+ nNumFormat = 0; // Initialized in GetNumberFormat
+ pAttrArray = nullptr;
+ nAttrEndRow = 0;
+
+ auto nCol = maStartPos.Col();
+ if (nCol < pTab->GetAllocatedColumnsCount())
+ {
+ mpCells = &pTab->aCol[nCol].maCells;
+ maCurPos = mpCells->position(maStartPos.Row());
+ }
+ else
+ mpCells = nullptr;
+ return GetThis(rValue, rErr);
+}
+
+bool ScValueIterator::GetNext(double& rValue, FormulaError& rErr)
+{
+ IncPos();
+ return GetThis(rValue, rErr);
+}
+
+ScDBQueryDataIterator::DataAccess::DataAccess()
+{
+}
+
+ScDBQueryDataIterator::DataAccess::~DataAccess()
+{
+}
+
+const sc::CellStoreType* ScDBQueryDataIterator::GetColumnCellStore(ScDocument& rDoc, SCTAB nTab, SCCOL nCol)
+{
+ ScTable* pTab = rDoc.FetchTable(nTab);
+ if (!pTab)
+ return nullptr;
+ if (nCol >= pTab->GetAllocatedColumnsCount())
+ return nullptr;
+ return &pTab->aCol[nCol].maCells;
+}
+
+const ScAttrArray* ScDBQueryDataIterator::GetAttrArrayByCol(ScDocument& rDoc, SCTAB nTab, SCCOL nCol)
+{
+ assert(nTab < rDoc.GetTableCount() && "index out of bounds, FIX IT");
+ ScColumn* pCol = &rDoc.maTabs[nTab]->aCol[nCol];
+ return pCol->pAttrArray.get();
+}
+
+bool ScDBQueryDataIterator::IsQueryValid(
+ ScDocument& rDoc, const ScQueryParam& rParam, SCTAB nTab, SCROW nRow, const ScRefCellValue* pCell)
+{
+ assert(nTab < rDoc.GetTableCount() && "index out of bounds, FIX IT");
+ ScQueryEvaluator queryEvaluator(rDoc, *rDoc.maTabs[nTab], rParam);
+ return queryEvaluator.ValidQuery(nRow, pCell);
+}
+
+ScDBQueryDataIterator::DataAccessInternal::DataAccessInternal(ScDBQueryParamInternal* pParam, ScDocument& rDoc, const ScInterpreterContext& rContext)
+ : mpCells(nullptr)
+ , mpParam(pParam)
+ , mrDoc(rDoc)
+ , mrContext(rContext)
+ , pAttrArray(nullptr)
+ , nNumFormat(0) // Initialized in GetNumberFormat
+ , nNumFmtIndex(0)
+ , nCol(mpParam->mnField)
+ , nRow(mpParam->nRow1)
+ , nAttrEndRow(0)
+ , nTab(mpParam->nTab)
+ , nNumFmtType(SvNumFormatType::ALL)
+ , bCalcAsShown(rDoc.GetDocOptions().IsCalcAsShown())
+{
+ SCSIZE i;
+ SCSIZE nCount = mpParam->GetEntryCount();
+ for (i=0; (i<nCount) && (mpParam->GetEntry(i).bDoQuery); i++)
+ {
+ ScQueryEntry& rEntry = mpParam->GetEntry(i);
+ ScQueryEntry::QueryItemsType& rItems = rEntry.GetQueryItems();
+ rItems.resize(1);
+ ScQueryEntry::Item& rItem = rItems.front();
+ sal_uInt32 nIndex = 0;
+ bool bNumber = mrDoc.GetFormatTable()->IsNumberFormat(
+ rItem.maString.getString(), nIndex, rItem.mfVal);
+ rItem.meType = bNumber ? ScQueryEntry::ByValue : ScQueryEntry::ByString;
+ }
+}
+
+ScDBQueryDataIterator::DataAccessInternal::~DataAccessInternal()
+{
+}
+
+bool ScDBQueryDataIterator::DataAccessInternal::getCurrent(Value& rValue)
+{
+ // Start with the current row position, and find the first row position
+ // that satisfies the query.
+
+ // If the query starts in the same column as the result vector we can
+ // prefetch the cell which saves us one fetch in the success case.
+ SCCOLROW nFirstQueryField = mpParam->GetEntry(0).nField;
+ ScRefCellValue aCell;
+
+ while (true)
+ {
+ if (maCurPos.first == mpCells->end() || nRow > mpParam->nRow2)
+ {
+ // Bottom of the range reached. Bail out.
+ rValue.mnError = FormulaError::NONE;
+ return false;
+ }
+
+ if (maCurPos.first->type == sc::element_type_empty)
+ {
+ // Skip the whole empty block.
+ incBlock();
+ continue;
+ }
+
+ ScRefCellValue* pCell = nullptr;
+ if (nCol == static_cast<SCCOL>(nFirstQueryField))
+ {
+ aCell = sc::toRefCell(maCurPos.first, maCurPos.second);
+ pCell = &aCell;
+ }
+
+ if (ScDBQueryDataIterator::IsQueryValid(mrDoc, *mpParam, nTab, nRow, pCell))
+ {
+ if (!pCell)
+ aCell = sc::toRefCell(maCurPos.first, maCurPos.second);
+ switch (aCell.meType)
+ {
+ case CELLTYPE_VALUE:
+ {
+ rValue.mfValue = aCell.mfValue;
+ rValue.mbIsNumber = true;
+ if ( bCalcAsShown )
+ {
+ const ScAttrArray* pNewAttrArray =
+ ScDBQueryDataIterator::GetAttrArrayByCol(mrDoc, nTab, nCol);
+ ScAttrArray_IterGetNumberFormat( nNumFormat, pAttrArray,
+ nAttrEndRow, pNewAttrArray, nRow, mrDoc );
+ rValue.mfValue = mrDoc.RoundValueAsShown( rValue.mfValue, nNumFormat );
+ }
+ nNumFmtType = SvNumFormatType::NUMBER;
+ nNumFmtIndex = 0;
+ rValue.mnError = FormulaError::NONE;
+ return true; // Found it!
+ }
+
+ case CELLTYPE_FORMULA:
+ {
+ if (aCell.mpFormula->IsValue())
+ {
+ rValue.mfValue = aCell.mpFormula->GetValue();
+ rValue.mbIsNumber = true;
+ mrDoc.GetNumberFormatInfo(
+ mrContext, nNumFmtType, nNumFmtIndex, ScAddress(nCol, nRow, nTab));
+ rValue.mnError = aCell.mpFormula->GetErrCode();
+ return true; // Found it!
+ }
+ else if(mpParam->mbSkipString)
+ incPos();
+ else
+ {
+ rValue.maString = aCell.mpFormula->GetString().getString();
+ rValue.mfValue = 0.0;
+ rValue.mnError = aCell.mpFormula->GetErrCode();
+ rValue.mbIsNumber = false;
+ return true;
+ }
+ }
+ break;
+ case CELLTYPE_STRING:
+ case CELLTYPE_EDIT:
+ if (mpParam->mbSkipString)
+ incPos();
+ else
+ {
+ rValue.maString = aCell.getString(&mrDoc);
+ rValue.mfValue = 0.0;
+ rValue.mnError = FormulaError::NONE;
+ rValue.mbIsNumber = false;
+ return true;
+ }
+ break;
+ default:
+ incPos();
+ }
+ }
+ else
+ incPos();
+ }
+// statement unreachable
+}
+
+bool ScDBQueryDataIterator::DataAccessInternal::getFirst(Value& rValue)
+{
+ if (mpParam->bHasHeader)
+ ++nRow;
+
+ mpCells = ScDBQueryDataIterator::GetColumnCellStore(mrDoc, nTab, nCol);
+ if (!mpCells)
+ return false;
+
+ maCurPos = mpCells->position(nRow);
+ return getCurrent(rValue);
+}
+
+bool ScDBQueryDataIterator::DataAccessInternal::getNext(Value& rValue)
+{
+ if (!mpCells || maCurPos.first == mpCells->end())
+ return false;
+
+ incPos();
+ return getCurrent(rValue);
+}
+
+void ScDBQueryDataIterator::DataAccessInternal::incBlock()
+{
+ ++maCurPos.first;
+ maCurPos.second = 0;
+
+ nRow = maCurPos.first->position;
+}
+
+void ScDBQueryDataIterator::DataAccessInternal::incPos()
+{
+ if (maCurPos.second + 1 < maCurPos.first->size)
+ {
+ // Move within the same block.
+ ++maCurPos.second;
+ ++nRow;
+ }
+ else
+ // Move to the next block.
+ incBlock();
+}
+
+ScDBQueryDataIterator::DataAccessMatrix::DataAccessMatrix(ScDBQueryParamMatrix* pParam)
+ : mpParam(pParam)
+ , mnCurRow(0)
+{
+ SCSIZE nC, nR;
+ mpParam->mpMatrix->GetDimensions(nC, nR);
+ mnRows = static_cast<SCROW>(nR);
+}
+
+ScDBQueryDataIterator::DataAccessMatrix::~DataAccessMatrix()
+{
+}
+
+bool ScDBQueryDataIterator::DataAccessMatrix::getCurrent(Value& rValue)
+{
+ // Starting from row == mnCurRow, get the first row that satisfies all the
+ // query parameters.
+ for ( ;mnCurRow < mnRows; ++mnCurRow)
+ {
+ const ScMatrix& rMat = *mpParam->mpMatrix;
+ if (rMat.IsEmpty(mpParam->mnField, mnCurRow))
+ // Don't take empty values into account.
+ continue;
+
+ bool bIsStrVal = rMat.IsStringOrEmpty(mpParam->mnField, mnCurRow);
+ if (bIsStrVal && mpParam->mbSkipString)
+ continue;
+
+ if (isValidQuery(mnCurRow, rMat))
+ {
+ rValue.maString = rMat.GetString(mpParam->mnField, mnCurRow).getString();
+ rValue.mfValue = rMat.GetDouble(mpParam->mnField, mnCurRow);
+ rValue.mbIsNumber = !bIsStrVal;
+ rValue.mnError = FormulaError::NONE;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ScDBQueryDataIterator::DataAccessMatrix::getFirst(Value& rValue)
+{
+ mnCurRow = mpParam->bHasHeader ? 1 : 0;
+ return getCurrent(rValue);
+}
+
+bool ScDBQueryDataIterator::DataAccessMatrix::getNext(Value& rValue)
+{
+ ++mnCurRow;
+ return getCurrent(rValue);
+}
+
+namespace {
+
+bool isQueryByValue(const ScQueryEntry::Item& rItem, const ScMatrix& rMat, SCSIZE nCol, SCSIZE nRow)
+{
+ if (rItem.meType == ScQueryEntry::ByString)
+ return false;
+
+ if (!rMat.IsValueOrEmpty(nCol, nRow))
+ return false;
+
+ return true;
+}
+
+bool isQueryByString(const ScQueryEntry& rEntry, const ScQueryEntry::Item& rItem, const ScMatrix& rMat, SCSIZE nCol, SCSIZE nRow)
+{
+ switch (rEntry.eOp)
+ {
+ case SC_EQUAL:
+ case SC_NOT_EQUAL:
+ case SC_CONTAINS:
+ case SC_DOES_NOT_CONTAIN:
+ case SC_BEGINS_WITH:
+ case SC_ENDS_WITH:
+ case SC_DOES_NOT_BEGIN_WITH:
+ case SC_DOES_NOT_END_WITH:
+ return true;
+ default:
+ ;
+ }
+
+ return rItem.meType == ScQueryEntry::ByString && rMat.IsStringOrEmpty(nCol, nRow);
+}
+
+}
+
+bool ScDBQueryDataIterator::DataAccessMatrix::isValidQuery(SCROW nRow, const ScMatrix& rMat) const
+{
+ SCSIZE nEntryCount = mpParam->GetEntryCount();
+ vector<bool> aResults;
+ aResults.reserve(nEntryCount);
+
+ const CollatorWrapper& rCollator = ScGlobal::GetCollator(mpParam->bCaseSens);
+
+ for (SCSIZE i = 0; i < nEntryCount; ++i)
+ {
+ const ScQueryEntry& rEntry = mpParam->GetEntry(i);
+ const ScQueryEntry::Item& rItem = rEntry.GetQueryItem();
+ if (!rEntry.bDoQuery)
+ continue;
+
+ switch (rEntry.eOp)
+ {
+ case SC_EQUAL:
+ case SC_LESS:
+ case SC_GREATER:
+ case SC_LESS_EQUAL:
+ case SC_GREATER_EQUAL:
+ case SC_NOT_EQUAL:
+ break;
+ default:
+ // Only the above operators are supported.
+ SAL_WARN("sc.core", "Unsupported operator " << rEntry.eOp
+ << " in ScDBQueryDataIterator::DataAccessMatrix::isValidQuery()");
+ continue;
+ }
+
+ bool bValid = false;
+
+ SCSIZE nField = static_cast<SCSIZE>(rEntry.nField);
+ if (isQueryByValue(rItem, rMat, nField, nRow))
+ {
+ // By value
+ double fMatVal = rMat.GetDouble(nField, nRow);
+ bool bEqual = approxEqual(fMatVal, rItem.mfVal);
+ switch (rEntry.eOp)
+ {
+ case SC_EQUAL:
+ bValid = bEqual;
+ break;
+ case SC_LESS:
+ bValid = (fMatVal < rItem.mfVal) && !bEqual;
+ break;
+ case SC_GREATER:
+ bValid = (fMatVal > rItem.mfVal) && !bEqual;
+ break;
+ case SC_LESS_EQUAL:
+ bValid = (fMatVal < rItem.mfVal) || bEqual;
+ break;
+ case SC_GREATER_EQUAL:
+ bValid = (fMatVal > rItem.mfVal) || bEqual;
+ break;
+ case SC_NOT_EQUAL:
+ bValid = !bEqual;
+ break;
+ default:
+ ;
+ }
+ }
+ else if (isQueryByString(rEntry, rItem, rMat, nField, nRow))
+ {
+ // By string
+ do
+ {
+ // Equality check first.
+ svl::SharedString aMatStr = rMat.GetString(nField, nRow);
+ svl::SharedString aQueryStr = rEntry.GetQueryItem().maString;
+ bool bDone = false;
+ rtl_uString* p1 = mpParam->bCaseSens ? aMatStr.getData() : aMatStr.getDataIgnoreCase();
+ rtl_uString* p2 = mpParam->bCaseSens ? aQueryStr.getData() : aQueryStr.getDataIgnoreCase();
+ switch (rEntry.eOp)
+ {
+ case SC_EQUAL:
+ bValid = (p1 == p2);
+ bDone = true;
+ break;
+ case SC_NOT_EQUAL:
+ bValid = (p1 != p2);
+ bDone = true;
+ break;
+ default:
+ ;
+ }
+
+ if (bDone)
+ break;
+
+ // Unequality check using collator.
+ sal_Int32 nCompare = rCollator.compareString(aMatStr.getString(), aQueryStr.getString());
+ switch (rEntry.eOp)
+ {
+ case SC_LESS :
+ bValid = (nCompare < 0);
+ break;
+ case SC_GREATER :
+ bValid = (nCompare > 0);
+ break;
+ case SC_LESS_EQUAL :
+ bValid = (nCompare <= 0);
+ break;
+ case SC_GREATER_EQUAL :
+ bValid = (nCompare >= 0);
+ break;
+ default:
+ ;
+ }
+ }
+ while (false);
+ }
+
+ if (aResults.empty())
+ // First query entry.
+ aResults.push_back(bValid);
+ else if (rEntry.eConnect == SC_AND)
+ {
+ // For AND op, tuck the result into the last result value.
+ size_t n = aResults.size();
+ aResults[n-1] = aResults[n-1] && bValid;
+ }
+ else
+ // For OR op, store its own result.
+ aResults.push_back(bValid);
+ }
+
+ // Row is valid as long as there is at least one result being true.
+ return std::find(aResults.begin(), aResults.end(), true) != aResults.end();
+}
+
+ScDBQueryDataIterator::Value::Value()
+ : mfValue(std::numeric_limits<double>::quiet_NaN())
+ , mnError(FormulaError::NONE), mbIsNumber(true)
+{
+}
+
+ScDBQueryDataIterator::ScDBQueryDataIterator(ScDocument& rDocument, const ScInterpreterContext& rContext, std::unique_ptr<ScDBQueryParamBase> pParam) :
+ mpParam (std::move(pParam))
+{
+ switch (mpParam->GetType())
+ {
+ case ScDBQueryParamBase::INTERNAL:
+ {
+ ScDBQueryParamInternal* p = static_cast<ScDBQueryParamInternal*>(mpParam.get());
+ mpData.reset(new DataAccessInternal(p, rDocument, rContext));
+ }
+ break;
+ case ScDBQueryParamBase::MATRIX:
+ {
+ ScDBQueryParamMatrix* p = static_cast<ScDBQueryParamMatrix*>(mpParam.get());
+ mpData.reset(new DataAccessMatrix(p));
+ }
+ }
+}
+
+bool ScDBQueryDataIterator::GetFirst(Value& rValue)
+{
+ return mpData->getFirst(rValue);
+}
+
+bool ScDBQueryDataIterator::GetNext(Value& rValue)
+{
+ return mpData->getNext(rValue);
+}
+
+ScFormulaGroupIterator::ScFormulaGroupIterator( ScDocument& rDoc ) :
+ mrDoc(rDoc),
+ mnTab(0),
+ mnCol(0),
+ mnIndex(0)
+{
+ ScTable *pTab = mrDoc.FetchTable(mnTab);
+ ScColumn *pCol = pTab ? pTab->FetchColumn(mnCol) : nullptr;
+ if (pCol)
+ {
+ mbNullCol = false;
+ maEntries = pCol->GetFormulaGroupEntries();
+ }
+ else
+ mbNullCol = true;
+}
+
+sc::FormulaGroupEntry* ScFormulaGroupIterator::first()
+{
+ return next();
+}
+
+sc::FormulaGroupEntry* ScFormulaGroupIterator::next()
+{
+ if (mnIndex >= maEntries.size() || mbNullCol)
+ {
+ while (mnIndex >= maEntries.size() || mbNullCol)
+ {
+ mnIndex = 0;
+ mnCol++;
+ if (mnCol > mrDoc.MaxCol())
+ {
+ mnCol = 0;
+ mnTab++;
+ if (mnTab >= mrDoc.GetTableCount())
+ return nullptr;
+ }
+ ScTable *pTab = mrDoc.FetchTable(mnTab);
+ ScColumn *pCol = pTab ? pTab->FetchColumn(mnCol) : nullptr;
+ if (pCol)
+ {
+ mbNullCol = false;
+ maEntries = pCol->GetFormulaGroupEntries();
+ }
+ else
+ mbNullCol = true;
+ }
+ }
+
+ return &maEntries[mnIndex++];
+}
+
+ScCellIterator::ScCellIterator( ScDocument& rDoc, const ScRange& rRange, SubtotalFlags nSubTotalFlags ) :
+ mrDoc(rDoc),
+ maStartPos(rRange.aStart),
+ maEndPos(rRange.aEnd),
+ mnSubTotalFlags(nSubTotalFlags)
+{
+ init();
+}
+
+void ScCellIterator::incBlock()
+{
+ ++maCurColPos.first;
+ maCurColPos.second = 0;
+
+ maCurPos.SetRow(maCurColPos.first->position);
+}
+
+void ScCellIterator::incPos()
+{
+ if (maCurColPos.second + 1 < maCurColPos.first->size)
+ {
+ // Move within the same block.
+ ++maCurColPos.second;
+ maCurPos.IncRow();
+ }
+ else
+ // Move to the next block.
+ incBlock();
+}
+
+void ScCellIterator::setPos(size_t nPos)
+{
+ maCurColPos = getColumn()->maCells.position(maCurColPos.first, nPos);
+ maCurPos.SetRow(nPos);
+}
+
+const ScColumn* ScCellIterator::getColumn() const
+{
+ return &mrDoc.maTabs[maCurPos.Tab()]->aCol[maCurPos.Col()];
+}
+
+void ScCellIterator::init()
+{
+ SCTAB nDocMaxTab = mrDoc.GetTableCount() - 1;
+
+ PutInOrder(maStartPos, maEndPos);
+
+ if (!mrDoc.ValidCol(maStartPos.Col())) maStartPos.SetCol(mrDoc.MaxCol());
+ if (!mrDoc.ValidCol(maEndPos.Col())) maEndPos.SetCol(mrDoc.MaxCol());
+ if (!mrDoc.ValidRow(maStartPos.Row())) maStartPos.SetRow(mrDoc.MaxRow());
+ if (!mrDoc.ValidRow(maEndPos.Row())) maEndPos.SetRow(mrDoc.MaxRow());
+ if (!ValidTab(maStartPos.Tab(), nDocMaxTab)) maStartPos.SetTab(nDocMaxTab);
+ if (!ValidTab(maEndPos.Tab(), nDocMaxTab)) maEndPos.SetTab(nDocMaxTab);
+
+ while (maEndPos.Tab() > 0 && !mrDoc.maTabs[maEndPos.Tab()])
+ maEndPos.IncTab(-1); // Only the tables in use
+
+ if (maStartPos.Tab() > maEndPos.Tab())
+ maStartPos.SetTab(maEndPos.Tab());
+
+ if (!mrDoc.maTabs[maStartPos.Tab()])
+ {
+ assert(!"Table not found");
+ maStartPos = ScAddress(mrDoc.MaxCol()+1, mrDoc.MaxRow()+1, MAXTAB+1); // -> Abort on GetFirst.
+ }
+ else
+ {
+ maStartPos.SetCol(mrDoc.maTabs[maStartPos.Tab()]->ClampToAllocatedColumns(maStartPos.Col()));
+ }
+
+ maCurPos = maStartPos;
+}
+
+bool ScCellIterator::getCurrent()
+{
+ const ScColumn* pCol = getColumn();
+
+ while (true)
+ {
+ bool bNextColumn = maCurColPos.first == pCol->maCells.end();
+ if (!bNextColumn)
+ {
+ if (maCurPos.Row() > maEndPos.Row())
+ bNextColumn = true;
+ }
+
+ if (bNextColumn)
+ {
+ // Move to the next column.
+ maCurPos.SetRow(maStartPos.Row());
+ do
+ {
+ maCurPos.IncCol();
+ while (maCurPos.Col() >= mrDoc.GetAllocatedColumnsCount(maCurPos.Tab())
+ || maCurPos.Col() > maEndPos.Col())
+ {
+ maCurPos.SetCol(maStartPos.Col());
+ maCurPos.IncTab();
+ if (maCurPos.Tab() > maEndPos.Tab())
+ {
+ maCurCell.clear();
+ return false;
+ }
+ }
+ pCol = getColumn();
+ }
+ while (pCol->IsEmptyData());
+
+ maCurColPos = pCol->maCells.position(maCurPos.Row());
+ }
+
+ if (maCurColPos.first->type == sc::element_type_empty)
+ {
+ incBlock();
+ continue;
+ }
+
+ SCROW nLastRow;
+ // Skip all filtered or hidden rows, depending on mSubTotalFlags
+ if ( ( ( mnSubTotalFlags & SubtotalFlags::IgnoreFiltered ) &&
+ pCol->GetDoc().RowFiltered(maCurPos.Row(), maCurPos.Tab(), nullptr, &nLastRow) ) ||
+ ( ( mnSubTotalFlags & SubtotalFlags::IgnoreHidden ) &&
+ pCol->GetDoc().RowHidden(maCurPos.Row(), maCurPos.Tab(), nullptr, &nLastRow) ) )
+ {
+ setPos(nLastRow+1);
+ continue;
+ }
+
+ if (maCurColPos.first->type == sc::element_type_formula)
+ {
+ if ( mnSubTotalFlags != SubtotalFlags::NONE )
+ {
+ ScFormulaCell* pCell = sc::formula_block::at(*maCurColPos.first->data, maCurColPos.second);
+ // Skip formula cells with Subtotal formulae or errors, depending on mnSubTotalFlags
+ if ( ( ( mnSubTotalFlags & SubtotalFlags::IgnoreNestedStAg ) && pCell->IsSubTotal() ) ||
+ ( ( mnSubTotalFlags & SubtotalFlags::IgnoreErrVal ) && pCell->GetErrCode() != FormulaError::NONE ) )
+ {
+ incPos();
+ continue;
+ }
+ }
+ }
+
+ maCurCell = sc::toRefCell(maCurColPos.first, maCurColPos.second);
+ return true;
+ }
+ return false;
+}
+
+OUString ScCellIterator::getString() const
+{
+ return maCurCell.getString(&mrDoc);
+}
+
+ScCellValue ScCellIterator::getCellValue() const
+{
+ ScCellValue aRet;
+ aRet.meType = maCurCell.meType;
+
+ switch (maCurCell.meType)
+ {
+ case CELLTYPE_STRING:
+ aRet.mpString = new svl::SharedString(*maCurCell.mpString);
+ break;
+ case CELLTYPE_EDIT:
+ aRet.mpEditText = maCurCell.mpEditText->Clone().release();
+ break;
+ case CELLTYPE_VALUE:
+ aRet.mfValue = maCurCell.mfValue;
+ break;
+ case CELLTYPE_FORMULA:
+ aRet.mpFormula = maCurCell.mpFormula->Clone();
+ break;
+ default:
+ ;
+ }
+
+ return aRet;
+}
+
+bool ScCellIterator::hasString() const
+{
+ return maCurCell.hasString();
+}
+
+bool ScCellIterator::isEmpty() const
+{
+ return maCurCell.isEmpty();
+}
+
+bool ScCellIterator::equalsWithoutFormat( const ScAddress& rPos ) const
+{
+ ScRefCellValue aOther(mrDoc, rPos);
+ return maCurCell.equalsWithoutFormat(aOther);
+}
+
+bool ScCellIterator::first()
+{
+ if (!ValidTab(maCurPos.Tab()))
+ return false;
+
+ maCurPos = maStartPos;
+ const ScColumn* pCol = getColumn();
+
+ maCurColPos = pCol->maCells.position(maCurPos.Row());
+ return getCurrent();
+}
+
+bool ScCellIterator::next()
+{
+ incPos();
+ return getCurrent();
+}
+
+ScHorizontalCellIterator::ScHorizontalCellIterator(ScDocument& rDocument, SCTAB nTable,
+ SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) :
+ rDoc( rDocument ),
+ mnTab( nTable ),
+ nStartCol( nCol1 ),
+ nEndCol( nCol2 ),
+ nStartRow( nRow1 ),
+ nEndRow( nRow2 ),
+ mnCol( nCol1 ),
+ mnRow( nRow1 ),
+ mbMore( false )
+{
+ assert(mnTab < rDoc.GetTableCount() && "index out of bounds, FIX IT");
+
+ nEndCol = rDoc.maTabs[mnTab]->ClampToAllocatedColumns(nEndCol);
+ if (nEndCol < nStartCol) // E.g., somewhere completely outside allocated area
+ nEndCol = nStartCol - 1; // Empty
+
+ maColPositions.reserve( nEndCol-nStartCol+1 );
+
+ SetTab( mnTab );
+}
+
+ScHorizontalCellIterator::~ScHorizontalCellIterator()
+{
+}
+
+void ScHorizontalCellIterator::SetTab( SCTAB nTabP )
+{
+ mbMore = false;
+ mnTab = nTabP;
+ mnRow = nStartRow;
+ mnCol = nStartCol;
+ maColPositions.resize(0);
+
+ // Set the start position in each column.
+ for (SCCOL i = nStartCol; i <= nEndCol; ++i)
+ {
+ ScColumn* pCol = &rDoc.maTabs[mnTab]->aCol[i];
+ ColParam aParam;
+ aParam.maPos = pCol->maCells.position(nStartRow).first;
+ aParam.maEnd = pCol->maCells.end();
+ aParam.mnCol = i;
+
+ // find first non-empty element.
+ while (aParam.maPos != aParam.maEnd) {
+ if (aParam.maPos->type == sc::element_type_empty)
+ ++aParam.maPos;
+ else
+ {
+ maColPositions.push_back( aParam );
+ break;
+ }
+ }
+ }
+
+ if (maColPositions.empty())
+ return;
+
+ maColPos = maColPositions.begin();
+ mbMore = true;
+ SkipInvalid();
+}
+
+ScRefCellValue* ScHorizontalCellIterator::GetNext( SCCOL& rCol, SCROW& rRow )
+{
+ if (!mbMore)
+ {
+ debugiter("no more !\n");
+ return nullptr;
+ }
+
+ // Return the current non-empty cell, and move the cursor to the next one.
+ ColParam& r = *maColPos;
+
+ rCol = mnCol = r.mnCol;
+ rRow = mnRow;
+ debugiter("return col %d row %d\n", (int)rCol, (int)rRow);
+
+ size_t nOffset = static_cast<size_t>(mnRow) - r.maPos->position;
+ maCurCell = sc::toRefCell(r.maPos, nOffset);
+ Advance();
+ debugiter("advance to: col %d row %d\n", (int)maColPos->mnCol, (int)mnRow);
+
+ return &maCurCell;
+}
+
+bool ScHorizontalCellIterator::GetPos( SCCOL& rCol, SCROW& rRow )
+{
+ rCol = mnCol;
+ rRow = mnRow;
+ return mbMore;
+}
+
+// Skip any invalid / empty cells across the current row,
+// we only advance the cursor if the current entry is invalid.
+// if we return true we have a valid cursor (or hit the end)
+bool ScHorizontalCellIterator::SkipInvalidInRow()
+{
+ assert (mbMore);
+ assert (maColPos != maColPositions.end());
+
+ // Find the next non-empty cell in the current row.
+ while( maColPos != maColPositions.end() )
+ {
+ ColParam& r = *maColPos;
+ assert (r.maPos != r.maEnd);
+
+ size_t nRow = static_cast<size_t>(mnRow);
+
+ if (nRow >= r.maPos->position)
+ {
+ if (nRow < r.maPos->position + r.maPos->size)
+ {
+ mnCol = maColPos->mnCol;
+ debugiter("found valid cell at column %d, row %d\n",
+ (int)mnCol, (int)mnRow);
+ assert(r.maPos->type != sc::element_type_empty);
+ return true;
+ }
+ else
+ {
+ bool bMoreBlocksInColumn = false;
+ // This block is behind the current row position. Advance the block.
+ for (++r.maPos; r.maPos != r.maEnd; ++r.maPos)
+ {
+ if (nRow < r.maPos->position + r.maPos->size &&
+ r.maPos->type != sc::element_type_empty)
+ {
+ bMoreBlocksInColumn = true;
+ break;
+ }
+ }
+ if (!bMoreBlocksInColumn)
+ {
+ debugiter("remove column %d at row %d\n",
+ (int)maColPos->mnCol, (int)nRow);
+ maColPos = maColPositions.erase(maColPos);
+ if (maColPositions.empty())
+ {
+ debugiter("no more columns\n");
+ mbMore = false;
+ }
+ }
+ else
+ {
+ debugiter("advanced column %d to block starting row %d, retrying\n",
+ (int)maColPos->mnCol, r.maPos->position);
+ }
+ }
+ }
+ else
+ {
+ debugiter("skip empty cells at column %d, row %d\n",
+ (int)maColPos->mnCol, (int)nRow);
+ ++maColPos;
+ }
+ }
+
+ // No more columns with anything interesting in them ?
+ if (maColPositions.empty())
+ {
+ debugiter("no more live columns left - done\n");
+ mbMore = false;
+ return true;
+ }
+
+ return false;
+}
+
+/// Find the next row that has some real content in one of its columns.
+SCROW ScHorizontalCellIterator::FindNextNonEmptyRow()
+{
+ size_t nNextRow = rDoc.MaxRow()+1;
+
+ for (const ColParam& r : maColPositions)
+ {
+ assert(o3tl::make_unsigned(mnRow) <= r.maPos->position);
+ nNextRow = std::min (nNextRow, static_cast<size_t>(r.maPos->position));
+ }
+
+ SCROW nRow = std::max(static_cast<SCROW>(nNextRow), mnRow);
+ debugiter("Next non empty row is %d\n", (int) nRow);
+ return nRow;
+}
+
+void ScHorizontalCellIterator::Advance()
+{
+ assert (mbMore);
+ assert (maColPos != maColPositions.end());
+
+ ++maColPos;
+
+ SkipInvalid();
+}
+
+void ScHorizontalCellIterator::SkipInvalid()
+{
+ if (maColPos == maColPositions.end() ||
+ !SkipInvalidInRow())
+ {
+ mnRow++;
+
+ if (mnRow > nEndRow)
+ {
+ mbMore = false;
+ return;
+ }
+
+ maColPos = maColPositions.begin();
+ debugiter("moving to next row\n");
+ if (SkipInvalidInRow())
+ {
+ debugiter("moved to valid cell in next row (or end)\n");
+ return;
+ }
+
+ mnRow = FindNextNonEmptyRow();
+ maColPos = maColPositions.begin();
+ bool bCorrect = SkipInvalidInRow();
+ assert (bCorrect); (void) bCorrect;
+ }
+
+ if (mnRow > nEndRow)
+ mbMore = false;
+}
+
+ScHorizontalValueIterator::ScHorizontalValueIterator( ScDocument& rDocument,
+ const ScRange& rRange ) :
+ rDoc( rDocument ),
+ nEndTab( rRange.aEnd.Tab() ),
+ bCalcAsShown( rDocument.GetDocOptions().IsCalcAsShown() )
+{
+ SCCOL nStartCol = rRange.aStart.Col();
+ SCROW nStartRow = rRange.aStart.Row();
+ SCTAB nStartTab = rRange.aStart.Tab();
+ SCCOL nEndCol = rRange.aEnd.Col();
+ SCROW nEndRow = rRange.aEnd.Row();
+ PutInOrder( nStartCol, nEndCol);
+ PutInOrder( nStartRow, nEndRow);
+ PutInOrder( nStartTab, nEndTab );
+
+ if (!rDoc.ValidCol(nStartCol)) nStartCol = rDoc.MaxCol();
+ if (!rDoc.ValidCol(nEndCol)) nEndCol = rDoc.MaxCol();
+ if (!rDoc.ValidRow(nStartRow)) nStartRow = rDoc.MaxRow();
+ if (!rDoc.ValidRow(nEndRow)) nEndRow = rDoc.MaxRow();
+ if (!ValidTab(nStartTab)) nStartTab = MAXTAB;
+ if (!ValidTab(nEndTab)) nEndTab = MAXTAB;
+
+ nCurCol = nStartCol;
+ nCurRow = nStartRow;
+ nCurTab = nStartTab;
+
+ nNumFormat = 0; // Will be initialized in GetNumberFormat()
+ pAttrArray = nullptr;
+ nAttrEndRow = 0;
+
+ pCellIter.reset( new ScHorizontalCellIterator( rDoc, nStartTab, nStartCol,
+ nStartRow, nEndCol, nEndRow ) );
+}
+
+ScHorizontalValueIterator::~ScHorizontalValueIterator()
+{
+}
+
+bool ScHorizontalValueIterator::GetNext( double& rValue, FormulaError& rErr )
+{
+ bool bFound = false;
+ while ( !bFound )
+ {
+ ScRefCellValue* pCell = pCellIter->GetNext( nCurCol, nCurRow );
+ while ( !pCell )
+ {
+ if ( nCurTab < nEndTab )
+ {
+ pCellIter->SetTab( ++nCurTab);
+ pCell = pCellIter->GetNext( nCurCol, nCurRow );
+ }
+ else
+ return false;
+ }
+ switch (pCell->meType)
+ {
+ case CELLTYPE_VALUE:
+ {
+ rValue = pCell->mfValue;
+ rErr = FormulaError::NONE;
+ if ( bCalcAsShown )
+ {
+ ScColumn* pCol = &rDoc.maTabs[nCurTab]->aCol[nCurCol];
+ ScAttrArray_IterGetNumberFormat( nNumFormat, pAttrArray,
+ nAttrEndRow, pCol->pAttrArray.get(), nCurRow, rDoc );
+ rValue = rDoc.RoundValueAsShown( rValue, nNumFormat );
+ }
+ bFound = true;
+ }
+ break;
+ case CELLTYPE_FORMULA:
+ {
+ rErr = pCell->mpFormula->GetErrCode();
+ if (rErr != FormulaError::NONE || pCell->mpFormula->IsValue())
+ {
+ rValue = pCell->mpFormula->GetValue();
+ bFound = true;
+ }
+ }
+ break;
+ case CELLTYPE_STRING :
+ case CELLTYPE_EDIT :
+ break;
+ default: ; // nothing
+ }
+ }
+ return bFound;
+}
+
+ScHorizontalAttrIterator::ScHorizontalAttrIterator( ScDocument& rDocument, SCTAB nTable,
+ SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) :
+ rDoc( rDocument ),
+ nTab( nTable ),
+ nStartCol( nCol1 ),
+ nStartRow( nRow1 ),
+ nEndCol( nCol2 ),
+ nEndRow( nRow2 )
+{
+ assert(nTab < rDoc.GetTableCount() && "index out of bounds, FIX IT");
+ assert(rDoc.maTabs[nTab]);
+
+ nRow = nStartRow;
+ nCol = nStartCol;
+
+ pIndices.reset( new SCSIZE[nEndCol-nStartCol+1] );
+ pNextEnd.reset( new SCROW[nEndCol-nStartCol+1] );
+ pHorizEnd.reset( new SCCOL[nEndCol-nStartCol+1] );
+ ppPatterns.reset( new const ScPatternAttr*[nEndCol-nStartCol+1] );
+
+ InitForNextRow(true);
+}
+
+ScHorizontalAttrIterator::~ScHorizontalAttrIterator()
+{
+}
+
+void ScHorizontalAttrIterator::InitForNextRow(bool bInitialization)
+{
+ nMinNextEnd = rDoc.MaxRow();
+ SCCOL nThisHead = 0;
+
+ for (SCCOL i=nStartCol; i<=nEndCol; i++)
+ {
+ SCCOL nPos = i - nStartCol;
+ if ( bInitialization || pNextEnd[nPos] < nRow )
+ {
+ const ScAttrArray& pArray = rDoc.maTabs[nTab]->ColumnData(i).AttrArray();
+
+ SCSIZE nIndex;
+ if (bInitialization)
+ {
+ if ( pArray.Count() )
+ pArray.Search( nStartRow, nIndex );
+ else
+ nIndex = 0;
+ pIndices[nPos] = nIndex;
+ pHorizEnd[nPos] = rDoc.MaxCol()+1; // only for assert()
+ }
+ else
+ nIndex = ++pIndices[nPos];
+
+ if ( !nIndex && !pArray.Count() )
+ {
+ pNextEnd[nPos] = rDoc.MaxRow();
+ assert( pNextEnd[nPos] >= nRow && "Sequence out of order" );
+ ppPatterns[nPos] = rDoc.GetDefPattern();
+ }
+ else if ( nIndex < pArray.Count() )
+ {
+ const ScPatternAttr* pPattern = pArray.mvData[nIndex].pPattern;
+ SCROW nThisEnd = pArray.mvData[nIndex].nEndRow;
+ pNextEnd[nPos] = nThisEnd;
+ assert( pNextEnd[nPos] >= nRow && "Sequence out of order" );
+ ppPatterns[nPos] = pPattern;
+ }
+ else
+ {
+ assert(!"AttrArray does not range to MAXROW");
+ pNextEnd[nPos] = rDoc.MaxRow();
+ ppPatterns[nPos] = nullptr;
+ }
+ }
+
+ if ( nMinNextEnd > pNextEnd[nPos] )
+ nMinNextEnd = pNextEnd[nPos];
+
+ // store positions of ScHorizontalAttrIterator elements (minimizing expensive ScPatternAttr comparisons)
+ if (i > nStartCol && ppPatterns[nThisHead] != ppPatterns[nPos])
+ {
+ pHorizEnd[nThisHead] = i - 1;
+ nThisHead = nPos; // start position of the next horizontal group
+ }
+ }
+
+ pHorizEnd[nThisHead] = nEndCol; // set the end position of the last horizontal group, too
+}
+
+const ScPatternAttr* ScHorizontalAttrIterator::GetNext( SCCOL& rCol1, SCCOL& rCol2, SCROW& rRow )
+{
+ assert(nTab < rDoc.GetTableCount() && "index out of bounds, FIX IT");
+ for (;;)
+ {
+ if ( nCol <= nEndCol )
+ {
+ const ScPatternAttr* pPat = ppPatterns[nCol-nStartCol];
+ rRow = nRow;
+ rCol1 = nCol;
+ assert( pHorizEnd[nCol-nStartCol] < rDoc.MaxCol()+1 && "missing stored data" );
+ nCol = pHorizEnd[nCol-nStartCol];
+ rCol2 = nCol;
+ ++nCol; // Count up for next call
+ return pPat; // Found it!
+ }
+
+ // Next row
+ ++nRow;
+ if ( nRow > nEndRow ) // Already at the end?
+ return nullptr; // Found nothing
+ nCol = nStartCol; // Start at the left again
+
+ if ( nRow > nMinNextEnd )
+ InitForNextRow(false);
+ }
+}
+
+static bool IsGreater( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
+{
+ return ( nRow1 > nRow2 ) || ( nRow1 == nRow2 && nCol1 > nCol2 );
+}
+
+ScUsedAreaIterator::ScUsedAreaIterator( ScDocument& rDocument, SCTAB nTable,
+ SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
+ : aCellIter( rDocument, nTable, nCol1, nRow1, nCol2, nRow2 )
+ , aAttrIter( rDocument, nTable, nCol1, nRow1, nCol2, nRow2 )
+ , nNextCol( nCol1 )
+ , nNextRow( nRow1 )
+ , nCellCol( 0 )
+ , nCellRow( 0 )
+ , nAttrCol1( 0 )
+ , nAttrCol2( 0 )
+ , nAttrRow( 0 )
+ , nFoundStartCol( 0 )
+ , nFoundEndCol( 0 )
+ , nFoundRow( 0 )
+ , pFoundPattern( nullptr )
+{
+ pCell = aCellIter.GetNext( nCellCol, nCellRow );
+ pPattern = aAttrIter.GetNext( nAttrCol1, nAttrCol2, nAttrRow );
+}
+
+ScUsedAreaIterator::~ScUsedAreaIterator()
+{
+}
+
+bool ScUsedAreaIterator::GetNext()
+{
+ // Forward iterators
+ if ( pCell && IsGreater( nNextCol, nNextRow, nCellCol, nCellRow ) )
+ pCell = aCellIter.GetNext( nCellCol, nCellRow );
+
+ while (pCell && pCell->isEmpty())
+ pCell = aCellIter.GetNext( nCellCol, nCellRow );
+
+ if ( pPattern && IsGreater( nNextCol, nNextRow, nAttrCol2, nAttrRow ) )
+ pPattern = aAttrIter.GetNext( nAttrCol1, nAttrCol2, nAttrRow );
+
+ if ( pPattern && nAttrRow == nNextRow && nAttrCol1 < nNextCol )
+ nAttrCol1 = nNextCol;
+
+ // Find next area
+ bool bFound = true;
+ bool bUseCell = false;
+
+ if ( pCell && pPattern )
+ {
+ if ( IsGreater( nCellCol, nCellRow, nAttrCol1, nAttrRow ) ) // Only attributes at the beginning?
+ {
+ maFoundCell.clear();
+ pFoundPattern = pPattern;
+ nFoundRow = nAttrRow;
+ nFoundStartCol = nAttrCol1;
+ if ( nCellRow == nAttrRow && nCellCol <= nAttrCol2 ) // Area also contains cell?
+ nFoundEndCol = nCellCol - 1; // Only until right before the cell
+ else
+ nFoundEndCol = nAttrCol2; // Everything
+ }
+ else
+ {
+ bUseCell = true;
+ if ( nAttrRow == nCellRow && nAttrCol1 == nCellCol ) // Attributes on the cell?
+ pFoundPattern = pPattern;
+ else
+ pFoundPattern = nullptr;
+ }
+ }
+ else if ( pCell ) // Just a cell -> take over right away
+ {
+ pFoundPattern = nullptr;
+ bUseCell = true; // Cell position
+ }
+ else if ( pPattern ) // Just attributes -> take over right away
+ {
+ maFoundCell.clear();
+ pFoundPattern = pPattern;
+ nFoundRow = nAttrRow;
+ nFoundStartCol = nAttrCol1;
+ nFoundEndCol = nAttrCol2;
+ }
+ else // Nothing
+ bFound = false;
+
+ if ( bUseCell ) // Cell position
+ {
+ if (pCell)
+ maFoundCell = *pCell;
+ else
+ maFoundCell.clear();
+
+ nFoundRow = nCellRow;
+ nFoundStartCol = nFoundEndCol = nCellCol;
+ }
+
+ if (bFound)
+ {
+ nNextRow = nFoundRow;
+ nNextCol = nFoundEndCol + 1;
+ }
+
+ return bFound;
+}
+
+ScDocAttrIterator::ScDocAttrIterator(ScDocument& rDocument, SCTAB nTable,
+ SCCOL nCol1, SCROW nRow1,
+ SCCOL nCol2, SCROW nRow2) :
+ rDoc( rDocument ),
+ nTab( nTable ),
+ nEndCol( nCol2 ),
+ nStartRow( nRow1 ),
+ nEndRow( nRow2 ),
+ nCol( nCol1 )
+{
+ if ( ValidTab(nTab) && nTab < rDoc.GetTableCount() && rDoc.maTabs[nTab] )
+ pColIter = rDoc.maTabs[nTab]->ColumnData(nCol).CreateAttrIterator( nStartRow, nEndRow );
+}
+
+ScDocAttrIterator::~ScDocAttrIterator()
+{
+}
+
+const ScPatternAttr* ScDocAttrIterator::GetNext( SCCOL& rCol, SCROW& rRow1, SCROW& rRow2 )
+{
+ while ( pColIter )
+ {
+ const ScPatternAttr* pPattern = pColIter->Next( rRow1, rRow2 );
+ if ( pPattern )
+ {
+ rCol = nCol;
+ return pPattern;
+ }
+
+ ++nCol;
+ if ( nCol <= nEndCol )
+ pColIter = rDoc.maTabs[nTab]->ColumnData(nCol).CreateAttrIterator( nStartRow, nEndRow );
+ else
+ pColIter.reset();
+ }
+ return nullptr; // Nothing anymore
+}
+
+ScDocRowHeightUpdater::TabRanges::TabRanges(SCTAB nTab, SCROW nMaxRow) :
+ mnTab(nTab), maRanges(nMaxRow)
+{
+}
+
+ScDocRowHeightUpdater::ScDocRowHeightUpdater(ScDocument& rDoc, OutputDevice* pOutDev, double fPPTX, double fPPTY, const vector<TabRanges>* pTabRangesArray) :
+ mrDoc(rDoc), mpOutDev(pOutDev), mfPPTX(fPPTX), mfPPTY(fPPTY), mpTabRangesArray(pTabRangesArray)
+{
+}
+
+void ScDocRowHeightUpdater::update()
+{
+ if (!mpTabRangesArray || mpTabRangesArray->empty())
+ {
+ // No ranges defined. Update all rows in all tables.
+ updateAll();
+ return;
+ }
+
+ sal_uInt64 nCellCount = 0;
+ for (const auto& rTabRanges : *mpTabRangesArray)
+ {
+ const SCTAB nTab = rTabRanges.mnTab;
+ if (!ValidTab(nTab) || nTab >= mrDoc.GetTableCount() || !mrDoc.maTabs[nTab])
+ continue;
+
+ ScFlatBoolRowSegments::RangeData aData;
+ ScFlatBoolRowSegments::RangeIterator aRangeItr(rTabRanges.maRanges);
+ for (bool bFound = aRangeItr.getFirst(aData); bFound; bFound = aRangeItr.getNext(aData))
+ {
+ if (!aData.mbValue)
+ continue;
+
+ nCellCount += mrDoc.maTabs[nTab]->GetWeightedCount(aData.mnRow1, aData.mnRow2);
+ }
+ }
+
+ ScProgress aProgress(mrDoc.GetDocumentShell(), ScResId(STR_PROGRESS_HEIGHTING), nCellCount, true);
+
+ Fraction aZoom(1, 1);
+ sal_uInt64 nProgressStart = 0;
+ for (const auto& rTabRanges : *mpTabRangesArray)
+ {
+ const SCTAB nTab = rTabRanges.mnTab;
+ if (!ValidTab(nTab) || nTab >= mrDoc.GetTableCount() || !mrDoc.maTabs[nTab])
+ continue;
+
+ sc::RowHeightContext aCxt(mrDoc.MaxRow(), mfPPTX, mfPPTY, aZoom, aZoom, mpOutDev);
+ ScFlatBoolRowSegments::RangeData aData;
+ ScFlatBoolRowSegments::RangeIterator aRangeItr(rTabRanges.maRanges);
+ for (bool bFound = aRangeItr.getFirst(aData); bFound; bFound = aRangeItr.getNext(aData))
+ {
+ if (!aData.mbValue)
+ continue;
+
+ mrDoc.maTabs[nTab]->SetOptimalHeight(
+ aCxt, aData.mnRow1, aData.mnRow2, true, &aProgress, nProgressStart);
+
+ nProgressStart += mrDoc.maTabs[nTab]->GetWeightedCount(aData.mnRow1, aData.mnRow2);
+ }
+ }
+}
+
+void ScDocRowHeightUpdater::updateAll()
+{
+ sal_uInt64 nCellCount = 0;
+ for (SCTAB nTab = 0; nTab < mrDoc.GetTableCount(); ++nTab)
+ {
+ if (!ValidTab(nTab) || !mrDoc.maTabs[nTab])
+ continue;
+
+ nCellCount += mrDoc.maTabs[nTab]->GetWeightedCount();
+ }
+
+ ScProgress aProgress(mrDoc.GetDocumentShell(), ScResId(STR_PROGRESS_HEIGHTING), nCellCount, true);
+
+ Fraction aZoom(1, 1);
+ sc::RowHeightContext aCxt(mrDoc.MaxRow(), mfPPTX, mfPPTY, aZoom, aZoom, mpOutDev);
+ sal_uInt64 nProgressStart = 0;
+ for (SCTAB nTab = 0; nTab < mrDoc.GetTableCount(); ++nTab)
+ {
+ if (!ValidTab(nTab) || !mrDoc.maTabs[nTab])
+ continue;
+
+ mrDoc.maTabs[nTab]->SetOptimalHeight(aCxt, 0, mrDoc.MaxRow(), true, &aProgress, nProgressStart);
+ nProgressStart += mrDoc.maTabs[nTab]->GetWeightedCount();
+ }
+}
+
+ScAttrRectIterator::ScAttrRectIterator(ScDocument& rDocument, SCTAB nTable,
+ SCCOL nCol1, SCROW nRow1,
+ SCCOL nCol2, SCROW nRow2) :
+ rDoc( rDocument ),
+ nTab( nTable ),
+ nEndCol( nCol2 ),
+ nStartRow( nRow1 ),
+ nEndRow( nRow2 ),
+ nIterStartCol( nCol1 ),
+ nIterEndCol( nCol1 )
+{
+ if ( ValidTab(nTab) && nTab < rDoc.GetTableCount() && rDoc.maTabs[nTab] )
+ {
+ pColIter = rDoc.maTabs[nTab]->ColumnData(nIterStartCol).CreateAttrIterator( nStartRow, nEndRow );
+ while ( nIterEndCol < nEndCol &&
+ rDoc.maTabs[nTab]->ColumnData(nIterEndCol).IsAllAttrEqual(
+ rDoc.maTabs[nTab]->ColumnData(nIterEndCol+1), nStartRow, nEndRow ) )
+ ++nIterEndCol;
+ }
+}
+
+ScAttrRectIterator::~ScAttrRectIterator()
+{
+}
+
+void ScAttrRectIterator::DataChanged()
+{
+ if (pColIter)
+ {
+ SCROW nNextRow = pColIter->GetNextRow();
+ pColIter = rDoc.maTabs[nTab]->ColumnData(nIterStartCol).CreateAttrIterator( nNextRow, nEndRow );
+ }
+}
+
+const ScPatternAttr* ScAttrRectIterator::GetNext( SCCOL& rCol1, SCCOL& rCol2,
+ SCROW& rRow1, SCROW& rRow2 )
+{
+ while ( pColIter )
+ {
+ const ScPatternAttr* pPattern = pColIter->Next( rRow1, rRow2 );
+ if ( pPattern )
+ {
+ rCol1 = nIterStartCol;
+ rCol2 = nIterEndCol;
+ return pPattern;
+ }
+
+ nIterStartCol = nIterEndCol+1;
+ if ( nIterStartCol <= nEndCol )
+ {
+ nIterEndCol = nIterStartCol;
+ pColIter = rDoc.maTabs[nTab]->ColumnData(nIterStartCol).CreateAttrIterator( nStartRow, nEndRow );
+ while ( nIterEndCol < nEndCol &&
+ rDoc.maTabs[nTab]->ColumnData(nIterEndCol).IsAllAttrEqual(
+ rDoc.maTabs[nTab]->ColumnData(nIterEndCol+1), nStartRow, nEndRow ) )
+ ++nIterEndCol;
+ }
+ else
+ pColIter.reset();
+ }
+ return nullptr; // Nothing anymore
+}
+
+ScRowBreakIterator::ScRowBreakIterator(set<SCROW>& rBreaks) :
+ mrBreaks(rBreaks),
+ maItr(rBreaks.begin()), maEnd(rBreaks.end())
+{
+}
+
+SCROW ScRowBreakIterator::first()
+{
+ maItr = mrBreaks.begin();
+ return maItr == maEnd ? NOT_FOUND : *maItr;
+}
+
+SCROW ScRowBreakIterator::next()
+{
+ ++maItr;
+ return maItr == maEnd ? NOT_FOUND : *maItr;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/docparam.cxx b/sc/source/core/data/docparam.cxx
new file mode 100644
index 000000000..3e2dea1b6
--- /dev/null
+++ b/sc/source/core/data/docparam.cxx
@@ -0,0 +1,25 @@
+/* -*- 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 <docparam.hxx>
+
+ScColWidthParam::ScColWidthParam() :
+ mnMaxTextRow(-1), mnMaxTextLen(0), mbSimpleText(true) {}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/docpool.cxx b/sc/source/core/data/docpool.cxx
new file mode 100644
index 000000000..e0a07582e
--- /dev/null
+++ b/sc/source/core/data/docpool.cxx
@@ -0,0 +1,619 @@
+/* -*- 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 <scitems.hxx>
+
+#include <comphelper/string.hxx>
+#include <i18nutil/unicode.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <svl/itemiter.hxx>
+#include <svl/stritem.hxx>
+#include <svx/algitem.hxx>
+#include <editeng/boxitem.hxx>
+#include <editeng/lineitem.hxx>
+#include <editeng/brushitem.hxx>
+#include <editeng/charreliefitem.hxx>
+#include <editeng/contouritem.hxx>
+#include <editeng/colritem.hxx>
+#include <editeng/crossedoutitem.hxx>
+#include <editeng/emphasismarkitem.hxx>
+#include <editeng/fhgtitem.hxx>
+#include <editeng/fontitem.hxx>
+#include <editeng/forbiddenruleitem.hxx>
+#include <editeng/frmdiritem.hxx>
+#include <editeng/hngpnctitem.hxx>
+#include <editeng/itemtype.hxx>
+#include <editeng/editrids.hrc>
+#include <editeng/eerdll.hxx>
+#include <editeng/langitem.hxx>
+#include <editeng/lrspitem.hxx>
+#include <svx/pageitem.hxx>
+#include <editeng/pbinitem.hxx>
+#include <editeng/postitem.hxx>
+#include <svx/rotmodit.hxx>
+#include <editeng/scriptspaceitem.hxx>
+#include <editeng/shaditem.hxx>
+#include <editeng/shdditem.hxx>
+#include <editeng/sizeitem.hxx>
+#include <editeng/udlnitem.hxx>
+#include <editeng/ulspitem.hxx>
+#include <editeng/wghtitem.hxx>
+#include <editeng/wrlmitem.hxx>
+#include <editeng/xmlcnitm.hxx>
+#include <editeng/justifyitem.hxx>
+
+#include <docpool.hxx>
+#include <global.hxx>
+#include <attrib.hxx>
+#include <patattr.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <scmod.hxx>
+#include <sc.hrc>
+
+// ATTR_FONT_TWOLINES (not used) was changed to ATTR_USERDEF (not saved in binary format) in 641c
+
+namespace {
+
+SvxFontItem* getDefaultFontItem(LanguageType eLang, DefaultFontType nFontType, sal_uInt16 nItemId)
+{
+ vcl::Font aDefFont = OutputDevice::GetDefaultFont( nFontType, eLang, GetDefaultFontFlags::OnlyOne );
+ SvxFontItem* pNewItem = new SvxFontItem( aDefFont.GetFamilyType(), aDefFont.GetFamilyName(), aDefFont.GetStyleName(),
+ aDefFont.GetPitch(), aDefFont.GetCharSet(), nItemId );
+
+ return pNewItem;
+}
+
+}
+
+SfxItemInfo const aItemInfos[] =
+{
+ { SID_ATTR_CHAR_FONT, true }, // ATTR_FONT
+ { SID_ATTR_CHAR_FONTHEIGHT, true }, // ATTR_FONT_HEIGHT
+ { SID_ATTR_CHAR_WEIGHT, true }, // ATTR_FONT_WEIGHT
+ { SID_ATTR_CHAR_POSTURE, true }, // ATTR_FONT_POSTURE
+ { SID_ATTR_CHAR_UNDERLINE, true }, // ATTR_FONT_UNDERLINE
+ { SID_ATTR_CHAR_OVERLINE, true }, // ATTR_FONT_OVERLINE
+ { SID_ATTR_CHAR_STRIKEOUT, true }, // ATTR_FONT_CROSSEDOUT
+ { SID_ATTR_CHAR_CONTOUR, true }, // ATTR_FONT_CONTOUR
+ { SID_ATTR_CHAR_SHADOWED, true }, // ATTR_FONT_SHADOWED
+ { SID_ATTR_CHAR_COLOR, true }, // ATTR_FONT_COLOR
+ { SID_ATTR_CHAR_LANGUAGE, true }, // ATTR_FONT_LANGUAGE
+ { SID_ATTR_CHAR_CJK_FONT, true }, // ATTR_CJK_FONT from 614
+ { SID_ATTR_CHAR_CJK_FONTHEIGHT, true }, // ATTR_CJK_FONT_HEIGHT from 614
+ { SID_ATTR_CHAR_CJK_WEIGHT, true }, // ATTR_CJK_FONT_WEIGHT from 614
+ { SID_ATTR_CHAR_CJK_POSTURE, true }, // ATTR_CJK_FONT_POSTURE from 614
+ { SID_ATTR_CHAR_CJK_LANGUAGE, true }, // ATTR_CJK_FONT_LANGUAGE from 614
+ { SID_ATTR_CHAR_CTL_FONT, true }, // ATTR_CTL_FONT from 614
+ { SID_ATTR_CHAR_CTL_FONTHEIGHT, true }, // ATTR_CTL_FONT_HEIGHT from 614
+ { SID_ATTR_CHAR_CTL_WEIGHT, true }, // ATTR_CTL_FONT_WEIGHT from 614
+ { SID_ATTR_CHAR_CTL_POSTURE, true }, // ATTR_CTL_FONT_POSTURE from 614
+ { SID_ATTR_CHAR_CTL_LANGUAGE, true }, // ATTR_CTL_FONT_LANGUAGE from 614
+ { SID_ATTR_CHAR_EMPHASISMARK, true }, // ATTR_FONT_EMPHASISMARK from 614
+ { 0, true }, // ATTR_USERDEF from 614 / 641c
+ { SID_ATTR_CHAR_WORDLINEMODE, true }, // ATTR_FONT_WORDLINE from 632b
+ { SID_ATTR_CHAR_RELIEF, true }, // ATTR_FONT_RELIEF from 632b
+ { SID_ATTR_ALIGN_HYPHENATION, true }, // ATTR_HYPHENATE from 632b
+ { 0, true }, // ATTR_SCRIPTSPACE from 614d
+ { 0, true }, // ATTR_HANGPUNCTUATION from 614d
+ { SID_ATTR_PARA_FORBIDDEN_RULES,true }, // ATTR_FORBIDDEN_RULES from 614d
+ { SID_ATTR_ALIGN_HOR_JUSTIFY, true }, // ATTR_HOR_JUSTIFY
+ { SID_ATTR_ALIGN_HOR_JUSTIFY_METHOD, true }, // ATTR_HOR_JUSTIFY_METHOD
+ { SID_ATTR_ALIGN_INDENT, true }, // ATTR_INDENT from 350
+ { SID_ATTR_ALIGN_VER_JUSTIFY, true }, // ATTR_VER_JUSTIFY
+ { SID_ATTR_ALIGN_VER_JUSTIFY_METHOD, true }, // ATTR_VER_JUSTIFY_METHOD
+ { SID_ATTR_ALIGN_STACKED, true }, // ATTR_STACKED from 680/dr14 (replaces ATTR_ORIENTATION)
+ { SID_ATTR_ALIGN_DEGREES, true }, // ATTR_ROTATE_VALUE from 367
+ { SID_ATTR_ALIGN_LOCKPOS, true }, // ATTR_ROTATE_MODE from 367
+ { SID_ATTR_ALIGN_ASIANVERTICAL, true }, // ATTR_VERTICAL_ASIAN from 642
+ { SID_ATTR_FRAMEDIRECTION, true }, // ATTR_WRITINGDIR from 643
+ { SID_ATTR_ALIGN_LINEBREAK, true }, // ATTR_LINEBREAK
+ { SID_ATTR_ALIGN_SHRINKTOFIT, true }, // ATTR_SHRINKTOFIT from 680/dr14
+ { SID_ATTR_BORDER_DIAG_TLBR, true }, // ATTR_BORDER_TLBR from 680/dr14
+ { SID_ATTR_BORDER_DIAG_BLTR, true }, // ATTR_BORDER_BLTR from 680/dr14
+ { SID_ATTR_ALIGN_MARGIN, true }, // ATTR_MARGIN
+ { 0, true }, // ATTR_MERGE
+ { 0, true }, // ATTR_MERGE_FLAG
+ { SID_ATTR_NUMBERFORMAT_VALUE, true }, // ATTR_VALUE_FORMAT
+ { 0, true }, // ATTR_LANGUAGE_FORMAT from 329, is combined with SID_ATTR_NUMBERFORMAT_VALUE in the dialog
+ { SID_ATTR_BRUSH, true }, // ATTR_BACKGROUND
+ { SID_SCATTR_PROTECTION, true }, // ATTR_PROTECTION
+ { SID_ATTR_BORDER_OUTER, true }, // ATTR_BORDER
+ { SID_ATTR_BORDER_INNER, true }, // ATTR_BORDER_INNER
+ { SID_ATTR_BORDER_SHADOW, true }, // ATTR_SHADOW
+ { 0, true }, // ATTR_VALIDDATA
+ { 0, true }, // ATTR_CONDITIONAL
+ { 0, true }, // ATTR_HYPERLINK
+ { 0, true }, // ATTR_PATTERN
+ { SID_ATTR_LRSPACE, true }, // ATTR_LRSPACE
+ { SID_ATTR_ULSPACE, true }, // ATTR_ULSPACE
+ { SID_ATTR_PAGE, true }, // ATTR_PAGE
+ { SID_ATTR_PAGE_PAPERBIN, true }, // ATTR_PAGE_PAPERBIN
+ { SID_ATTR_PAGE_SIZE, true }, // ATTR_PAGE_SIZE
+ { SID_ATTR_PAGE_EXT1, true }, // ATTR_PAGE_HORCENTER
+ { SID_ATTR_PAGE_EXT2, true }, // ATTR_PAGE_VERCENTER
+ { SID_ATTR_PAGE_ON, true }, // ATTR_PAGE_ON
+ { SID_ATTR_PAGE_DYNAMIC, true }, // ATTR_PAGE_DYNAMIC
+ { SID_ATTR_PAGE_SHARED, true }, // ATTR_PAGE_SHARED
+ { SID_ATTR_PAGE_SHARED_FIRST, true }, // ATTR_PAGE_SHARED_FIRST
+ { 0, true }, // ATTR_PAGE_NOTES aka. SID_SCATTR_PAGE_NOTES
+ { 0, true }, // ATTR_PAGE_GRID aka. SID_SCATTR_PAGE_GRID
+ { 0, true }, // ATTR_PAGE_HEADERS aka. SID_SCATTR_PAGE_HEADERS
+ { 0, true }, // ATTR_PAGE_CHARTS aka. SID_SCATTR_PAGE_CHARTS
+ { 0, true }, // ATTR_PAGE_OBJECTS aka. SID_SCATTR_PAGE_OBJECTS
+ { 0, true }, // ATTR_PAGE_DRAWINGS aka. SID_SCATTR_PAGE_DRAWINGS
+ { 0, true }, // ATTR_PAGE_TOPDOWN aka. SID_SCATTR_PAGE_TOPDOWN
+ { 0, true }, // ATTR_PAGE_SCALE aka SID_SCATTR_PAGE_SCALE
+ { 0, true }, // ATTR_PAGE_SCALETOPAGES aka SID_SCATTR_PAGE_SCALETOPAGES
+ { 0, true }, // ATTR_PAGE_FIRSTPAGENO aka SID_SCATTR_PAGE_FIRSTPAGENO
+ { 0, true }, // ATTR_PAGE_HEADERLEFT aka SID_SCATTR_PAGE_HEADERLEFT
+ { 0, true }, // ATTR_PAGE_FOOTERLEFT aka SID_SCATTR_PAGE_FOOTERLEFT
+ { 0, true }, // ATTR_PAGE_HEADERRIGHT aka SID_SCATTR_PAGE_HEADERRIGHT
+ { 0, true }, // ATTR_PAGE_FOOTERRIGHT aka. SID_SCATTR_PAGE_FOOTERRIGHT
+ { 0, true }, // ATTR_PAGE_HEADERFIRST aka. SID_SCATTR_PAGE_HEADERFIRST
+ { 0, true }, // ATTR_PAGE_FOOTERFIRST aka. SID_SCATTR_PAGE_FOOTERFIRST`
+ { SID_ATTR_PAGE_HEADERSET, true }, // ATTR_PAGE_HEADERSET
+ { SID_ATTR_PAGE_FOOTERSET, true }, // ATTR_PAGE_FOOTERSET
+ { 0, true }, // ATTR_PAGE_FORMULAS aka. SID_SCATTR_PAGE_FORMULAS
+ { 0, true }, // ATTR_PAGE_NULLVALS aka. SID_SCATTR_PAGE_NULLVALS
+ { 0, true }, // ATTR_PAGE_SCALETO aka. SID_SCATTR_PAGE_SCALETO
+ { 0, true } // ATTR_HIDDEN
+};
+static_assert(
+ SAL_N_ELEMENTS(aItemInfos) == ATTR_ENDINDEX - ATTR_STARTINDEX + 1, "these must match");
+
+ScDocumentPool::ScDocumentPool()
+
+ : SfxItemPool ( "ScDocumentPool",
+ ATTR_STARTINDEX, ATTR_ENDINDEX,
+ aItemInfos, nullptr ),
+ mvPoolDefaults(ATTR_ENDINDEX-ATTR_STARTINDEX+1),
+ mnCurrentMaxKey(0)
+{
+
+ LanguageType nDefLang, nCjkLang, nCtlLang;
+ bool bAutoSpell;
+ ScModule::GetSpellSettings( nDefLang, nCjkLang, nCtlLang, bAutoSpell );
+
+ // latin font from GetDefaultFonts is not used, DEFAULTFONT_LATIN_SPREADSHEET instead
+ SvxFontItem* pStdFont = getDefaultFontItem(nDefLang, DefaultFontType::LATIN_SPREADSHEET, ATTR_FONT);
+ SvxFontItem* pCjkFont = getDefaultFontItem(nCjkLang, DefaultFontType::CJK_SPREADSHEET, ATTR_CJK_FONT);
+ SvxFontItem* pCtlFont = getDefaultFontItem(nCtlLang, DefaultFontType::CTL_SPREADSHEET, ATTR_CTL_FONT);
+
+ SvxBoxInfoItem* pGlobalBorderInnerAttr = new SvxBoxInfoItem( ATTR_BORDER_INNER );
+ SfxItemSet aSetItemItemSet( *this,
+ svl::Items<ATTR_BACKGROUND, ATTR_BACKGROUND,
+ ATTR_BORDER, ATTR_SHADOW,
+ ATTR_LRSPACE, ATTR_ULSPACE,
+ ATTR_PAGE_SIZE, ATTR_PAGE_SIZE,
+ ATTR_PAGE_ON, ATTR_PAGE_SHARED_FIRST> );
+
+ pGlobalBorderInnerAttr->SetLine(nullptr, SvxBoxInfoItemLine::HORI);
+ pGlobalBorderInnerAttr->SetLine(nullptr, SvxBoxInfoItemLine::VERT);
+ pGlobalBorderInnerAttr->SetTable(true);
+ pGlobalBorderInnerAttr->SetDist(true);
+ pGlobalBorderInnerAttr->SetMinDist(false);
+
+ mvPoolDefaults[ ATTR_FONT - ATTR_STARTINDEX ] = pStdFont;
+ mvPoolDefaults[ ATTR_FONT_HEIGHT - ATTR_STARTINDEX ] = new SvxFontHeightItem( 200, 100, ATTR_FONT_HEIGHT ); // 10 pt;
+ mvPoolDefaults[ ATTR_FONT_WEIGHT - ATTR_STARTINDEX ] = new SvxWeightItem( WEIGHT_NORMAL, ATTR_FONT_WEIGHT );
+ mvPoolDefaults[ ATTR_FONT_POSTURE - ATTR_STARTINDEX ] = new SvxPostureItem( ITALIC_NONE, ATTR_FONT_POSTURE );
+ mvPoolDefaults[ ATTR_FONT_UNDERLINE - ATTR_STARTINDEX ] = new SvxUnderlineItem( LINESTYLE_NONE, ATTR_FONT_UNDERLINE );
+ mvPoolDefaults[ ATTR_FONT_OVERLINE - ATTR_STARTINDEX ] = new SvxOverlineItem( LINESTYLE_NONE, ATTR_FONT_OVERLINE );
+ mvPoolDefaults[ ATTR_FONT_CROSSEDOUT - ATTR_STARTINDEX ] = new SvxCrossedOutItem( STRIKEOUT_NONE, ATTR_FONT_CROSSEDOUT );
+ mvPoolDefaults[ ATTR_FONT_CONTOUR - ATTR_STARTINDEX ] = new SvxContourItem( false, ATTR_FONT_CONTOUR );
+ mvPoolDefaults[ ATTR_FONT_SHADOWED - ATTR_STARTINDEX ] = new SvxShadowedItem( false, ATTR_FONT_SHADOWED );
+ mvPoolDefaults[ ATTR_FONT_COLOR - ATTR_STARTINDEX ] = new SvxColorItem( COL_AUTO, ATTR_FONT_COLOR );
+ mvPoolDefaults[ ATTR_FONT_LANGUAGE - ATTR_STARTINDEX ] = new SvxLanguageItem( LANGUAGE_DONTKNOW, ATTR_FONT_LANGUAGE );
+ mvPoolDefaults[ ATTR_CJK_FONT - ATTR_STARTINDEX ] = pCjkFont;
+ mvPoolDefaults[ ATTR_CJK_FONT_HEIGHT - ATTR_STARTINDEX ] = new SvxFontHeightItem( 200, 100, ATTR_CJK_FONT_HEIGHT );
+ mvPoolDefaults[ ATTR_CJK_FONT_WEIGHT - ATTR_STARTINDEX ] = new SvxWeightItem( WEIGHT_NORMAL, ATTR_CJK_FONT_WEIGHT );
+ mvPoolDefaults[ ATTR_CJK_FONT_POSTURE- ATTR_STARTINDEX ] = new SvxPostureItem( ITALIC_NONE, ATTR_CJK_FONT_POSTURE );
+ mvPoolDefaults[ ATTR_CJK_FONT_LANGUAGE-ATTR_STARTINDEX ] = new SvxLanguageItem( LANGUAGE_DONTKNOW, ATTR_CJK_FONT_LANGUAGE );
+ mvPoolDefaults[ ATTR_CTL_FONT - ATTR_STARTINDEX ] = pCtlFont;
+ mvPoolDefaults[ ATTR_CTL_FONT_HEIGHT - ATTR_STARTINDEX ] = new SvxFontHeightItem( 200, 100, ATTR_CTL_FONT_HEIGHT );
+ mvPoolDefaults[ ATTR_CTL_FONT_WEIGHT - ATTR_STARTINDEX ] = new SvxWeightItem( WEIGHT_NORMAL, ATTR_CTL_FONT_WEIGHT );
+ mvPoolDefaults[ ATTR_CTL_FONT_POSTURE- ATTR_STARTINDEX ] = new SvxPostureItem( ITALIC_NONE, ATTR_CTL_FONT_POSTURE );
+ mvPoolDefaults[ ATTR_CTL_FONT_LANGUAGE-ATTR_STARTINDEX ] = new SvxLanguageItem( LANGUAGE_DONTKNOW, ATTR_CTL_FONT_LANGUAGE );
+ mvPoolDefaults[ ATTR_FONT_EMPHASISMARK-ATTR_STARTINDEX ] = new SvxEmphasisMarkItem( FontEmphasisMark::NONE, ATTR_FONT_EMPHASISMARK );
+ mvPoolDefaults[ ATTR_USERDEF - ATTR_STARTINDEX ] = new SvXMLAttrContainerItem( ATTR_USERDEF );
+ mvPoolDefaults[ ATTR_FONT_WORDLINE - ATTR_STARTINDEX ] = new SvxWordLineModeItem(false, ATTR_FONT_WORDLINE );
+ mvPoolDefaults[ ATTR_FONT_RELIEF - ATTR_STARTINDEX ] = new SvxCharReliefItem( FontRelief::NONE, ATTR_FONT_RELIEF );
+ mvPoolDefaults[ ATTR_HYPHENATE - ATTR_STARTINDEX ] = new ScHyphenateCell();
+ mvPoolDefaults[ ATTR_SCRIPTSPACE - ATTR_STARTINDEX ] = new SvxScriptSpaceItem( false, ATTR_SCRIPTSPACE);
+ mvPoolDefaults[ ATTR_HANGPUNCTUATION - ATTR_STARTINDEX ] = new SvxHangingPunctuationItem( false, ATTR_HANGPUNCTUATION);
+ mvPoolDefaults[ ATTR_FORBIDDEN_RULES - ATTR_STARTINDEX ] = new SvxForbiddenRuleItem( false, ATTR_FORBIDDEN_RULES);
+ mvPoolDefaults[ ATTR_HOR_JUSTIFY - ATTR_STARTINDEX ] = new SvxHorJustifyItem( SvxCellHorJustify::Standard, ATTR_HOR_JUSTIFY);
+ mvPoolDefaults[ ATTR_HOR_JUSTIFY_METHOD - ATTR_STARTINDEX ] = new SvxJustifyMethodItem( SvxCellJustifyMethod::Auto, ATTR_HOR_JUSTIFY_METHOD);
+ mvPoolDefaults[ ATTR_INDENT - ATTR_STARTINDEX ] = new ScIndentItem( 0 );
+ mvPoolDefaults[ ATTR_VER_JUSTIFY - ATTR_STARTINDEX ] = new SvxVerJustifyItem( SvxCellVerJustify::Standard, ATTR_VER_JUSTIFY);
+ mvPoolDefaults[ ATTR_VER_JUSTIFY_METHOD - ATTR_STARTINDEX ] = new SvxJustifyMethodItem( SvxCellJustifyMethod::Auto, ATTR_VER_JUSTIFY_METHOD);
+ mvPoolDefaults[ ATTR_STACKED - ATTR_STARTINDEX ] = new ScVerticalStackCell(false);
+ mvPoolDefaults[ ATTR_ROTATE_VALUE - ATTR_STARTINDEX ] = new ScRotateValueItem( 0_deg100 );
+ mvPoolDefaults[ ATTR_ROTATE_MODE - ATTR_STARTINDEX ] = new SvxRotateModeItem( SVX_ROTATE_MODE_BOTTOM, ATTR_ROTATE_MODE );
+ mvPoolDefaults[ ATTR_VERTICAL_ASIAN - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_VERTICAL_ASIAN );
+ // The default for the ATTR_WRITINGDIR cell attribute must by SvxFrameDirection::Environment,
+ // so that value is returned when asking for a default cell's attributes.
+ // The value from the page style is set as DefaultHorizontalTextDirection for the EditEngine.
+ mvPoolDefaults[ ATTR_WRITINGDIR - ATTR_STARTINDEX ] = new SvxFrameDirectionItem( SvxFrameDirection::Environment, ATTR_WRITINGDIR );
+ mvPoolDefaults[ ATTR_LINEBREAK - ATTR_STARTINDEX ] = new ScLineBreakCell();
+ mvPoolDefaults[ ATTR_SHRINKTOFIT - ATTR_STARTINDEX ] = new ScShrinkToFitCell();
+ mvPoolDefaults[ ATTR_BORDER_TLBR - ATTR_STARTINDEX ] = new SvxLineItem( ATTR_BORDER_TLBR );
+ mvPoolDefaults[ ATTR_BORDER_BLTR - ATTR_STARTINDEX ] = new SvxLineItem( ATTR_BORDER_BLTR );
+ mvPoolDefaults[ ATTR_MARGIN - ATTR_STARTINDEX ] = new SvxMarginItem( ATTR_MARGIN );
+ mvPoolDefaults[ ATTR_MERGE - ATTR_STARTINDEX ] = new ScMergeAttr;
+ mvPoolDefaults[ ATTR_MERGE_FLAG - ATTR_STARTINDEX ] = new ScMergeFlagAttr;
+ mvPoolDefaults[ ATTR_VALUE_FORMAT - ATTR_STARTINDEX ] = new SfxUInt32Item( ATTR_VALUE_FORMAT, 0 );
+ mvPoolDefaults[ ATTR_LANGUAGE_FORMAT - ATTR_STARTINDEX ] = new SvxLanguageItem( ScGlobal::eLnge, ATTR_LANGUAGE_FORMAT );
+ mvPoolDefaults[ ATTR_BACKGROUND - ATTR_STARTINDEX ] = new SvxBrushItem( COL_TRANSPARENT, ATTR_BACKGROUND );
+ mvPoolDefaults[ ATTR_PROTECTION - ATTR_STARTINDEX ] = new ScProtectionAttr;
+ mvPoolDefaults[ ATTR_BORDER - ATTR_STARTINDEX ] = new SvxBoxItem( ATTR_BORDER );
+ mvPoolDefaults[ ATTR_BORDER_INNER - ATTR_STARTINDEX ] = pGlobalBorderInnerAttr;
+ mvPoolDefaults[ ATTR_SHADOW - ATTR_STARTINDEX ] = new SvxShadowItem( ATTR_SHADOW );
+ mvPoolDefaults[ ATTR_VALIDDATA - ATTR_STARTINDEX ] = new SfxUInt32Item( ATTR_VALIDDATA, 0 );
+ mvPoolDefaults[ ATTR_CONDITIONAL - ATTR_STARTINDEX ] = new ScCondFormatItem;
+ mvPoolDefaults[ ATTR_HYPERLINK - ATTR_STARTINDEX ] = new SfxStringItem( ATTR_HYPERLINK, OUString() ) ;
+
+ // GetRscString only works after ScGlobal::Init (indicated by the EmptyBrushItem)
+ // TODO: Write additional method ScGlobal::IsInit() or somesuch
+ // or detect whether this is the Secondary Pool for a MessagePool
+ if ( ScGlobal::GetEmptyBrushItem() )
+
+ mvPoolDefaults[ ATTR_PATTERN - ATTR_STARTINDEX ] =
+ new ScPatternAttr( SfxItemSetFixed<ATTR_PATTERN_START, ATTR_PATTERN_END>( *this ),
+ ScResId(STR_STYLENAME_STANDARD) );
+ else
+ mvPoolDefaults[ ATTR_PATTERN - ATTR_STARTINDEX ] =
+ new ScPatternAttr( SfxItemSetFixed<ATTR_PATTERN_START, ATTR_PATTERN_END>( *this ),
+ STRING_STANDARD ); // FIXME: without name?
+
+ mvPoolDefaults[ ATTR_LRSPACE - ATTR_STARTINDEX ] = new SvxLRSpaceItem( ATTR_LRSPACE );
+ mvPoolDefaults[ ATTR_ULSPACE - ATTR_STARTINDEX ] = new SvxULSpaceItem( ATTR_ULSPACE );
+ mvPoolDefaults[ ATTR_PAGE - ATTR_STARTINDEX ] = new SvxPageItem( ATTR_PAGE );
+ mvPoolDefaults[ ATTR_PAGE_PAPERBIN - ATTR_STARTINDEX ] = new SvxPaperBinItem( ATTR_PAGE_PAPERBIN );
+ mvPoolDefaults[ ATTR_PAGE_SIZE - ATTR_STARTINDEX ] = new SvxSizeItem( ATTR_PAGE_SIZE );
+ mvPoolDefaults[ ATTR_PAGE_HORCENTER - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_HORCENTER );
+ mvPoolDefaults[ ATTR_PAGE_VERCENTER - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_VERCENTER );
+ mvPoolDefaults[ ATTR_PAGE_ON - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_ON, true );
+ mvPoolDefaults[ ATTR_PAGE_DYNAMIC - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_DYNAMIC, true );
+ mvPoolDefaults[ ATTR_PAGE_SHARED - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_SHARED, true );
+ mvPoolDefaults[ ATTR_PAGE_SHARED_FIRST- ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_SHARED_FIRST, true );
+ mvPoolDefaults[ ATTR_PAGE_NOTES - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_NOTES, false );
+ mvPoolDefaults[ ATTR_PAGE_GRID - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_GRID, false );
+ mvPoolDefaults[ ATTR_PAGE_HEADERS - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_HEADERS, false );
+ mvPoolDefaults[ ATTR_PAGE_CHARTS - ATTR_STARTINDEX ] = new ScViewObjectModeItem( ATTR_PAGE_CHARTS );
+ mvPoolDefaults[ ATTR_PAGE_OBJECTS - ATTR_STARTINDEX ] = new ScViewObjectModeItem( ATTR_PAGE_OBJECTS );
+ mvPoolDefaults[ ATTR_PAGE_DRAWINGS - ATTR_STARTINDEX ] = new ScViewObjectModeItem( ATTR_PAGE_DRAWINGS );
+ mvPoolDefaults[ ATTR_PAGE_TOPDOWN - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_TOPDOWN, true );
+ mvPoolDefaults[ ATTR_PAGE_SCALE - ATTR_STARTINDEX ] = new SfxUInt16Item( ATTR_PAGE_SCALE, 100 );
+ mvPoolDefaults[ ATTR_PAGE_SCALETOPAGES-ATTR_STARTINDEX ] = new SfxUInt16Item( ATTR_PAGE_SCALETOPAGES, 1 );
+ mvPoolDefaults[ ATTR_PAGE_FIRSTPAGENO- ATTR_STARTINDEX ] = new SfxUInt16Item( ATTR_PAGE_FIRSTPAGENO, 1 );
+ mvPoolDefaults[ ATTR_PAGE_HEADERLEFT - ATTR_STARTINDEX ] = new ScPageHFItem( ATTR_PAGE_HEADERLEFT );
+ mvPoolDefaults[ ATTR_PAGE_FOOTERLEFT - ATTR_STARTINDEX ] = new ScPageHFItem( ATTR_PAGE_FOOTERLEFT );
+ mvPoolDefaults[ ATTR_PAGE_HEADERRIGHT- ATTR_STARTINDEX ] = new ScPageHFItem( ATTR_PAGE_HEADERRIGHT );
+ mvPoolDefaults[ ATTR_PAGE_FOOTERRIGHT- ATTR_STARTINDEX ] = new ScPageHFItem( ATTR_PAGE_FOOTERRIGHT );
+ mvPoolDefaults[ ATTR_PAGE_HEADERFIRST- ATTR_STARTINDEX ] = new ScPageHFItem( ATTR_PAGE_HEADERFIRST );
+ mvPoolDefaults[ ATTR_PAGE_FOOTERFIRST- ATTR_STARTINDEX ] = new ScPageHFItem( ATTR_PAGE_FOOTERFIRST );
+ mvPoolDefaults[ ATTR_PAGE_HEADERSET - ATTR_STARTINDEX ] = new SvxSetItem( ATTR_PAGE_HEADERSET, aSetItemItemSet );
+ mvPoolDefaults[ ATTR_PAGE_FOOTERSET - ATTR_STARTINDEX ] = new SvxSetItem( ATTR_PAGE_FOOTERSET, aSetItemItemSet );
+ mvPoolDefaults[ ATTR_PAGE_FORMULAS - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_FORMULAS, false );
+ mvPoolDefaults[ ATTR_PAGE_NULLVALS - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_PAGE_NULLVALS, true );
+ mvPoolDefaults[ ATTR_PAGE_SCALETO - ATTR_STARTINDEX ] = new ScPageScaleToItem( 1, 1 );
+ mvPoolDefaults[ ATTR_HIDDEN - ATTR_STARTINDEX ] = new SfxBoolItem( ATTR_HIDDEN, false );
+
+ SetDefaults( &mvPoolDefaults );
+}
+
+ScDocumentPool::~ScDocumentPool()
+{
+ Delete();
+
+ for ( sal_uInt16 i=0; i < ATTR_ENDINDEX-ATTR_STARTINDEX+1; i++ )
+ {
+ ClearRefCount( *mvPoolDefaults[i] );
+ delete mvPoolDefaults[i];
+ }
+}
+
+const SfxPoolItem& ScDocumentPool::PutImpl( const SfxPoolItem& rItem, sal_uInt16 nWhich, bool bPassingOwnership )
+{
+ if ( rItem.Which() != ATTR_PATTERN ) // Only Pattern is special
+ return SfxItemPool::PutImpl( rItem, nWhich, bPassingOwnership );
+
+ // Don't copy the default pattern of this Pool
+ if (&rItem == mvPoolDefaults[ ATTR_PATTERN - ATTR_STARTINDEX ])
+ return rItem;
+
+ // Else Put must always happen, because it could be another Pool
+ const SfxPoolItem& rNew = SfxItemPool::PutImpl( rItem, nWhich, bPassingOwnership );
+ sal_uInt32 nRef = rNew.GetRefCount();
+ if (nRef == 1)
+ {
+ ++mnCurrentMaxKey;
+ const_cast<ScPatternAttr&>(static_cast<const ScPatternAttr&>(rNew)).SetKey(mnCurrentMaxKey);
+ }
+ return rNew;
+}
+
+void ScDocumentPool::StyleDeleted( const ScStyleSheet* pStyle )
+{
+ for (const SfxPoolItem* pItem : GetItemSurrogates( ATTR_PATTERN ))
+ {
+ ScPatternAttr* pPattern = const_cast<ScPatternAttr*>(dynamic_cast<const ScPatternAttr*>(pItem));
+ if ( pPattern && pPattern->GetStyleSheet() == pStyle )
+ pPattern->StyleToName();
+ }
+}
+
+void ScDocumentPool::CellStyleCreated( std::u16string_view rName, const ScDocument& rDoc )
+{
+ // If a style was created, don't keep any pattern with its name string in the pool,
+ // because it would compare equal to a pattern with a pointer to the new style.
+ // Calling StyleSheetChanged isn't enough because the pool may still contain items
+ // for undo or clipboard content.
+
+ for (const SfxPoolItem* pItem : GetItemSurrogates( ATTR_PATTERN ))
+ {
+ auto pPattern = const_cast<ScPatternAttr*>(dynamic_cast<const ScPatternAttr*>(pItem));
+ if ( pPattern && pPattern->GetStyleSheet() == nullptr )
+ {
+ const OUString* pStyleName = pPattern->GetStyleName();
+ if ( pStyleName && *pStyleName == rName )
+ pPattern->UpdateStyleSheet(rDoc); // find and store style pointer
+ }
+ }
+}
+
+rtl::Reference<SfxItemPool> ScDocumentPool::Clone() const
+{
+ return new SfxItemPool (*this, true);
+}
+
+static bool lcl_HFPresentation
+(
+ const SfxPoolItem& rItem,
+ MapUnit eCoreMetric,
+ MapUnit ePresentationMetric,
+ OUString& rText,
+ const IntlWrapper& rIntl
+)
+{
+ const SfxItemSet& rSet = static_cast<const SfxSetItem&>(rItem).GetItemSet();
+
+ if ( const SfxBoolItem* pItem = rSet.GetItemIfSet(ATTR_PAGE_ON,false) )
+ {
+ if( !pItem->GetValue() )
+ return false;
+ }
+
+ SfxItemIter aIter( rSet );
+
+ for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem())
+ {
+ sal_uInt16 nWhich = pItem->Which();
+
+ OUString aText;
+
+ switch( nWhich )
+ {
+ case ATTR_PAGE_ON:
+ case ATTR_PAGE_DYNAMIC:
+ case ATTR_PAGE_SHARED:
+ case ATTR_PAGE_SHARED_FIRST:
+ break;
+
+ case ATTR_LRSPACE:
+ {
+ const SvxLRSpaceItem& rLRItem = static_cast<const SvxLRSpaceItem&>(*pItem);
+ sal_uInt16 nPropLeftMargin = rLRItem.GetPropLeft();
+ sal_uInt16 nPropRightMargin = rLRItem.GetPropRight();
+ sal_uInt16 nLeftMargin, nRightMargin;
+ tools::Long nTmp;
+ nTmp = rLRItem.GetLeft();
+ nLeftMargin = nTmp < 0 ? 0 : sal_uInt16(nTmp);
+ nTmp = rLRItem.GetRight();
+ nRightMargin = nTmp < 0 ? 0 : sal_uInt16(nTmp);
+
+ aText = EditResId(RID_SVXITEMS_LRSPACE_LEFT);
+ if ( 100 != nPropLeftMargin )
+ {
+ aText += unicode::formatPercent(nPropLeftMargin,
+ Application::GetSettings().GetUILanguageTag());
+ }
+ else
+ {
+ aText += GetMetricText( static_cast<tools::Long>(nLeftMargin),
+ eCoreMetric, ePresentationMetric, &rIntl ) +
+ " " + EditResId(GetMetricId(ePresentationMetric));
+ }
+ aText += cpDelim +
+ // We don't have a nPropFirstLineOffset
+ EditResId(RID_SVXITEMS_LRSPACE_RIGHT);
+ if ( 100 != nPropRightMargin )
+ {
+ aText += unicode::formatPercent(nPropLeftMargin,
+ Application::GetSettings().GetUILanguageTag());
+ }
+ else
+ {
+ aText += GetMetricText( static_cast<tools::Long>(nRightMargin),
+ eCoreMetric, ePresentationMetric, &rIntl ) +
+ " " + EditResId(GetMetricId(ePresentationMetric));
+ }
+ }
+ break;
+
+ default:
+ pItem->GetPresentation( SfxItemPresentation::Complete, eCoreMetric, ePresentationMetric, aText, rIntl );
+
+ }
+
+ if ( aText.getLength() )
+ {
+ rText += aText + " + ";
+ }
+ }
+
+ rText = comphelper::string::stripEnd(rText, ' ');
+ rText = comphelper::string::stripEnd(rText, '+');
+ rText = comphelper::string::stripEnd(rText, ' ');
+ return true;
+}
+
+bool ScDocumentPool::GetPresentation(
+ const SfxPoolItem& rItem,
+ MapUnit ePresentationMetric,
+ OUString& rText,
+ const IntlWrapper& rIntl ) const
+{
+ sal_uInt16 nW = rItem.Which();
+ OUString aStrYes ( ScResId(STR_YES) );
+ OUString aStrNo ( ScResId(STR_NO) );
+ OUString aStrSep(": ");
+
+ bool ePresentationRet = true;
+ switch( nW )
+ {
+ case ATTR_PAGE_TOPDOWN:
+ rText = ScResId(STR_SCATTR_PAGE_PRINTDIR) + aStrSep;
+ rText += static_cast<const SfxBoolItem&>(rItem).GetValue() ?
+ ScResId(STR_SCATTR_PAGE_TOPDOWN) :
+ ScResId(STR_SCATTR_PAGE_LEFTRIGHT) ;
+ break;
+
+ case ATTR_PAGE_HEADERS:
+ rText = ScResId(STR_SCATTR_PAGE_HEADERS) + aStrSep;
+ rText += static_cast<const SfxBoolItem&>(rItem).GetValue() ? aStrYes : aStrNo ;
+ break;
+
+ case ATTR_PAGE_NULLVALS:
+ rText = ScResId(STR_SCATTR_PAGE_NULLVALS) + aStrSep;
+ rText += static_cast<const SfxBoolItem&>(rItem).GetValue() ? aStrYes : aStrNo ;
+ break;
+
+ case ATTR_PAGE_FORMULAS:
+ rText = ScResId(STR_SCATTR_PAGE_FORMULAS) + aStrSep;
+ rText += static_cast<const SfxBoolItem&>(rItem).GetValue() ? aStrYes : aStrNo ;
+ break;
+
+ case ATTR_PAGE_NOTES:
+ rText = ScResId(STR_SCATTR_PAGE_NOTES) + aStrSep;
+ rText += static_cast<const SfxBoolItem&>(rItem).GetValue() ? aStrYes : aStrNo ;
+ break;
+
+ case ATTR_PAGE_GRID:
+ rText = ScResId(STR_SCATTR_PAGE_GRID) + aStrSep;
+ rText += static_cast<const SfxBoolItem&>(rItem).GetValue() ? aStrYes : aStrNo ;
+ break;
+
+ case ATTR_PAGE_SCALETOPAGES:
+ {
+ sal_uInt16 nPagNo = static_cast<const SfxUInt16Item&>(rItem).GetValue();
+
+ if( nPagNo )
+ {
+ rText = ScResId( STR_SCATTR_PAGE_SCALETOPAGES ) + aStrSep;
+ OUString aPages(ScResId(STR_SCATTR_PAGE_SCALE_PAGES, nPagNo));
+ aPages = aPages.replaceFirst( "%1", OUString::number( nPagNo ) );
+ rText += aPages;
+ }
+ else
+ {
+ ePresentationRet = false;
+ }
+ }
+ break;
+
+ case ATTR_PAGE_FIRSTPAGENO:
+ {
+ sal_uInt16 nPagNo = static_cast<const SfxUInt16Item&>(rItem).GetValue();
+
+ if( nPagNo )
+ {
+ rText = ScResId(STR_SCATTR_PAGE_FIRSTPAGENO) + aStrSep;
+ rText += OUString::number( nPagNo );
+ }
+ else
+ {
+ ePresentationRet = false;
+ }
+ }
+ break;
+
+ case ATTR_PAGE_SCALE:
+ {
+ sal_uInt16 nPercent = static_cast<const SfxUInt16Item &>(rItem).GetValue();
+
+ if( nPercent )
+ {
+ rText = ScResId(STR_SCATTR_PAGE_SCALE) + aStrSep;
+ rText += unicode::formatPercent(nPercent,
+ Application::GetSettings().GetUILanguageTag());
+ }
+ else
+ {
+ ePresentationRet = false;
+ }
+ }
+ break;
+
+ case ATTR_PAGE_HEADERSET:
+ {
+ OUString aBuffer;
+
+ if( lcl_HFPresentation( rItem, GetMetric( nW ), ePresentationMetric, aBuffer, rIntl ) )
+ {
+ rText = ScResId(STR_HEADER) + " ( " + aBuffer + " ) ";
+ }
+ }
+ break;
+
+ case ATTR_PAGE_FOOTERSET:
+ {
+ OUString aBuffer;
+
+ if( lcl_HFPresentation( rItem, GetMetric( nW ), ePresentationMetric, aBuffer, rIntl ) )
+ {
+ rText = ScResId(STR_FOOTER) + " ( " + aBuffer + " ) ";
+ }
+ }
+ break;
+
+ default:
+ ePresentationRet = rItem.GetPresentation( SfxItemPresentation::Complete, GetMetric( nW ), ePresentationMetric, rText, rIntl );
+ break;
+ }
+
+ return ePresentationRet;
+}
+
+MapUnit ScDocumentPool::GetMetric( sal_uInt16 nWhich ) const
+{
+ // Own attributes in Twips, everything else in 1/100 mm
+ if ( nWhich >= ATTR_STARTINDEX && nWhich <= ATTR_ENDINDEX )
+ return MapUnit::MapTwip;
+ else
+ return MapUnit::Map100thMM;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/documen2.cxx b/sc/source/core/data/documen2.cxx
new file mode 100644
index 000000000..c29025f43
--- /dev/null
+++ b/sc/source/core/data/documen2.cxx
@@ -0,0 +1,1471 @@
+/* -*- 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 <scextopt.hxx>
+#include <autonamecache.hxx>
+
+#include <o3tl/safeint.hxx>
+#include <osl/thread.h>
+#include <svx/xtable.hxx>
+#include <sfx2/bindings.hxx>
+#include <sfx2/objsh.hxx>
+#include <sfx2/docfile.hxx>
+#include <sfx2/printer.hxx>
+#include <svl/asiancfg.hxx>
+#include <vcl/virdev.hxx>
+#include <svl/sharedstringpool.hxx>
+#include <tools/urlobj.hxx>
+#include <rtl/crc.h>
+#include <basic/basmgr.hxx>
+#include <comphelper/threadpool.hxx>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+#include <unotools/configmgr.hxx>
+
+#include <scmod.hxx>
+#include <document.hxx>
+#include <table.hxx>
+#include <patattr.hxx>
+#include <rangenam.hxx>
+#include <dbdata.hxx>
+#include <chartlock.hxx>
+#include <rechead.hxx>
+#include <global.hxx>
+#include <bcaslot.hxx>
+#include <adiasync.hxx>
+#include <addinlis.hxx>
+#include <chartlis.hxx>
+#include <markdata.hxx>
+#include <validat.hxx>
+#include <detdata.hxx>
+#include <defaultsoptions.hxx>
+#include <ddelink.hxx>
+#include <chgtrack.hxx>
+#include <chgviset.hxx>
+#include <editutil.hxx>
+#include <hints.hxx>
+#include <dpobject.hxx>
+#include <scrdata.hxx>
+#include <poolhelp.hxx>
+#include <unoreflist.hxx>
+#include <listenercalls.hxx>
+#include <recursionhelper.hxx>
+#include <lookupcache.hxx>
+#include <rangecache.hxx>
+#include <externalrefmgr.hxx>
+#include <viewdata.hxx>
+#include <viewutil.hxx>
+#include <tabprotection.hxx>
+#include <formulaparserpool.hxx>
+#include <clipparam.hxx>
+#include <macromgr.hxx>
+#include <formulacell.hxx>
+#include <clipcontext.hxx>
+#include <refupdatecontext.hxx>
+#include <refreshtimerprotector.hxx>
+#include <scopetools.hxx>
+#include <documentlinkmgr.hxx>
+#include <interpre.hxx>
+#include <tokenstringcontext.hxx>
+#include <docsh.hxx>
+#include <clipoptions.hxx>
+#include <listenercontext.hxx>
+#include <datamapper.hxx>
+#include <drwlayer.hxx>
+#include <sharedstringpoolpurge.hxx>
+#include <dociter.hxx>
+#include <config_features.h>
+
+using namespace com::sun::star;
+
+const sal_uInt16 ScDocument::nSrcVer = SC_CURRENT_VERSION;
+
+ScSheetLimits ScSheetLimits::CreateDefault()
+{
+#if HAVE_FEATURE_JUMBO_SHEETS
+ bool jumboSheets = false;
+ if( SC_MOD())
+ jumboSheets = SC_MOD()->GetDefaultsOptions().GetInitJumboSheets();
+ else
+ assert( getenv("LO_TESTNAME") != nullptr ); // in unittests
+ if (jumboSheets)
+ return ScSheetLimits(MAXCOL_JUMBO, MAXROW_JUMBO);
+ else
+#endif
+ return ScSheetLimits(MAXCOL, MAXROW);
+}
+
+ScDocument::ScDocument( ScDocumentMode eMode, SfxObjectShell* pDocShell ) :
+ mpCellStringPool(std::make_shared<svl::SharedStringPool>(ScGlobal::getCharClass())),
+ mpDocLinkMgr(new sc::DocumentLinkManager(pDocShell)),
+ mbFormulaGroupCxtBlockDiscard(false),
+ maCalcConfig( ScInterpreter::GetGlobalConfig()),
+ mpUndoManager( nullptr ),
+ mpShell( pDocShell ),
+ mpPrinter( nullptr ),
+ mpVirtualDevice_100th_mm( nullptr ),
+ pFormatExchangeList( nullptr ),
+ mxSheetLimits(new ScSheetLimits(ScSheetLimits::CreateDefault())),
+ pFormulaTree( nullptr ),
+ pEOFormulaTree( nullptr ),
+ pFormulaTrack( nullptr ),
+ pEOFormulaTrack( nullptr ),
+ pPreviewCellStyle( nullptr ),
+ maPreviewSelection(*mxSheetLimits),
+ nUnoObjectId( 0 ),
+ nRangeOverflowType( 0 ),
+ aCurTextWidthCalcPos(MaxCol(),0,0),
+ aTrackIdle("sc ScDocument Track Idle"),
+ nFormulaCodeInTree(0),
+ nXMLImportedFormulaCount( 0 ),
+ nInterpretLevel(0),
+ nMacroInterpretLevel(0),
+ nInterpreterTableOpLevel(0),
+ maInterpreterContext( *this, nullptr ),
+ mxScSortedRangeCache(new ScSortedRangeCacheMap),
+ nFormulaTrackCount(0),
+ eHardRecalcState(HardRecalcState::OFF),
+ nVisibleTab( 0 ),
+ nPosLeft( 0 ),
+ nPosTop( 0 ),
+ eLinkMode(LM_UNKNOWN),
+ bAutoCalc( eMode == SCDOCMODE_DOCUMENT || eMode == SCDOCMODE_FUNCTIONACCESS ),
+ bAutoCalcShellDisabled( false ),
+ bForcedFormulaPending( false ),
+ bCalculatingFormulaTree( false ),
+ bIsClip( eMode == SCDOCMODE_CLIP ),
+ bIsUndo( eMode == SCDOCMODE_UNDO ),
+ bIsFunctionAccess( eMode == SCDOCMODE_FUNCTIONACCESS ),
+ bIsVisible( false ),
+ bIsEmbedded( false ),
+ bInsertingFromOtherDoc( false ),
+ bLoadingMedium( false ),
+ bImportingXML( false ),
+ bCalcingAfterLoad( false ),
+ bNoListening( false ),
+ mbIdleEnabled(true),
+ bInLinkUpdate( false ),
+ bChartListenerCollectionNeedsUpdate( false ),
+ bHasForcedFormulas( false ),
+ bInDtorClear( false ),
+ bExpandRefs( false ),
+ bDetectiveDirty( false ),
+ bDelayedDeletingBroadcasters( false ),
+ bLinkFormulaNeedingCheck( false ),
+ nAsianCompression(CharCompressType::Invalid),
+ nAsianKerning(SC_ASIANKERNING_INVALID),
+ bPastingDrawFromOtherDoc( false ),
+ nInDdeLinkUpdate( 0 ),
+ bInUnoBroadcast( false ),
+ bInUnoListenerCall( false ),
+ nAdjustHeightLock(0),
+ eGrammar( formula::FormulaGrammar::GRAM_NATIVE ),
+ bStyleSheetUsageInvalid( true ),
+ mbUndoEnabled( true ),
+ mbExecuteLinkEnabled( true ),
+ mbChangeReadOnlyEnabled( false ),
+ mbStreamValidLocked( false ),
+ mbUserInteractionEnabled(true),
+ mnNamedRangesLockCount(0),
+ mbEmbedFonts(false),
+ mbEmbedUsedFontsOnly(false),
+ mbEmbedFontScriptLatin(true),
+ mbEmbedFontScriptAsian(true),
+ mbEmbedFontScriptComplex(true),
+ mnImagePreferredDPI(0),
+ mbTrackFormulasPending(false),
+ mbFinalTrackFormulas(false),
+ mbDocShellRecalc(false),
+ mbLayoutStrings(false),
+ mnMutationGuardFlags(0)
+{
+ maPreviewSelection = { *mxSheetLimits };
+ aCurTextWidthCalcPos = { MaxCol(), 0, 0 };
+
+ SetStorageGrammar( formula::FormulaGrammar::GRAM_STORAGE_DEFAULT);
+
+ eSrcSet = osl_getThreadTextEncoding();
+
+ /* TODO: for SCDOCMODE_FUNCTIONACCESS it might not even be necessary to
+ * have all of these available. */
+ if ( eMode == SCDOCMODE_DOCUMENT || eMode == SCDOCMODE_FUNCTIONACCESS )
+ {
+ mxPoolHelper = new ScPoolHelper( *this );
+ if (!utl::ConfigManager::IsFuzzing()) //just too slow
+ pBASM.reset( new ScBroadcastAreaSlotMachine( this ) );
+ pChartListenerCollection.reset( new ScChartListenerCollection( *this ) );
+ pRefreshTimerControl.reset( new ScRefreshTimerControl );
+ }
+ else
+ {
+ pChartListenerCollection = nullptr;
+ }
+ pDBCollection.reset( new ScDBCollection(*this) );
+ pSelectionAttr = nullptr;
+ apTemporaryChartLock.reset( new ScTemporaryChartLock(this) );
+ xColNameRanges = new ScRangePairList;
+ xRowNameRanges = new ScRangePairList;
+ ImplCreateOptions();
+ // languages for a visible document are set by docshell later (from options)
+ SetLanguage( ScGlobal::eLnge, ScGlobal::eLnge, ScGlobal::eLnge );
+
+ aTrackIdle.SetInvokeHandler( LINK( this, ScDocument, TrackTimeHdl ) );
+}
+
+sfx2::LinkManager* ScDocument::GetLinkManager()
+{
+ return GetDocLinkManager().getLinkManager();
+}
+
+const sfx2::LinkManager* ScDocument::GetLinkManager() const
+{
+ return GetDocLinkManager().getExistingLinkManager();
+}
+
+sc::DocumentLinkManager& ScDocument::GetDocLinkManager()
+{
+ return *mpDocLinkMgr;
+}
+
+const sc::DocumentLinkManager& ScDocument::GetDocLinkManager() const
+{
+ return const_cast<ScDocument*>(this)->GetDocLinkManager();
+}
+
+void ScDocument::SetStorageGrammar( formula::FormulaGrammar::Grammar eGram )
+{
+ OSL_PRECOND(
+ eGram == formula::FormulaGrammar::GRAM_ODFF ||
+ eGram == formula::FormulaGrammar::GRAM_PODF,
+ "ScDocument::SetStorageGrammar: wrong storage grammar");
+
+ eStorageGrammar = eGram;
+}
+
+void ScDocument::SetDocVisible( bool bSet )
+{
+ // called from view ctor - only for a visible document,
+ // each new sheet's RTL flag is initialized from the locale
+ bIsVisible = bSet;
+}
+
+sal_uInt32 ScDocument::GetDocumentID() const
+{
+ const ScDocument* pThis = this;
+ sal_uInt32 nCrc = rtl_crc32( 0, &pThis, sizeof(ScDocument*) );
+ // the this pointer only might not be sufficient
+ nCrc = rtl_crc32( nCrc, &mpShell, sizeof(SfxObjectShell*) );
+ return nCrc;
+}
+
+void ScDocument::StartChangeTracking()
+{
+ if (!pChangeTrack)
+ pChangeTrack.reset( new ScChangeTrack( *this ) );
+}
+
+void ScDocument::EndChangeTracking()
+{
+ pChangeTrack.reset();
+}
+
+void ScDocument::SetChangeTrack( std::unique_ptr<ScChangeTrack> pTrack )
+{
+ OSL_ENSURE( &pTrack->GetDocument() == this, "SetChangeTrack: different documents" );
+ if ( !pTrack || pTrack == pChangeTrack || &pTrack->GetDocument() != this )
+ return ;
+ EndChangeTracking();
+ pChangeTrack = std::move(pTrack);
+}
+
+IMPL_LINK_NOARG(ScDocument, TrackTimeHdl, Timer *, void)
+{
+ if ( ScDdeLink::IsInUpdate() ) // do not nest
+ {
+ aTrackIdle.Start(); // try again later
+ }
+ else if (mpShell) // execute
+ {
+ TrackFormulas();
+ mpShell->Broadcast( SfxHint( SfxHintId::ScDataChanged ) );
+
+ if (!mpShell->IsModified())
+ {
+ mpShell->SetModified();
+ SfxBindings* pBindings = GetViewBindings();
+ if (pBindings)
+ {
+ pBindings->Invalidate( SID_SAVEDOC );
+ pBindings->Invalidate( SID_DOC_MODIFIED );
+ }
+ }
+ }
+}
+
+void ScDocument::SetExpandRefs( bool bVal )
+{
+ bExpandRefs = bVal;
+}
+
+void ScDocument::StartTrackTimer()
+{
+ if (!aTrackIdle.IsActive()) // do not postpone for forever
+ aTrackIdle.Start();
+}
+
+void ScDocument::ClosingClipboardSource()
+{
+ if (!bIsClip)
+ return;
+
+ ForgetNoteCaptions( ScRangeList( ScRange( 0,0,0, MaxCol(), MaxRow(), GetTableCount()-1)), true);
+}
+
+ScDocument::~ScDocument()
+{
+ OSL_PRECOND( !bInLinkUpdate, "bInLinkUpdate in dtor" );
+
+ // Join any pending(recalc) threads in global threadpool
+ comphelper::ThreadPool::getSharedOptimalPool().joinThreadsIfIdle();
+
+ bInDtorClear = true;
+
+ // first of all disable all refresh timers by deleting the control
+ if ( pRefreshTimerControl )
+ { // To be sure there isn't anything running do it with a protector,
+ // this ensures also that nothing needs the control anymore.
+ ScRefreshTimerProtector aProt( GetRefreshTimerControlAddress() );
+ pRefreshTimerControl.reset();
+ }
+
+ mxFormulaParserPool.reset();
+ // Destroy the external ref mgr instance here because it has a timer
+ // which needs to be stopped before the app closes.
+ pExternalRefMgr.reset();
+
+ ScAddInAsync::RemoveDocument( this );
+ ScAddInListener::RemoveDocument( this );
+ pChartListenerCollection.reset(); // before pBASM because of potential Listener!
+
+ ClearLookupCaches(); // before pBASM because of listeners
+
+ // destroy BroadcastAreas first to avoid un-needed Single-EndListenings of Formula-Cells
+ pBASM.reset(); // BroadcastAreaSlotMachine
+
+ pUnoBroadcaster.reset(); // broadcasts SfxHintId::Dying again
+
+ pUnoRefUndoList.reset();
+ pUnoListenerCalls.reset();
+
+ Clear( true ); // true = from destructor (needed for SdrModel::ClearModel)
+
+ pValidationList.reset();
+ pRangeName.reset();
+ pDBCollection.reset();
+ pSelectionAttr.reset();
+ apTemporaryChartLock.reset();
+ DeleteDrawLayer();
+ mpPrinter.disposeAndClear();
+ ImplDeleteOptions();
+ pConsolidateDlgData.reset();
+ pClipData.reset();
+ pDetOpList.reset(); // also deletes entries
+ pChangeTrack.reset();
+ mpEditEngine.reset();
+ mpNoteEngine.reset();
+ pChangeViewSettings.reset(); // and delete
+ mpVirtualDevice_100th_mm.disposeAndClear();
+
+ pDPCollection.reset();
+ mpAnonymousDBData.reset();
+
+ // delete the EditEngine before destroying the mxPoolHelper
+ pCacheFieldEditEngine.reset();
+
+ if ( mxPoolHelper.is() && !bIsClip && !bIsUndo)
+ mxPoolHelper->SourceDocumentGone();
+ mxPoolHelper.clear();
+
+ pScriptTypeData.reset();
+ maNonThreaded.xRecursionHelper.reset();
+ assert(!maThreadSpecific.xRecursionHelper);
+
+ pPreviewFont.reset();
+ SAL_WARN_IF( pAutoNameCache, "sc.core", "AutoNameCache still set in dtor" );
+
+ mpFormulaGroupCxt.reset();
+ // Purge unused items if the string pool will be still used (e.g. by undo history).
+ if(mpCellStringPool.use_count() > 1)
+ {
+ // Calling purge() may be somewhat expensive with large documents, so
+ // try to delay and compress it for temporary documents.
+ if(IsClipOrUndo())
+ ScGlobal::GetSharedStringPoolPurge().delayedPurge(mpCellStringPool);
+ else
+ mpCellStringPool->purge();
+ }
+ mpCellStringPool.reset();
+
+ assert( pDelayedFormulaGrouping == nullptr );
+ assert( pDelayedStartListeningFormulaCells.empty());
+}
+
+void ScDocument::InitClipPtrs( ScDocument* pSourceDoc )
+{
+ OSL_ENSURE(bIsClip, "InitClipPtrs and not bIsClip");
+
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+
+ pValidationList.reset();
+
+ Clear();
+
+ SharePooledResources(pSourceDoc);
+
+ // conditional Formats / validations
+ // TODO: Copy Templates?
+ const ScValidationDataList* pSourceValid = pSourceDoc->pValidationList.get();
+ if ( pSourceValid )
+ pValidationList.reset(new ScValidationDataList(*this, *pSourceValid));
+
+ // store Links in Stream
+ pClipData.reset();
+ if (pSourceDoc->GetDocLinkManager().hasDdeLinks())
+ {
+ pClipData.reset( new SvMemoryStream );
+ pSourceDoc->SaveDdeLinks(*pClipData);
+ }
+
+ // Options pointers exist (ImplCreateOptions) for any document.
+ // Must be copied for correct results in OLE objects (#i42666#).
+ SetDocOptions( pSourceDoc->GetDocOptions() );
+ SetViewOptions( pSourceDoc->GetViewOptions() );
+}
+
+SvNumberFormatter* ScDocument::GetFormatTable() const
+{
+ assert(!IsThreadedGroupCalcInProgress());
+ return mxPoolHelper->GetFormTable();
+}
+
+SfxItemPool* ScDocument::GetEditPool() const
+{
+ return mxPoolHelper->GetEditPool();
+}
+
+SfxItemPool* ScDocument::GetEnginePool() const
+{
+ return mxPoolHelper->GetEnginePool();
+}
+
+ScFieldEditEngine& ScDocument::GetEditEngine()
+{
+ if ( !mpEditEngine )
+ {
+ mpEditEngine.reset( new ScFieldEditEngine(this, GetEnginePool(), GetEditPool()) );
+ mpEditEngine->SetUpdateLayout( false );
+ mpEditEngine->EnableUndo( false );
+ mpEditEngine->SetRefMapMode(MapMode(MapUnit::Map100thMM));
+ ApplyAsianEditSettings( *mpEditEngine );
+ }
+ return *mpEditEngine;
+}
+
+ScNoteEditEngine& ScDocument::GetNoteEngine()
+{
+ if ( !mpNoteEngine )
+ {
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ mpNoteEngine.reset( new ScNoteEditEngine( GetEnginePool(), GetEditPool() ) );
+ mpNoteEngine->SetUpdateLayout( false );
+ mpNoteEngine->EnableUndo( false );
+ mpNoteEngine->SetRefMapMode(MapMode(MapUnit::Map100thMM));
+ ApplyAsianEditSettings( *mpNoteEngine );
+ const SfxItemSet& rItemSet = GetDefPattern()->GetItemSet();
+ SfxItemSet aEEItemSet( mpNoteEngine->GetEmptyItemSet() );
+ ScPatternAttr::FillToEditItemSet( aEEItemSet, rItemSet );
+ mpNoteEngine->SetDefaults( std::move(aEEItemSet) ); // edit engine takes ownership
+ }
+ return *mpNoteEngine;
+}
+
+void ScDocument::ResetClip( ScDocument* pSourceDoc, const ScMarkData* pMarks )
+{
+ if (bIsClip)
+ {
+ InitClipPtrs(pSourceDoc);
+
+ for (SCTAB i = 0; i < static_cast<SCTAB>(pSourceDoc->maTabs.size()); i++)
+ if (pSourceDoc->maTabs[i])
+ if (!pMarks || pMarks->GetTableSelect(i))
+ {
+ OUString aString = pSourceDoc->maTabs[i]->GetName();
+ if ( i < static_cast<SCTAB>(maTabs.size()) )
+ {
+ maTabs[i].reset( new ScTable(*this, i, aString) );
+
+ }
+ else
+ {
+ if( i > static_cast<SCTAB>(maTabs.size()) )
+ {
+ maTabs.resize(i);
+ }
+ maTabs.emplace_back(new ScTable(*this, i, aString));
+ }
+ maTabs[i]->SetLayoutRTL( pSourceDoc->maTabs[i]->IsLayoutRTL() );
+ }
+ }
+ else
+ {
+ OSL_FAIL("ResetClip");
+ }
+}
+
+void ScDocument::ResetClip( ScDocument* pSourceDoc, SCTAB nTab )
+{
+ if (bIsClip)
+ {
+ InitClipPtrs(pSourceDoc);
+ if (nTab >= static_cast<SCTAB>(maTabs.size()))
+ {
+ maTabs.resize(nTab+1);
+ }
+ maTabs[nTab].reset( new ScTable(*this, nTab, "baeh") );
+ if (nTab < static_cast<SCTAB>(pSourceDoc->maTabs.size()) && pSourceDoc->maTabs[nTab])
+ maTabs[nTab]->SetLayoutRTL( pSourceDoc->maTabs[nTab]->IsLayoutRTL() );
+ }
+ else
+ {
+ OSL_FAIL("ResetClip");
+ }
+}
+
+void ScDocument::EnsureTable( SCTAB nTab )
+{
+ bool bExtras = !bIsUndo; // Column-Widths, Row-Heights, Flags
+ if (o3tl::make_unsigned(nTab) >= maTabs.size())
+ maTabs.resize(nTab+1);
+
+ if (!maTabs[nTab])
+ maTabs[nTab].reset( new ScTable(*this, nTab, "temp", bExtras, bExtras) );
+}
+
+ScRefCellValue ScDocument::GetRefCellValue( const ScAddress& rPos )
+{
+ if (!TableExists(rPos.Tab()))
+ return ScRefCellValue(); // empty
+
+ return maTabs[rPos.Tab()]->GetRefCellValue(rPos.Col(), rPos.Row());
+}
+
+ScRefCellValue ScDocument::GetRefCellValue( const ScAddress& rPos, sc::ColumnBlockPosition& rBlockPos )
+{
+ if (!TableExists(rPos.Tab()))
+ return ScRefCellValue(); // empty
+
+ return maTabs[rPos.Tab()]->GetRefCellValue(rPos.Col(), rPos.Row(), rBlockPos);
+}
+
+svl::SharedStringPool& ScDocument::GetSharedStringPool()
+{
+ return *mpCellStringPool;
+}
+
+const svl::SharedStringPool& ScDocument::GetSharedStringPool() const
+{
+ return *mpCellStringPool;
+}
+
+bool ScDocument::GetPrintArea( SCTAB nTab, SCCOL& rEndCol, SCROW& rEndRow,
+ bool bNotes) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ {
+ bool bAny = maTabs[nTab]->GetPrintArea( rEndCol, rEndRow, bNotes, /*bCalcHiddens*/false);
+ if (mpDrawLayer)
+ {
+ ScRange aDrawRange(0,0,nTab, MaxCol(),MaxRow(),nTab);
+ if (DrawGetPrintArea( aDrawRange, true, true ))
+ {
+ if (aDrawRange.aEnd.Col()>rEndCol) rEndCol=aDrawRange.aEnd.Col();
+ if (aDrawRange.aEnd.Row()>rEndRow) rEndRow=aDrawRange.aEnd.Row();
+ bAny = true;
+ }
+ }
+ return bAny;
+ }
+
+ rEndCol = 0;
+ rEndRow = 0;
+ return false;
+}
+
+bool ScDocument::GetPrintAreaHor( SCTAB nTab, SCROW nStartRow, SCROW nEndRow,
+ SCCOL& rEndCol ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ {
+ bool bAny = maTabs[nTab]->GetPrintAreaHor( nStartRow, nEndRow, rEndCol );
+ if (mpDrawLayer)
+ {
+ ScRange aDrawRange(0,nStartRow,nTab, MaxCol(),nEndRow,nTab);
+ if (DrawGetPrintArea( aDrawRange, true, false ))
+ {
+ if (aDrawRange.aEnd.Col()>rEndCol) rEndCol=aDrawRange.aEnd.Col();
+ bAny = true;
+ }
+ }
+ return bAny;
+ }
+
+ rEndCol = 0;
+ return false;
+}
+
+bool ScDocument::GetPrintAreaVer( SCTAB nTab, SCCOL nStartCol, SCCOL nEndCol,
+ SCROW& rEndRow, bool bNotes ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ {
+ bool bAny = maTabs[nTab]->GetPrintAreaVer( nStartCol, nEndCol, rEndRow, bNotes );
+ if (mpDrawLayer)
+ {
+ ScRange aDrawRange(nStartCol,0,nTab, nEndCol,MaxRow(),nTab);
+ if (DrawGetPrintArea( aDrawRange, false, true ))
+ {
+ if (aDrawRange.aEnd.Row()>rEndRow) rEndRow=aDrawRange.aEnd.Row();
+ bAny = true;
+ }
+ }
+ return bAny;
+ }
+
+ rEndRow = 0;
+ return false;
+}
+
+bool ScDocument::GetDataStart( SCTAB nTab, SCCOL& rStartCol, SCROW& rStartRow ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ {
+ bool bAny = maTabs[nTab]->GetDataStart( rStartCol, rStartRow );
+ if (mpDrawLayer)
+ {
+ ScRange aDrawRange(0,0,nTab, MaxCol(),MaxRow(),nTab);
+ if (DrawGetPrintArea( aDrawRange, true, true ))
+ {
+ if (aDrawRange.aStart.Col()<rStartCol) rStartCol=aDrawRange.aStart.Col();
+ if (aDrawRange.aStart.Row()<rStartRow) rStartRow=aDrawRange.aStart.Row();
+ bAny = true;
+ }
+ }
+ return bAny;
+ }
+
+ rStartCol = 0;
+ rStartRow = 0;
+ return false;
+}
+
+void ScDocument::GetTiledRenderingArea(SCTAB nTab, SCCOL& rEndCol, SCROW& rEndRow) const
+{
+ bool bHasPrintArea = GetPrintArea(nTab, rEndCol, rEndRow, false);
+
+ // we need some reasonable minimal document size
+ ScViewData* pViewData = ScDocShell::GetViewData();
+ if (!pViewData)
+ {
+ if (!bHasPrintArea)
+ {
+ rEndCol = 20;
+ rEndRow = 50;
+ }
+ else
+ {
+ rEndCol += 20;
+ rEndRow += 50;
+ }
+ }
+ else if (!bHasPrintArea)
+ {
+ rEndCol = pViewData->GetMaxTiledCol();
+ rEndRow = pViewData->GetMaxTiledRow();
+ }
+ else
+ {
+ rEndCol = std::max(rEndCol, pViewData->GetMaxTiledCol());
+ rEndRow = std::max(rEndRow, pViewData->GetMaxTiledRow());
+ }
+}
+
+bool ScDocument::MoveTab( SCTAB nOldPos, SCTAB nNewPos, ScProgress* pProgress )
+{
+ if (nOldPos == nNewPos)
+ return false;
+
+ SCTAB nTabCount = static_cast<SCTAB>(maTabs.size());
+ if(nTabCount < 2)
+ return false;
+
+ bool bValid = false;
+ if (ValidTab(nOldPos) && nOldPos < nTabCount )
+ {
+ if (maTabs[nOldPos])
+ {
+ sc::AutoCalcSwitch aACSwitch(*this, false);
+ sc::DelayDeletingBroadcasters delayDeletingBroadcasters(*this);
+
+ SetNoListening( true );
+ if (nNewPos == SC_TAB_APPEND || nNewPos >= nTabCount)
+ nNewPos = nTabCount-1;
+
+ // Update Reference
+ // TODO: combine with UpdateReference!
+
+ sc::RefUpdateMoveTabContext aCxt( *this, nOldPos, nNewPos);
+
+ SCTAB nDz = nNewPos - nOldPos;
+ ScRange aSourceRange( 0,0,nOldPos, MaxCol(),MaxRow(),nOldPos );
+ if (pRangeName)
+ pRangeName->UpdateMoveTab(aCxt);
+
+ pDBCollection->UpdateMoveTab( nOldPos, nNewPos );
+ xColNameRanges->UpdateReference( URM_REORDER, this, aSourceRange, 0,0,nDz );
+ xRowNameRanges->UpdateReference( URM_REORDER, this, aSourceRange, 0,0,nDz );
+ if (pDPCollection)
+ pDPCollection->UpdateReference( URM_REORDER, aSourceRange, 0,0,nDz );
+ if (pDetOpList)
+ pDetOpList->UpdateReference( this, URM_REORDER, aSourceRange, 0,0,nDz );
+ UpdateChartRef( URM_REORDER,
+ 0,0,nOldPos, MaxCol(),MaxRow(),nOldPos, 0,0,nDz );
+ UpdateRefAreaLinks( URM_REORDER, aSourceRange, 0,0,nDz );
+ if ( pValidationList )
+ pValidationList->UpdateMoveTab(aCxt);
+ if ( pUnoBroadcaster )
+ pUnoBroadcaster->Broadcast( ScUpdateRefHint( URM_REORDER,
+ aSourceRange, 0,0,nDz ) );
+
+ ScTableUniquePtr pSaveTab = std::move(maTabs[nOldPos]);
+ maTabs.erase(maTabs.begin()+nOldPos);
+ maTabs.insert(maTabs.begin()+nNewPos, std::move(pSaveTab));
+ for (SCTAB i = 0; i < nTabCount; i++)
+ if (maTabs[i])
+ maTabs[i]->UpdateMoveTab(aCxt, i, pProgress);
+ for (auto& rxTab : maTabs)
+ if (rxTab)
+ rxTab->UpdateCompile();
+ SetNoListening( false );
+ StartAllListeners();
+
+ sc::SetFormulaDirtyContext aFormulaDirtyCxt;
+ SetAllFormulasDirty(aFormulaDirtyCxt);
+
+ if (mpDrawLayer)
+ mpDrawLayer->ScMovePage( static_cast<sal_uInt16>(nOldPos), static_cast<sal_uInt16>(nNewPos) );
+
+ bValid = true;
+ }
+ }
+ return bValid;
+}
+
+bool ScDocument::CopyTab( SCTAB nOldPos, SCTAB nNewPos, const ScMarkData* pOnlyMarked )
+{
+ if (SC_TAB_APPEND == nNewPos || nNewPos >= static_cast<SCTAB>(maTabs.size()))
+ nNewPos = static_cast<SCTAB>(maTabs.size());
+ OUString aName;
+ GetName(nOldPos, aName);
+
+ // check first if Prefix is valid; if not, then only avoid duplicates
+ bool bPrefix = ValidTabName( aName );
+ OSL_ENSURE(bPrefix, "invalid table name");
+ SCTAB nDummy;
+
+ CreateValidTabName(aName);
+
+ bool bValid;
+ if (bPrefix)
+ bValid = ValidNewTabName(aName);
+ else
+ bValid = !GetTable( aName, nDummy );
+
+ sc::AutoCalcSwitch aACSwitch(*this, false);
+ sc::RefUpdateInsertTabContext aCxt( *this, nNewPos, 1);
+
+ if (bValid)
+ {
+ if (nNewPos >= static_cast<SCTAB>(maTabs.size()))
+ {
+ nNewPos = static_cast<SCTAB>(maTabs.size());
+ maTabs.emplace_back(new ScTable(*this, nNewPos, aName));
+ }
+ else
+ {
+ if (ValidTab(nNewPos) && (nNewPos < static_cast<SCTAB>(maTabs.size())))
+ {
+ SetNoListening( true );
+
+ ScRange aRange( 0,0,nNewPos, MaxCol(),MaxRow(),MAXTAB );
+ xColNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,1 );
+ xRowNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,1 );
+ if (pRangeName)
+ pRangeName->UpdateInsertTab(aCxt);
+
+ pDBCollection->UpdateReference(
+ URM_INSDEL, 0,0,nNewPos, MaxCol(),MaxRow(),MAXTAB, 0,0,1 );
+ if (pDPCollection)
+ pDPCollection->UpdateReference( URM_INSDEL, aRange, 0,0,1 );
+ if (pDetOpList)
+ pDetOpList->UpdateReference( this, URM_INSDEL, aRange, 0,0,1 );
+ UpdateChartRef( URM_INSDEL, 0,0,nNewPos, MaxCol(),MaxRow(),MAXTAB, 0,0,1 );
+ UpdateRefAreaLinks( URM_INSDEL, aRange, 0,0,1 );
+ if ( pUnoBroadcaster )
+ pUnoBroadcaster->Broadcast( ScUpdateRefHint( URM_INSDEL, aRange, 0,0,1 ) );
+
+ for (TableContainer::iterator it = maTabs.begin(); it != maTabs.end(); ++it)
+ if (*it && it != (maTabs.begin() + nOldPos))
+ (*it)->UpdateInsertTab(aCxt);
+ if (nNewPos <= nOldPos)
+ nOldPos++;
+ maTabs.emplace(maTabs.begin() + nNewPos, new ScTable(*this, nNewPos, aName));
+ bValid = true;
+ for (TableContainer::iterator it = maTabs.begin(); it != maTabs.end(); ++it)
+ if (*it && it != maTabs.begin()+nOldPos && it != maTabs.begin() + nNewPos)
+ (*it)->UpdateCompile();
+ SetNoListening( false );
+ sc::StartListeningContext aSLCxt(*this);
+ for (TableContainer::iterator it = maTabs.begin(); it != maTabs.end(); ++it)
+ if (*it && it != maTabs.begin()+nOldPos && it != maTabs.begin()+nNewPos)
+ (*it)->StartListeners(aSLCxt, true);
+
+ if (pValidationList)
+ pValidationList->UpdateInsertTab(aCxt);
+ }
+ else
+ bValid = false;
+ }
+ }
+
+ if (bValid)
+ {
+ SetNoListening( true ); // not yet at CopyToTable/Insert
+
+ const bool bGlobalNamesToLocal = true;
+ const SCTAB nRealOldPos = (nNewPos < nOldPos) ? nOldPos - 1 : nOldPos;
+ const ScRangeName* pNames = GetRangeName( nOldPos);
+ if (pNames)
+ pNames->CopyUsedNames( nOldPos, nRealOldPos, nNewPos, *this, *this, bGlobalNamesToLocal);
+ GetRangeName()->CopyUsedNames( -1, nRealOldPos, nNewPos, *this, *this, bGlobalNamesToLocal);
+
+ sc::CopyToDocContext aCopyDocCxt(*this);
+ pDBCollection->CopyToTable(nOldPos, nNewPos);
+ maTabs[nOldPos]->CopyToTable(aCopyDocCxt, 0, 0, MaxCol(), MaxRow(), InsertDeleteFlags::ALL,
+ (pOnlyMarked != nullptr), maTabs[nNewPos].get(), pOnlyMarked,
+ false /*bAsLink*/, true /*bColRowFlags*/, bGlobalNamesToLocal, false /*bCopyCaptions*/ );
+ maTabs[nNewPos]->SetTabBgColor(maTabs[nOldPos]->GetTabBgColor());
+
+ SCTAB nDz = nNewPos - nOldPos;
+ sc::RefUpdateContext aRefCxt(*this);
+ aRefCxt.meMode = URM_COPY;
+ aRefCxt.maRange = ScRange(0, 0, nNewPos, MaxCol(), MaxRow(), nNewPos);
+ aRefCxt.mnTabDelta = nDz;
+ maTabs[nNewPos]->UpdateReference(aRefCxt);
+
+ maTabs[nNewPos]->UpdateInsertTabAbs(nNewPos); // move all paragraphs up by one!!
+ maTabs[nOldPos]->UpdateInsertTab(aCxt);
+
+ maTabs[nOldPos]->UpdateCompile();
+ maTabs[nNewPos]->UpdateCompile( true ); // maybe already compiled in Clone, but used names need recompilation
+ SetNoListening( false );
+ sc::StartListeningContext aSLCxt(*this);
+ maTabs[nOldPos]->StartListeners(aSLCxt, true);
+ maTabs[nNewPos]->StartListeners(aSLCxt, true);
+
+ sc::SetFormulaDirtyContext aFormulaDirtyCxt;
+ SetAllFormulasDirty(aFormulaDirtyCxt);
+
+ if (mpDrawLayer) // Skip cloning Note caption object
+ // page is already created in ScTable ctor
+ mpDrawLayer->ScCopyPage( static_cast<sal_uInt16>(nOldPos), static_cast<sal_uInt16>(nNewPos) );
+
+ if (pDPCollection)
+ pDPCollection->CopyToTab(nOldPos, nNewPos);
+
+ maTabs[nNewPos]->SetPageStyle( maTabs[nOldPos]->GetPageStyle() );
+ maTabs[nNewPos]->SetPendingRowHeights( maTabs[nOldPos]->IsPendingRowHeights() );
+
+ // Copy the custom print range if exists.
+ maTabs[nNewPos]->CopyPrintRange(*maTabs[nOldPos]);
+
+ // Copy the RTL settings
+ maTabs[nNewPos]->SetLayoutRTL(maTabs[nOldPos]->IsLayoutRTL());
+ maTabs[nNewPos]->SetLoadingRTL(maTabs[nOldPos]->IsLoadingRTL());
+
+ // Finally copy the note captions, which need
+ // 1. the updated source ScColumn::nTab members if nNewPos <= nOldPos
+ // 2. row heights and column widths of the destination
+ // 3. RTL settings of the destination
+ maTabs[nOldPos]->CopyCaptionsToTable( 0, 0, MaxCol(), MaxRow(), maTabs[nNewPos].get(), true /*bCloneCaption*/);
+ }
+
+ return bValid;
+}
+
+sal_uLong ScDocument::TransferTab( ScDocument& rSrcDoc, SCTAB nSrcPos,
+ SCTAB nDestPos, bool bInsertNew,
+ bool bResultsOnly )
+{
+ sal_uLong nRetVal = 1; // 0 => error 1 = ok
+ // 3 => NameBox
+ // 4 => both
+
+ if (rSrcDoc.mpShell->GetMedium())
+ {
+ rSrcDoc.maFileURL = rSrcDoc.mpShell->GetMedium()->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::ToIUri);
+ // for unsaved files use the title name and adjust during save of file
+ if (rSrcDoc.maFileURL.isEmpty())
+ rSrcDoc.maFileURL = rSrcDoc.mpShell->GetName();
+ }
+ else
+ {
+ rSrcDoc.maFileURL = rSrcDoc.mpShell->GetName();
+ }
+
+ bool bValid = true;
+ if (bInsertNew) // re-insert
+ {
+ OUString aName;
+ rSrcDoc.GetName(nSrcPos, aName);
+ CreateValidTabName(aName);
+ bValid = InsertTab(nDestPos, aName);
+
+ // Copy the RTL settings
+ maTabs[nDestPos]->SetLayoutRTL(rSrcDoc.maTabs[nSrcPos]->IsLayoutRTL());
+ maTabs[nDestPos]->SetLoadingRTL(rSrcDoc.maTabs[nSrcPos]->IsLoadingRTL());
+ }
+ else // replace existing tables
+ {
+ if (ValidTab(nDestPos) && nDestPos < static_cast<SCTAB>(maTabs.size()) && maTabs[nDestPos])
+ {
+ maTabs[nDestPos]->DeleteArea( 0,0, MaxCol(),MaxRow(), InsertDeleteFlags::ALL );
+ }
+ else
+ bValid = false;
+ }
+
+ if (bValid)
+ {
+ bool bOldAutoCalcSrc = false;
+ bool bOldAutoCalc = GetAutoCalc();
+ SetAutoCalc( false ); // avoid repeated calculations
+ SetNoListening( true );
+ if ( bResultsOnly )
+ {
+ bOldAutoCalcSrc = rSrcDoc.GetAutoCalc();
+ rSrcDoc.SetAutoCalc( true ); // in case something needs calculation
+ }
+
+ {
+ NumFmtMergeHandler aNumFmtMergeHdl(*this, rSrcDoc);
+
+ sc::CopyToDocContext aCxt(*this);
+ nDestPos = std::min(nDestPos, static_cast<SCTAB>(GetTableCount() - 1));
+ { // scope for bulk broadcast
+ ScBulkBroadcast aBulkBroadcast( pBASM.get(), SfxHintId::ScDataChanged);
+ if (!bResultsOnly)
+ {
+ const bool bGlobalNamesToLocal = false;
+ const ScRangeName* pNames = rSrcDoc.GetRangeName( nSrcPos);
+ if (pNames)
+ pNames->CopyUsedNames( nSrcPos, nSrcPos, nDestPos, rSrcDoc, *this, bGlobalNamesToLocal);
+ rSrcDoc.GetRangeName()->CopyUsedNames( -1, nSrcPos, nDestPos, rSrcDoc, *this, bGlobalNamesToLocal);
+ }
+ rSrcDoc.maTabs[nSrcPos]->CopyToTable(aCxt, 0, 0, MaxCol(), MaxRow(),
+ ( bResultsOnly ? InsertDeleteFlags::ALL & ~InsertDeleteFlags::FORMULA : InsertDeleteFlags::ALL),
+ false, maTabs[nDestPos].get(), /*pMarkData*/nullptr, /*bAsLink*/false, /*bColRowFlags*/true,
+ /*bGlobalNamesToLocal*/false, /*bCopyCaptions*/true );
+ }
+ }
+ maTabs[nDestPos]->SetTabNo(nDestPos);
+ maTabs[nDestPos]->SetTabBgColor(rSrcDoc.maTabs[nSrcPos]->GetTabBgColor());
+
+ if ( !bResultsOnly )
+ {
+ sc::RefUpdateContext aRefCxt(*this);
+ aRefCxt.meMode = URM_COPY;
+ aRefCxt.maRange = ScRange(0, 0, nDestPos, MaxCol(), MaxRow(), nDestPos);
+ aRefCxt.mnTabDelta = nDestPos - nSrcPos;
+ maTabs[nDestPos]->UpdateReference(aRefCxt);
+
+ // Readjust self-contained absolute references to this sheet
+ maTabs[nDestPos]->TestTabRefAbs(nSrcPos);
+ sc::CompileFormulaContext aFormulaCxt(*this);
+ maTabs[nDestPos]->CompileAll(aFormulaCxt);
+ }
+
+ SetNoListening( false );
+ if ( !bResultsOnly )
+ {
+ sc::StartListeningContext aSLCxt(*this);
+ maTabs[nDestPos]->StartListeners(aSLCxt, true);
+ }
+ SetDirty( ScRange( 0, 0, nDestPos, MaxCol(), MaxRow(), nDestPos), false);
+
+ if ( bResultsOnly )
+ rSrcDoc.SetAutoCalc( bOldAutoCalcSrc );
+ SetAutoCalc( bOldAutoCalc );
+
+ // copy Drawing
+
+ if (bInsertNew)
+ TransferDrawPage( rSrcDoc, nSrcPos, nDestPos );
+
+ maTabs[nDestPos]->SetPendingRowHeights( rSrcDoc.maTabs[nSrcPos]->IsPendingRowHeights() );
+ }
+ if (!bValid)
+ nRetVal = 0;
+ bool bVbaEnabled = IsInVBAMode();
+
+ if ( bVbaEnabled )
+ {
+ SfxObjectShell* pSrcShell = rSrcDoc.GetDocumentShell();
+ if ( pSrcShell )
+ {
+ OUString aLibName("Standard");
+#if HAVE_FEATURE_SCRIPTING
+ const BasicManager *pBasicManager = pSrcShell->GetBasicManager();
+ if (pBasicManager && !pBasicManager->GetName().isEmpty())
+ {
+ aLibName = pSrcShell->GetBasicManager()->GetName();
+ }
+#endif
+ OUString sSource;
+ uno::Reference< script::XLibraryContainer > xLibContainer = pSrcShell->GetBasicContainer();
+ uno::Reference< container::XNameContainer > xLib;
+ if( xLibContainer.is() )
+ {
+ uno::Any aLibAny = xLibContainer->getByName(aLibName);
+ aLibAny >>= xLib;
+ }
+
+ if( xLib.is() )
+ {
+ OUString sSrcCodeName;
+ rSrcDoc.GetCodeName( nSrcPos, sSrcCodeName );
+ OUString sRTLSource;
+ xLib->getByName( sSrcCodeName ) >>= sRTLSource;
+ sSource = sRTLSource;
+ }
+ VBA_InsertModule( *this, nDestPos, sSource );
+ }
+ }
+
+ return nRetVal;
+}
+
+void ScDocument::SetError( SCCOL nCol, SCROW nRow, SCTAB nTab, const FormulaError nError)
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ if (maTabs[nTab])
+ maTabs[nTab]->SetError( nCol, nRow, nError );
+}
+
+void ScDocument::SetFormula(
+ const ScAddress& rPos, const ScTokenArray& rArray )
+{
+ if (!TableExists(rPos.Tab()))
+ return;
+
+ maTabs[rPos.Tab()]->SetFormula(rPos.Col(), rPos.Row(), rArray, formula::FormulaGrammar::GRAM_DEFAULT);
+}
+
+void ScDocument::SetFormula(
+ const ScAddress& rPos, const OUString& rFormula, formula::FormulaGrammar::Grammar eGram )
+{
+ if (!TableExists(rPos.Tab()))
+ return;
+
+ maTabs[rPos.Tab()]->SetFormula(rPos.Col(), rPos.Row(), rFormula, eGram);
+}
+
+ScFormulaCell* ScDocument::SetFormulaCell( const ScAddress& rPos, ScFormulaCell* pCell )
+{
+ if (!TableExists(rPos.Tab()))
+ {
+ delete pCell;
+ return nullptr;
+ }
+
+ return maTabs[rPos.Tab()]->SetFormulaCell(rPos.Col(), rPos.Row(), pCell);
+}
+
+bool ScDocument::SetFormulaCells( const ScAddress& rPos, std::vector<ScFormulaCell*>& rCells )
+{
+ if (rCells.empty())
+ return false;
+
+ ScTable* pTab = FetchTable(rPos.Tab());
+ if (!pTab)
+ return false;
+
+ return pTab->SetFormulaCells(rPos.Col(), rPos.Row(), rCells);
+}
+
+void ScDocument::SetConsolidateDlgData( std::unique_ptr<ScConsolidateParam> pData )
+{
+ pConsolidateDlgData = std::move(pData);
+}
+
+void ScDocument::SetChangeViewSettings(const ScChangeViewSettings& rNew)
+{
+ if (pChangeViewSettings==nullptr)
+ pChangeViewSettings.reset( new ScChangeViewSettings );
+
+ *pChangeViewSettings=rNew;
+}
+
+std::unique_ptr<ScFieldEditEngine> ScDocument::CreateFieldEditEngine()
+{
+ std::unique_ptr<ScFieldEditEngine> pNewEditEngine;
+ if (!pCacheFieldEditEngine)
+ {
+ pNewEditEngine.reset( new ScFieldEditEngine(
+ this, GetEnginePool(), GetEditPool(), false) );
+ }
+ else
+ {
+ if ( !bImportingXML )
+ {
+ // #i66209# previous use might not have restored update mode,
+ // ensure same state as for a new EditEngine (UpdateMode = true)
+ pCacheFieldEditEngine->SetUpdateLayout(true);
+ }
+
+ pNewEditEngine = std::move(pCacheFieldEditEngine);
+ }
+ return pNewEditEngine;
+}
+
+void ScDocument::DisposeFieldEditEngine(std::unique_ptr<ScFieldEditEngine>& rpEditEngine)
+{
+ if (!pCacheFieldEditEngine && rpEditEngine)
+ {
+ pCacheFieldEditEngine = std::move( rpEditEngine );
+ pCacheFieldEditEngine->Clear();
+ }
+ else
+ rpEditEngine.reset();
+}
+
+ScLookupCache & ScDocument::GetLookupCache( const ScRange & rRange, ScInterpreterContext* pContext )
+{
+ ScLookupCache* pCache = nullptr;
+ if (!pContext->mxScLookupCache)
+ pContext->mxScLookupCache.reset(new ScLookupCacheMap);
+ ScLookupCacheMap* pCacheMap = pContext->mxScLookupCache.get();
+ // insert with temporary value to avoid doing two lookups
+ auto [findIt, bInserted] = pCacheMap->aCacheMap.emplace(rRange, nullptr);
+ if (bInserted)
+ {
+ findIt->second = std::make_unique<ScLookupCache>(this, rRange, *pCacheMap);
+ pCache = findIt->second.get();
+ // The StartListeningArea() call is not thread-safe, as all threads
+ // would access the same SvtBroadcaster.
+ std::unique_lock guard( mScLookupMutex );
+ StartListeningArea(rRange, false, pCache);
+ }
+ else
+ pCache = (*findIt).second.get();
+
+ return *pCache;
+}
+
+ScSortedRangeCache& ScDocument::GetSortedRangeCache( const ScRange & rRange, const ScQueryParam& param,
+ ScInterpreterContext* pContext )
+{
+ assert(mxScSortedRangeCache);
+ ScSortedRangeCache::HashKey key = ScSortedRangeCache::makeHashKey(rRange, param);
+ // This should be created just once for one range, and repeated calls should reuse it, even
+ // between threads (it doesn't make sense to use ScInterpreterContext and have all threads
+ // build their own copy of the same data). So first try read-only access, which should
+ // in most cases be enough.
+ {
+ std::shared_lock guard(mScLookupMutex);
+ auto findIt = mxScSortedRangeCache->aCacheMap.find(key);
+ if( findIt != mxScSortedRangeCache->aCacheMap.end())
+ return *findIt->second;
+ }
+ // Avoid recursive calls because of some cells in the range being dirty and triggering
+ // interpreting, which may call into this again. Threaded calculation makes sure
+ // no cells are dirty. If some cells in the range cannot be interpreted and remain
+ // dirty e.g. because of circular dependencies, create only an invalid empty cache to prevent
+ // a possible recursive deadlock.
+ bool invalid = false;
+ if(!IsThreadedGroupCalcInProgress())
+ if(!InterpretCellsIfNeeded(rRange))
+ invalid = true;
+ std::unique_lock guard(mScLookupMutex);
+ auto [findIt, bInserted] = mxScSortedRangeCache->aCacheMap.emplace(key, nullptr);
+ if (bInserted)
+ {
+ findIt->second = std::make_unique<ScSortedRangeCache>(this, rRange, param, pContext, invalid);
+ StartListeningArea(rRange, false, findIt->second.get());
+ }
+ return *findIt->second;
+}
+
+void ScDocument::RemoveLookupCache( ScLookupCache & rCache )
+{
+ // Data changes leading to this should never happen during calculation (they are either
+ // a result of user input or recalc). If it turns out this can be the case, locking is needed
+ // here and also in ScLookupCache::Notify().
+ assert(!IsThreadedGroupCalcInProgress());
+ auto & cacheMap = rCache.getCacheMap();
+ auto it(cacheMap.aCacheMap.find(rCache.getRange()));
+ if (it != cacheMap.aCacheMap.end())
+ {
+ ScLookupCache* pCache = (*it).second.release();
+ cacheMap.aCacheMap.erase(it);
+ assert(!IsThreadedGroupCalcInProgress()); // EndListeningArea() is not thread-safe
+ EndListeningArea(pCache->getRange(), false, &rCache);
+ return;
+ }
+ OSL_FAIL( "ScDocument::RemoveLookupCache: range not found in hash map");
+}
+
+void ScDocument::RemoveSortedRangeCache( ScSortedRangeCache & rCache )
+{
+ // Data changes leading to this should never happen during calculation (they are either
+ // a result of user input or recalc). If it turns out this can be the case, locking is needed
+ // here and also in ScSortedRangeCache::Notify().
+ assert(!IsThreadedGroupCalcInProgress());
+ auto it(mxScSortedRangeCache->aCacheMap.find(rCache.getHashKey()));
+ if (it != mxScSortedRangeCache->aCacheMap.end())
+ {
+ ScSortedRangeCache* pCache = (*it).second.release();
+ mxScSortedRangeCache->aCacheMap.erase(it);
+ assert(!IsThreadedGroupCalcInProgress()); // EndListeningArea() is not thread-safe
+ EndListeningArea(pCache->getRange(), false, &rCache);
+ return;
+ }
+ OSL_FAIL( "ScDocument::RemoveSortedRangeCache: range not found in hash map");
+}
+
+void ScDocument::ClearLookupCaches()
+{
+ assert(!IsThreadedGroupCalcInProgress());
+ GetNonThreadedContext().mxScLookupCache.reset();
+ mxScSortedRangeCache->aCacheMap.clear();
+ // Clear lookup cache in all interpreter-contexts in the (threaded/non-threaded) pools.
+ ScInterpreterContextPool::ClearLookupCaches();
+}
+
+bool ScDocument::IsCellInChangeTrack(const ScAddress &cell,Color *pColCellBorder)
+{
+ ScChangeTrack* pTrack = GetChangeTrack();
+ ScChangeViewSettings* pSettings = GetChangeViewSettings();
+ if ( !pTrack || !pTrack->GetFirst() || !pSettings || !pSettings->ShowChanges() )
+ return false; // missing or turned-off
+ ScActionColorChanger aColorChanger(*pTrack);
+ // Clipping happens from outside
+ //! TODO: without Clipping; only paint affected cells ??!??!?
+ const ScChangeAction* pAction = pTrack->GetFirst();
+ while (pAction)
+ {
+ if ( pAction->IsVisible() )
+ {
+ ScChangeActionType eType = pAction->GetType();
+ const ScBigRange& rBig = pAction->GetBigRange();
+ if ( rBig.aStart.Tab() == cell.Tab())
+ {
+ ScRange aRange = rBig.MakeRange( *this );
+ if ( eType == SC_CAT_DELETE_ROWS )
+ aRange.aEnd.SetRow( aRange.aStart.Row() );
+ else if ( eType == SC_CAT_DELETE_COLS )
+ aRange.aEnd.SetCol( aRange.aStart.Col() );
+ if (ScViewUtil::IsActionShown( *pAction, *pSettings, *this ) )
+ {
+ if (aRange.Contains(cell))
+ {
+ if (pColCellBorder != nullptr)
+ {
+ aColorChanger.Update( *pAction );
+ Color aColor( aColorChanger.GetColor() );
+ *pColCellBorder = aColor;
+ }
+ return true;
+ }
+ }
+ }
+ if ( eType == SC_CAT_MOVE &&
+ static_cast<const ScChangeActionMove*>(pAction)->
+ GetFromRange().aStart.Tab() == cell.Col() )
+ {
+ ScRange aRange = static_cast<const ScChangeActionMove*>(pAction)->
+ GetFromRange().MakeRange( *this );
+ if (ScViewUtil::IsActionShown( *pAction, *pSettings, *this ) )
+ {
+ if (aRange.Contains(cell))
+ {
+ if (pColCellBorder != nullptr)
+ {
+ aColorChanger.Update( *pAction );
+ Color aColor( aColorChanger.GetColor() );
+ *pColCellBorder = aColor;
+ }
+ return true;
+ }
+ }
+ }
+ }
+ pAction = pAction->GetNext();
+ }
+ return false;
+}
+
+void ScDocument::GetCellChangeTrackNote( const ScAddress &aCellPos, OUString &aTrackText,bool &bLeftEdge)
+{
+ aTrackText.clear();
+ // Change-Tracking
+ ScChangeTrack* pTrack = GetChangeTrack();
+ ScChangeViewSettings* pSettings = GetChangeViewSettings();
+ if ( !(pTrack && pTrack->GetFirst() && pSettings && pSettings->ShowChanges()))
+ return;
+
+ const ScChangeAction* pFound = nullptr;
+ const ScChangeAction* pFoundContent = nullptr;
+ const ScChangeAction* pFoundMove = nullptr;
+ const ScChangeAction* pAction = pTrack->GetFirst();
+ while (pAction)
+ {
+ if ( pAction->IsVisible() &&
+ ScViewUtil::IsActionShown( *pAction, *pSettings, *this ) )
+ {
+ ScChangeActionType eType = pAction->GetType();
+ const ScBigRange& rBig = pAction->GetBigRange();
+ if ( rBig.aStart.Tab() == aCellPos.Tab())
+ {
+ ScRange aRange = rBig.MakeRange( *this );
+ if ( eType == SC_CAT_DELETE_ROWS )
+ aRange.aEnd.SetRow( aRange.aStart.Row() );
+ else if ( eType == SC_CAT_DELETE_COLS )
+ aRange.aEnd.SetCol( aRange.aStart.Col() );
+ if ( aRange.Contains( aCellPos ) )
+ {
+ pFound = pAction; // the last wins
+ switch ( eType )
+ {
+ case SC_CAT_CONTENT :
+ pFoundContent = pAction;
+ break;
+ case SC_CAT_MOVE :
+ pFoundMove = pAction;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ if ( eType == SC_CAT_MOVE )
+ {
+ ScRange aRange =
+ static_cast<const ScChangeActionMove*>(pAction)->
+ GetFromRange().MakeRange( *this );
+ if ( aRange.Contains( aCellPos ) )
+ {
+ pFound = pAction;
+ }
+ }
+ }
+ pAction = pAction->GetNext();
+ }
+ if ( !pFound )
+ return;
+
+ if ( pFoundContent && pFound->GetType() != SC_CAT_CONTENT )
+ pFound = pFoundContent; // Content wins
+ if ( pFoundMove && pFound->GetType() != SC_CAT_MOVE &&
+ pFoundMove->GetActionNumber() >
+ pFound->GetActionNumber() )
+ pFound = pFoundMove; // Move wins
+ // for deleted columns: arrow on left side of row
+ if ( pFound->GetType() == SC_CAT_DELETE_COLS )
+ bLeftEdge = true;
+ DateTime aDT = pFound->GetDateTime();
+ aTrackText = pFound->GetUser();
+ aTrackText += ", ";
+ aTrackText += ScGlobal::getLocaleData().getDate(aDT);
+ aTrackText += " ";
+ aTrackText += ScGlobal::getLocaleData().getTime(aDT);
+ aTrackText += ":\n";
+ OUString aComStr = pFound->GetComment();
+ if(!aComStr.isEmpty())
+ {
+ aTrackText += aComStr;
+ aTrackText += "\n( ";
+ }
+ aTrackText = pFound->GetDescription( *this );
+ if (!aComStr.isEmpty())
+ {
+ aTrackText += ")";
+ }
+}
+
+void ScDocument::SetPreviewFont( std::unique_ptr<SfxItemSet> pFont )
+{
+ pPreviewFont = std::move(pFont);
+}
+
+void ScDocument::SetPreviewSelection( const ScMarkData& rSel )
+{
+ maPreviewSelection = rSel;
+}
+
+SfxItemSet* ScDocument::GetPreviewFont( SCCOL nCol, SCROW nRow, SCTAB nTab )
+{
+ SfxItemSet* pRet = nullptr;
+ if ( pPreviewFont )
+ {
+ ScMarkData aSel = GetPreviewSelection();
+ if ( aSel.IsCellMarked( nCol, nRow ) && aSel.GetFirstSelected() == nTab )
+ pRet = pPreviewFont.get();
+ }
+ return pRet;
+}
+
+ScStyleSheet* ScDocument::GetPreviewCellStyle( SCCOL nCol, SCROW nRow, SCTAB nTab )
+{
+ ScStyleSheet* pRet = nullptr;
+ ScMarkData aSel = GetPreviewSelection();
+ if ( pPreviewCellStyle && aSel.IsCellMarked( nCol, nRow ) && aSel.GetFirstSelected() == nTab )
+ pRet = pPreviewCellStyle;
+ return pRet;
+}
+
+sc::IconSetBitmapMap& ScDocument::GetIconSetBitmapMap()
+{
+ if (!m_pIconSetBitmapMap)
+ {
+ m_pIconSetBitmapMap.reset(new sc::IconSetBitmapMap);
+ }
+ return *m_pIconSetBitmapMap;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/documen3.cxx b/sc/source/core/data/documen3.cxx
new file mode 100644
index 000000000..77afc2ff7
--- /dev/null
+++ b/sc/source/core/data/documen3.cxx
@@ -0,0 +1,2155 @@
+/* -*- 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/script/vba/XVBAEventProcessor.hpp>
+#include <com/sun/star/sheet/TableValidationVisibility.hpp>
+#include <scitems.hxx>
+#include <editeng/langitem.hxx>
+#include <svl/srchitem.hxx>
+#include <sfx2/linkmgr.hxx>
+#include <sfx2/bindings.hxx>
+#include <sfx2/objsh.hxx>
+#include <sfx2/viewsh.hxx>
+#include <vcl/svapp.hxx>
+#include <osl/thread.hxx>
+#include <osl/diagnose.h>
+#include <document.hxx>
+#include <attrib.hxx>
+#include <table.hxx>
+#include <rangenam.hxx>
+#include <dbdata.hxx>
+#include <docpool.hxx>
+#include <poolhelp.hxx>
+#include <rangelst.hxx>
+#include <chartlock.hxx>
+#include <refupdat.hxx>
+#include <docoptio.hxx>
+#include <scmod.hxx>
+#include <clipoptions.hxx>
+#include <viewopti.hxx>
+#include <scextopt.hxx>
+#include <tablink.hxx>
+#include <externalrefmgr.hxx>
+#include <markdata.hxx>
+#include <validat.hxx>
+#include <dociter.hxx>
+#include <detdata.hxx>
+#include <inputopt.hxx>
+#include <chartlis.hxx>
+#include <sc.hrc>
+#include <hints.hxx>
+#include <dpobject.hxx>
+#include <drwlayer.hxx>
+#include <unoreflist.hxx>
+#include <listenercalls.hxx>
+#include <tabprotection.hxx>
+#include <formulaparserpool.hxx>
+#include <clipparam.hxx>
+#include <sheetevents.hxx>
+#include <queryentry.hxx>
+#include <formulacell.hxx>
+#include <refupdatecontext.hxx>
+#include <scopetools.hxx>
+#include <filterentries.hxx>
+#include <queryparam.hxx>
+
+#include <globalnames.hxx>
+#include <LibreOfficeKit/LibreOfficeKitEnums.h>
+#include <comphelper/lok.hxx>
+#include <config_fuzzers.h>
+#include <memory>
+
+using namespace com::sun::star;
+
+namespace {
+
+void sortAndRemoveDuplicates(std::vector<ScTypedStrData>& rStrings, bool bCaseSens)
+{
+ if (bCaseSens)
+ {
+ std::sort(rStrings.begin(), rStrings.end(), ScTypedStrData::LessCaseSensitive());
+ std::vector<ScTypedStrData>::iterator it =
+ std::unique(rStrings.begin(), rStrings.end(), ScTypedStrData::EqualCaseSensitive());
+ rStrings.erase(it, rStrings.end());
+ }
+ else
+ {
+ std::sort(rStrings.begin(), rStrings.end(), ScTypedStrData::LessCaseInsensitive());
+ std::vector<ScTypedStrData>::iterator it =
+ std::unique(rStrings.begin(), rStrings.end(), ScTypedStrData::EqualCaseInsensitive());
+ rStrings.erase(it, rStrings.end());
+ }
+}
+
+}
+
+void ScDocument::GetAllTabRangeNames(ScRangeName::TabNameCopyMap& rNames) const
+{
+ ScRangeName::TabNameCopyMap aNames;
+ for (SCTAB i = 0; i < static_cast<SCTAB>(maTabs.size()); ++i)
+ {
+ if (!maTabs[i])
+ // no more tables to iterate through.
+ break;
+
+ const ScRangeName* p = maTabs[i]->mpRangeName.get();
+ if (!p || p->empty())
+ // ignore empty ones.
+ continue;
+
+ aNames.emplace(i, p);
+ }
+ rNames.swap(aNames);
+}
+
+void ScDocument::SetAllRangeNames(const std::map<OUString, std::unique_ptr<ScRangeName>>& rRangeMap)
+{
+ for (const auto& [rName, rxRangeName] : rRangeMap)
+ {
+ if (rName == STR_GLOBAL_RANGE_NAME)
+ {
+ pRangeName.reset();
+ const ScRangeName *const pName = rxRangeName.get();
+ if (!pName->empty())
+ pRangeName.reset( new ScRangeName( *pName ) );
+ }
+ else
+ {
+ const ScRangeName *const pName = rxRangeName.get();
+ SCTAB nTab;
+ bool bFound = GetTable(rName, nTab);
+ assert(bFound); (void)bFound; // fouled up?
+ if (pName->empty())
+ SetRangeName( nTab, nullptr );
+ else
+ SetRangeName( nTab, std::unique_ptr<ScRangeName>(new ScRangeName( *pName )) );
+ }
+ }
+}
+
+void ScDocument::GetRangeNameMap(std::map<OUString, ScRangeName*>& aRangeNameMap)
+{
+ for (SCTAB i = 0; i < static_cast<SCTAB>(maTabs.size()); ++i)
+ {
+ if (!maTabs[i])
+ continue;
+ ScRangeName* p = maTabs[i]->GetRangeName();
+ if (!p )
+ {
+ p = new ScRangeName();
+ SetRangeName(i, std::unique_ptr<ScRangeName>(p));
+ }
+ OUString aTableName = maTabs[i]->GetName();
+ aRangeNameMap.insert(std::pair<OUString, ScRangeName*>(aTableName,p));
+ }
+ if (!pRangeName)
+ {
+ pRangeName.reset(new ScRangeName());
+ }
+ aRangeNameMap.insert(std::pair<OUString, ScRangeName*>(STR_GLOBAL_RANGE_NAME, pRangeName.get()));
+}
+
+ScRangeName* ScDocument::GetRangeName(SCTAB nTab) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return nullptr;
+
+ return maTabs[nTab]->GetRangeName();
+}
+
+ScRangeName* ScDocument::GetRangeName() const
+{
+ if (!pRangeName)
+ pRangeName.reset(new ScRangeName);
+ return pRangeName.get();
+}
+
+void ScDocument::SetRangeName(SCTAB nTab, std::unique_ptr<ScRangeName> pNew)
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return;
+
+ return maTabs[nTab]->SetRangeName(std::move(pNew));
+}
+
+void ScDocument::SetRangeName( std::unique_ptr<ScRangeName> pNewRangeName )
+{
+ pRangeName = std::move(pNewRangeName);
+}
+
+bool ScDocument::IsAddressInRangeName( RangeNameScope eScope, const ScAddress& rAddress )
+{
+ ScRangeName* pRangeNames;
+ ScRange aNameRange;
+
+ if (eScope == RangeNameScope::GLOBAL)
+ pRangeNames= GetRangeName();
+ else
+ pRangeNames= GetRangeName(rAddress.Tab());
+
+ for (const auto& rEntry : *pRangeNames)
+ {
+ if (rEntry.second->IsValidReference(aNameRange))
+ {
+ if (aNameRange.Contains(rAddress))
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool ScDocument::InsertNewRangeName( const OUString& rName, const ScAddress& rPos, const OUString& rExpr )
+{
+ ScRangeName* pGlobalNames = GetRangeName();
+ if (!pGlobalNames)
+ return false;
+
+ ScRangeData* pName = new ScRangeData(*this, rName, rExpr, rPos, ScRangeData::Type::Name, GetGrammar());
+ return pGlobalNames->insert(pName);
+}
+
+bool ScDocument::InsertNewRangeName( SCTAB nTab, const OUString& rName, const ScAddress& rPos, const OUString& rExpr )
+{
+ ScRangeName* pLocalNames = GetRangeName(nTab);
+ if (!pLocalNames)
+ return false;
+
+ ScRangeData* pName = new ScRangeData(*this, rName, rExpr, rPos, ScRangeData::Type::Name, GetGrammar());
+ return pLocalNames->insert(pName);
+}
+
+const ScRangeData* ScDocument::GetRangeAtBlock( const ScRange& rBlock, OUString& rName, bool* pSheetLocal ) const
+{
+ const ScRangeData* pData = nullptr;
+ if (rBlock.aStart.Tab() == rBlock.aEnd.Tab())
+ {
+ const ScRangeName* pLocalNames = GetRangeName(rBlock.aStart.Tab());
+ if (pLocalNames)
+ {
+ pData = pLocalNames->findByRange( rBlock );
+ if (pData)
+ {
+ rName = pData->GetName();
+ if (pSheetLocal)
+ *pSheetLocal = true;
+ return pData;
+ }
+ }
+ }
+ if ( pRangeName )
+ {
+ pData = pRangeName->findByRange( rBlock );
+ if (pData)
+ {
+ rName = pData->GetName();
+ if (pSheetLocal)
+ *pSheetLocal = false;
+ }
+ }
+ return pData;
+}
+
+ScRangeData* ScDocument::FindRangeNameBySheetAndIndex( SCTAB nTab, sal_uInt16 nIndex ) const
+{
+ const ScRangeName* pRN = (nTab < 0 ? GetRangeName() : GetRangeName(nTab));
+ return (pRN ? pRN->findByIndex( nIndex) : nullptr);
+}
+
+void ScDocument::SetDBCollection( std::unique_ptr<ScDBCollection> pNewDBCollection, bool bRemoveAutoFilter )
+{
+ if (pDBCollection && bRemoveAutoFilter)
+ {
+ // remove auto filter attribute if new db data don't contain auto filter flag
+ // start position is also compared, so bRemoveAutoFilter must not be set from ref-undo!
+
+ ScDBCollection::NamedDBs& rNamedDBs = pDBCollection->getNamedDBs();
+ for (const auto& rxNamedDB : rNamedDBs)
+ {
+ const ScDBData& rOldData = *rxNamedDB;
+ if (!rOldData.HasAutoFilter())
+ continue;
+
+ ScRange aOldRange;
+ rOldData.GetArea(aOldRange);
+
+ bool bFound = false;
+ if (pNewDBCollection)
+ {
+ ScDBData* pNewData = pNewDBCollection->getNamedDBs().findByUpperName(rOldData.GetUpperName());
+ if (pNewData)
+ {
+ if (pNewData->HasAutoFilter())
+ {
+ ScRange aNewRange;
+ pNewData->GetArea(aNewRange);
+ if (aOldRange.aStart == aNewRange.aStart)
+ bFound = true;
+ }
+ }
+ }
+
+ if (!bFound)
+ {
+ aOldRange.aEnd.SetRow(aOldRange.aStart.Row());
+ RemoveFlagsTab( aOldRange.aStart.Col(), aOldRange.aStart.Row(),
+ aOldRange.aEnd.Col(), aOldRange.aEnd.Row(),
+ aOldRange.aStart.Tab(), ScMF::Auto );
+ RepaintRange( aOldRange );
+ }
+ }
+ }
+
+ pDBCollection = std::move(pNewDBCollection);
+}
+
+const ScDBData* ScDocument::GetDBAtCursor(SCCOL nCol, SCROW nRow, SCTAB nTab, ScDBDataPortion ePortion) const
+{
+ if (pDBCollection)
+ return pDBCollection->GetDBAtCursor(nCol, nRow, nTab, ePortion);
+ else
+ return nullptr;
+}
+
+ScDBData* ScDocument::GetDBAtCursor(SCCOL nCol, SCROW nRow, SCTAB nTab, ScDBDataPortion ePortion)
+{
+ if (pDBCollection)
+ return pDBCollection->GetDBAtCursor(nCol, nRow, nTab, ePortion);
+ else
+ return nullptr;
+}
+
+const ScDBData* ScDocument::GetDBAtArea(SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2) const
+{
+ if (pDBCollection)
+ return pDBCollection->GetDBAtArea(nTab, nCol1, nRow1, nCol2, nRow2);
+ else
+ return nullptr;
+}
+
+ScDBData* ScDocument::GetDBAtArea(SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2)
+{
+ if (pDBCollection)
+ return pDBCollection->GetDBAtArea(nTab, nCol1, nRow1, nCol2, nRow2);
+ else
+ return nullptr;
+}
+
+void ScDocument::RefreshDirtyTableColumnNames()
+{
+ if (pDBCollection)
+ pDBCollection->RefreshDirtyTableColumnNames();
+}
+
+bool ScDocument::HasPivotTable() const
+{
+ return pDPCollection && pDPCollection->GetCount();
+}
+
+ScDPCollection* ScDocument::GetDPCollection()
+{
+ if (!pDPCollection)
+ pDPCollection.reset( new ScDPCollection(*this) );
+ return pDPCollection.get();
+}
+
+const ScDPCollection* ScDocument::GetDPCollection() const
+{
+ return pDPCollection.get();
+}
+
+ScDPObject* ScDocument::GetDPAtCursor(SCCOL nCol, SCROW nRow, SCTAB nTab) const
+{
+ if (!pDPCollection)
+ return nullptr;
+
+ sal_uInt16 nCount = pDPCollection->GetCount();
+ ScAddress aPos( nCol, nRow, nTab );
+ for (sal_uInt16 i=0; i<nCount; i++)
+ if ( (*pDPCollection)[i].GetOutRange().Contains( aPos ) )
+ return &(*pDPCollection)[i];
+
+ return nullptr;
+}
+
+ScDPObject* ScDocument::GetDPAtBlock( const ScRange & rBlock ) const
+{
+ if (!pDPCollection)
+ return nullptr;
+
+ /* Walk the collection in reverse order to get something of an
+ * approximation of MS Excels 'most recent' effect. */
+ sal_uInt16 i = pDPCollection->GetCount();
+ while ( i-- > 0 )
+ if ( (*pDPCollection)[i].GetOutRange().Contains( rBlock ) )
+ return &(*pDPCollection)[i];
+
+ return nullptr;
+}
+
+void ScDocument::StopTemporaryChartLock()
+{
+ if (apTemporaryChartLock)
+ apTemporaryChartLock->StopLocking();
+}
+
+void ScDocument::SetChartListenerCollection(
+ std::unique_ptr<ScChartListenerCollection> pNewChartListenerCollection,
+ bool bSetChartRangeLists )
+{
+ std::unique_ptr<ScChartListenerCollection> pOld = std::move(pChartListenerCollection);
+ pChartListenerCollection = std::move(pNewChartListenerCollection);
+ if ( pChartListenerCollection )
+ {
+ if ( pOld )
+ pChartListenerCollection->SetDiffDirty( *pOld, bSetChartRangeLists );
+ pChartListenerCollection->StartAllListeners();
+ }
+}
+
+void ScDocument::SetScenario( SCTAB nTab, bool bFlag )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->SetScenario(bFlag);
+}
+
+bool ScDocument::IsScenario( SCTAB nTab ) const
+{
+ return ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] &&maTabs[nTab]->IsScenario();
+}
+
+void ScDocument::SetScenarioData( SCTAB nTab, const OUString& rComment,
+ const Color& rColor, ScScenarioFlags nFlags )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->IsScenario())
+ {
+ maTabs[nTab]->SetScenarioComment( rComment );
+ maTabs[nTab]->SetScenarioColor( rColor );
+ maTabs[nTab]->SetScenarioFlags( nFlags );
+ }
+}
+
+Color ScDocument::GetTabBgColor( SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetTabBgColor();
+ return COL_AUTO;
+}
+
+void ScDocument::SetTabBgColor( SCTAB nTab, const Color& rColor )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->SetTabBgColor(rColor);
+}
+
+bool ScDocument::IsDefaultTabBgColor( SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetTabBgColor() == COL_AUTO;
+ return true;
+}
+
+void ScDocument::GetScenarioData( SCTAB nTab, OUString& rComment,
+ Color& rColor, ScScenarioFlags& rFlags ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->IsScenario())
+ {
+ maTabs[nTab]->GetScenarioComment( rComment );
+ rColor = maTabs[nTab]->GetScenarioColor();
+ rFlags = maTabs[nTab]->GetScenarioFlags();
+ }
+}
+
+void ScDocument::GetScenarioFlags( SCTAB nTab, ScScenarioFlags& rFlags ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->IsScenario())
+ rFlags = maTabs[nTab]->GetScenarioFlags();
+}
+
+bool ScDocument::IsLinked( SCTAB nTab ) const
+{
+ return ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->IsLinked();
+ // equivalent to
+ //if (ValidTab(nTab) && pTab[nTab])
+ // return pTab[nTab]->IsLinked();
+ //return false;
+}
+
+formula::FormulaGrammar::AddressConvention ScDocument::GetAddressConvention() const
+{
+ return formula::FormulaGrammar::extractRefConvention(eGrammar);
+}
+
+void ScDocument::SetGrammar( formula::FormulaGrammar::Grammar eGram )
+{
+ eGrammar = eGram;
+}
+
+ScLinkMode ScDocument::GetLinkMode( SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetLinkMode();
+ return ScLinkMode::NONE;
+}
+
+OUString ScDocument::GetLinkDoc( SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetLinkDoc();
+ return OUString();
+}
+
+OUString ScDocument::GetLinkFlt( SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetLinkFlt();
+ return OUString();
+}
+
+OUString ScDocument::GetLinkOpt( SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetLinkOpt();
+ return OUString();
+}
+
+OUString ScDocument::GetLinkTab( SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetLinkTab();
+ return OUString();
+}
+
+sal_uLong ScDocument::GetLinkRefreshDelay( SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetLinkRefreshDelay();
+ return 0;
+}
+
+void ScDocument::SetLink( SCTAB nTab, ScLinkMode nMode, const OUString& rDoc,
+ const OUString& rFilter, const OUString& rOptions,
+ const OUString& rTabName, sal_uLong nRefreshDelay )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->SetLink( nMode, rDoc, rFilter, rOptions, rTabName, nRefreshDelay );
+}
+
+bool ScDocument::HasLink( std::u16string_view rDoc,
+ std::u16string_view rFilter, std::u16string_view rOptions ) const
+{
+ SCTAB nCount = static_cast<SCTAB>(maTabs.size());
+ for (SCTAB i=0; i<nCount; i++)
+ if (maTabs[i]->IsLinked()
+ && maTabs[i]->GetLinkDoc() == rDoc
+ && maTabs[i]->GetLinkFlt() == rFilter
+ && maTabs[i]->GetLinkOpt() == rOptions)
+ return true;
+
+ return false;
+}
+
+bool ScDocument::LinkExternalTab( SCTAB& rTab, const OUString& aDocTab,
+ const OUString& aFileName, const OUString& aTabName )
+{
+ if ( IsClipboard() )
+ {
+ OSL_FAIL( "LinkExternalTab in Clipboard" );
+ return false;
+ }
+ rTab = 0;
+#if ENABLE_FUZZERS
+ (void)aDocTab;
+ (void)aFileName;
+ (void)aTabName;
+ return false;
+#else
+ OUString aFilterName; // Is filled by the Loader
+ OUString aOptions; // Filter options
+ sal_uInt32 nLinkCnt = pExtDocOptions ? pExtDocOptions->GetDocSettings().mnLinkCnt : 0;
+ ScDocumentLoader aLoader( aFileName, aFilterName, aOptions, nLinkCnt + 1 );
+ if ( aLoader.IsError() )
+ return false;
+ ScDocument* pSrcDoc = aLoader.GetDocument();
+
+ // Copy table
+ SCTAB nSrcTab;
+ if ( pSrcDoc->GetTable( aTabName, nSrcTab ) )
+ {
+ if ( !InsertTab( SC_TAB_APPEND, aDocTab, true ) )
+ {
+ OSL_FAIL("can't insert external document table");
+ return false;
+ }
+ rTab = GetTableCount() - 1;
+ // Don't insert anew, just the results
+ TransferTab( *pSrcDoc, nSrcTab, rTab, false, true );
+ }
+ else
+ return false;
+
+ sal_uLong nRefreshDelay = 0;
+
+ bool bWasThere = HasLink( aFileName, aFilterName, aOptions );
+ SetLink( rTab, ScLinkMode::VALUE, aFileName, aFilterName, aOptions, aTabName, nRefreshDelay );
+ if ( !bWasThere ) // Add link only once per source document
+ {
+ ScTableLink* pLink = new ScTableLink( mpShell, aFileName, aFilterName, aOptions, nRefreshDelay );
+ pLink->SetInCreate( true );
+ OUString aFilName = aFilterName;
+ GetLinkManager()->InsertFileLink( *pLink, sfx2::SvBaseLinkObjectType::ClientFile, aFileName, &aFilName );
+ pLink->Update();
+ pLink->SetInCreate( false );
+ SfxBindings* pBindings = GetViewBindings();
+ if (pBindings)
+ pBindings->Invalidate( SID_LINKS );
+ }
+ return true;
+#endif
+}
+
+ScExternalRefManager* ScDocument::GetExternalRefManager() const
+{
+ ScDocument* pThis = const_cast<ScDocument*>(this);
+ if (!pExternalRefMgr)
+ pThis->pExternalRefMgr.reset( new ScExternalRefManager(*pThis));
+
+ return pExternalRefMgr.get();
+}
+
+bool ScDocument::IsInExternalReferenceMarking() const
+{
+ return pExternalRefMgr && pExternalRefMgr->isInReferenceMarking();
+}
+
+void ScDocument::MarkUsedExternalReferences()
+{
+ if (!pExternalRefMgr)
+ return;
+ if (!pExternalRefMgr->hasExternalData())
+ return;
+ // Charts.
+ pExternalRefMgr->markUsedByLinkListeners();
+ // Formula cells.
+ pExternalRefMgr->markUsedExternalRefCells();
+
+ /* NOTE: Conditional formats and validation objects are marked when
+ * collecting them during export. */
+}
+
+ScFormulaParserPool& ScDocument::GetFormulaParserPool() const
+{
+ if (!mxFormulaParserPool)
+ mxFormulaParserPool.reset( new ScFormulaParserPool( *this ) );
+ return *mxFormulaParserPool;
+}
+
+const ScSheetEvents* ScDocument::GetSheetEvents( SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetSheetEvents();
+ return nullptr;
+}
+
+void ScDocument::SetSheetEvents( SCTAB nTab, std::unique_ptr<ScSheetEvents> pNew )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->SetSheetEvents( std::move(pNew) );
+}
+
+bool ScDocument::HasSheetEventScript( SCTAB nTab, ScSheetEventId nEvent, bool bWithVbaEvents ) const
+{
+ if (nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ {
+ // check if any event handler script has been configured
+ const ScSheetEvents* pEvents = maTabs[nTab]->GetSheetEvents();
+ if ( pEvents && pEvents->GetScript( nEvent ) )
+ return true;
+ // check if VBA event handlers exist
+ if (bWithVbaEvents && mxVbaEvents.is()) try
+ {
+ uno::Sequence< uno::Any > aArgs{ uno::Any(nTab) };
+ if (mxVbaEvents->hasVbaEventHandler( ScSheetEvents::GetVbaSheetEventId( nEvent ), aArgs ) ||
+ mxVbaEvents->hasVbaEventHandler( ScSheetEvents::GetVbaDocumentEventId( nEvent ), uno::Sequence< uno::Any >() ))
+ return true;
+ }
+ catch( uno::Exception& )
+ {
+ }
+ }
+ return false;
+}
+
+bool ScDocument::HasAnySheetEventScript( ScSheetEventId nEvent, bool bWithVbaEvents ) const
+{
+ SCTAB nSize = static_cast<SCTAB>(maTabs.size());
+ for (SCTAB nTab = 0; nTab < nSize; nTab++)
+ if (HasSheetEventScript( nTab, nEvent, bWithVbaEvents ))
+ return true;
+ return false;
+}
+
+bool ScDocument::HasAnyCalcNotification() const
+{
+ SCTAB nSize = static_cast<SCTAB>(maTabs.size());
+ for (SCTAB nTab = 0; nTab < nSize; nTab++)
+ if (maTabs[nTab] && maTabs[nTab]->GetCalcNotification())
+ return true;
+ return false;
+}
+
+bool ScDocument::HasCalcNotification( SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetCalcNotification();
+ return false;
+}
+
+void ScDocument::SetCalcNotification( SCTAB nTab )
+{
+ // set only if not set before
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] && !maTabs[nTab]->GetCalcNotification())
+ maTabs[nTab]->SetCalcNotification(true);
+}
+
+void ScDocument::ResetCalcNotifications()
+{
+ SCTAB nSize = static_cast<SCTAB>(maTabs.size());
+ for (SCTAB nTab = 0; nTab < nSize; nTab++)
+ if (maTabs[nTab] && maTabs[nTab]->GetCalcNotification())
+ maTabs[nTab]->SetCalcNotification(false);
+}
+
+ScOutlineTable* ScDocument::GetOutlineTable( SCTAB nTab, bool bCreate )
+{
+ ScOutlineTable* pVal = nullptr;
+
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ if (maTabs[nTab])
+ {
+ pVal = maTabs[nTab]->GetOutlineTable();
+ if (!pVal && bCreate)
+ {
+ maTabs[nTab]->StartOutlineTable();
+ pVal = maTabs[nTab]->GetOutlineTable();
+ }
+ }
+
+ return pVal;
+}
+
+bool ScDocument::SetOutlineTable( SCTAB nTab, const ScOutlineTable* pNewOutline )
+{
+ return ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->SetOutlineTable(pNewOutline);
+}
+
+void ScDocument::DoAutoOutline( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow, SCTAB nTab )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->DoAutoOutline( nStartCol, nStartRow, nEndCol, nEndRow );
+}
+
+bool ScDocument::TestRemoveSubTotals( SCTAB nTab, const ScSubTotalParam& rParam )
+{
+ return ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->TestRemoveSubTotals( rParam );
+}
+
+void ScDocument::RemoveSubTotals( SCTAB nTab, ScSubTotalParam& rParam )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->RemoveSubTotals( rParam );
+}
+
+bool ScDocument::DoSubTotals( SCTAB nTab, ScSubTotalParam& rParam )
+{
+ return ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->DoSubTotals( rParam );
+}
+
+bool ScDocument::HasSubTotalCells( const ScRange& rRange )
+{
+ ScCellIterator aIter(*this, rRange);
+ for (bool bHas = aIter.first(); bHas; bHas = aIter.next())
+ {
+ if (aIter.getType() != CELLTYPE_FORMULA)
+ continue;
+
+ if (aIter.getFormulaCell()->IsSubTotal())
+ return true;
+ }
+ return false; // none found
+}
+
+/**
+ * From this document this method copies the cells of positions at which
+ * there are also cells in pPosDoc to pDestDoc
+ */
+void ScDocument::CopyUpdated( ScDocument* pPosDoc, ScDocument* pDestDoc )
+{
+ SCTAB nCount = static_cast<SCTAB>(maTabs.size());
+ for (SCTAB nTab=0; nTab<nCount; nTab++)
+ if (maTabs[nTab] && pPosDoc->maTabs[nTab] && pDestDoc->maTabs[nTab])
+ maTabs[nTab]->CopyUpdated( pPosDoc->maTabs[nTab].get(), pDestDoc->maTabs[nTab].get() );
+}
+
+void ScDocument::CopyScenario( SCTAB nSrcTab, SCTAB nDestTab, bool bNewScenario )
+{
+ if (!(ValidTab(nSrcTab) && ValidTab(nDestTab) && nSrcTab < static_cast<SCTAB>(maTabs.size())
+ && nDestTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nSrcTab] && maTabs[nDestTab]))
+ return;
+
+ // Set flags correctly for active scenarios
+ // and write current values back to recently active scenarios
+ ScRangeList aRanges = *maTabs[nSrcTab]->GetScenarioRanges();
+
+ // nDestTab is the target table
+ for ( SCTAB nTab = nDestTab+1;
+ nTab< static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->IsScenario();
+ nTab++ )
+ {
+ if ( maTabs[nTab]->IsActiveScenario() ) // Even if it's the same scenario
+ {
+ bool bTouched = false;
+ for ( size_t nR=0, nRangeCount = aRanges.size(); nR < nRangeCount && !bTouched; nR++ )
+ {
+ const ScRange& rRange = aRanges[ nR ];
+ if ( maTabs[nTab]->HasScenarioRange( rRange ) )
+ bTouched = true;
+ }
+ if (bTouched)
+ {
+ maTabs[nTab]->SetActiveScenario(false);
+ if ( maTabs[nTab]->GetScenarioFlags() & ScScenarioFlags::TwoWay )
+ maTabs[nTab]->CopyScenarioFrom( maTabs[nDestTab].get() );
+ }
+ }
+ }
+
+ maTabs[nSrcTab]->SetActiveScenario(true); // This is where it's from ...
+ if (!bNewScenario) // Copy data from the selected scenario
+ {
+ sc::AutoCalcSwitch aACSwitch(*this, false);
+ maTabs[nSrcTab]->CopyScenarioTo( maTabs[nDestTab].get() );
+
+ sc::SetFormulaDirtyContext aCxt;
+ SetAllFormulasDirty(aCxt);
+ }
+}
+
+void ScDocument::MarkScenario( SCTAB nSrcTab, SCTAB nDestTab, ScMarkData& rDestMark,
+ bool bResetMark, ScScenarioFlags nNeededBits ) const
+{
+ if (bResetMark)
+ rDestMark.ResetMark();
+
+ if (ValidTab(nSrcTab) && nSrcTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nSrcTab])
+ maTabs[nSrcTab]->MarkScenarioIn( rDestMark, nNeededBits );
+
+ rDestMark.SetAreaTab( nDestTab );
+}
+
+bool ScDocument::HasScenarioRange( SCTAB nTab, const ScRange& rRange ) const
+{
+ return ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->HasScenarioRange( rRange );
+}
+
+const ScRangeList* ScDocument::GetScenarioRanges( SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetScenarioRanges();
+
+ return nullptr;
+}
+
+bool ScDocument::IsActiveScenario( SCTAB nTab ) const
+{
+ return ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->IsActiveScenario( );
+}
+
+void ScDocument::SetActiveScenario( SCTAB nTab, bool bActive )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->SetActiveScenario( bActive );
+}
+
+bool ScDocument::TestCopyScenario( SCTAB nSrcTab, SCTAB nDestTab ) const
+{
+ if (ValidTab(nSrcTab) && nSrcTab < static_cast<SCTAB>(maTabs.size())
+ && nDestTab < static_cast<SCTAB>(maTabs.size())&& ValidTab(nDestTab))
+ return maTabs[nSrcTab]->TestCopyScenarioTo( maTabs[nDestTab].get() );
+
+ OSL_FAIL("wrong table at TestCopyScenario");
+ return false;
+}
+
+void ScDocument::AddUnoObject( SfxListener& rObject )
+{
+ if (!pUnoBroadcaster)
+ pUnoBroadcaster.reset( new SfxBroadcaster );
+
+ rObject.StartListening( *pUnoBroadcaster );
+}
+
+void ScDocument::RemoveUnoObject( SfxListener& rObject )
+{
+ if (pUnoBroadcaster)
+ {
+ rObject.EndListening( *pUnoBroadcaster );
+
+ if ( bInUnoBroadcast )
+ {
+ // Broadcasts from ScDocument::BroadcastUno are the only way that
+ // uno object methods are called without holding a reference.
+ //
+ // If RemoveUnoObject is called from an object dtor in the finalizer thread
+ // while the main thread is calling BroadcastUno, the dtor thread must wait
+ // (or the object's Notify might try to access a deleted object).
+ // The SolarMutex can't be locked here because if a component is called from
+ // a VCL event, the main thread has the SolarMutex locked all the time.
+ //
+ // This check is done after calling EndListening, so a later BroadcastUno call
+ // won't touch this object.
+
+ vcl::SolarMutexTryAndBuyGuard g;
+ if (g.isAcquired())
+ {
+ // BroadcastUno is always called with the SolarMutex locked, so if it
+ // can be acquired, this is within the same thread (should not happen)
+ OSL_FAIL( "RemoveUnoObject called from BroadcastUno" );
+ }
+ else
+ {
+ // Let the thread that called BroadcastUno continue
+ while ( bInUnoBroadcast )
+ {
+ osl::Thread::yield();
+ }
+ }
+ }
+ }
+ else
+ {
+ OSL_FAIL("No Uno broadcaster");
+ }
+}
+
+void ScDocument::BroadcastUno( const SfxHint &rHint )
+{
+ if (!pUnoBroadcaster)
+ return;
+
+ bInUnoBroadcast = true;
+ pUnoBroadcaster->Broadcast( rHint );
+ bInUnoBroadcast = false;
+
+ // During Broadcast notification, Uno objects can add to pUnoListenerCalls.
+ // The listener calls must be processed after completing the broadcast,
+ // because they can add or remove objects from pUnoBroadcaster.
+
+ if ( pUnoListenerCalls &&
+ rHint.GetId() == SfxHintId::DataChanged &&
+ !bInUnoListenerCall )
+ {
+ // Listener calls may lead to BroadcastUno calls again. The listener calls
+ // are not nested, instead the calls are collected in the list, and the
+ // outermost call executes them all.
+
+ ScChartLockGuard aChartLockGuard(this);
+ bInUnoListenerCall = true;
+ pUnoListenerCalls->ExecuteAndClear();
+ bInUnoListenerCall = false;
+ }
+}
+
+void ScDocument::AddUnoListenerCall( const uno::Reference<util::XModifyListener>& rListener,
+ const lang::EventObject& rEvent )
+{
+ OSL_ENSURE( bInUnoBroadcast, "AddUnoListenerCall is supposed to be called from BroadcastUno only" );
+
+ if ( !pUnoListenerCalls )
+ pUnoListenerCalls.reset( new ScUnoListenerCalls );
+ pUnoListenerCalls->Add( rListener, rEvent );
+}
+
+void ScDocument::BeginUnoRefUndo()
+{
+ OSL_ENSURE( !pUnoRefUndoList, "BeginUnoRefUndo twice" );
+ pUnoRefUndoList.reset( new ScUnoRefList );
+}
+
+std::unique_ptr<ScUnoRefList> ScDocument::EndUnoRefUndo()
+{
+ return std::move(pUnoRefUndoList);
+ // Must be deleted by caller!
+}
+
+void ScDocument::AddUnoRefChange( sal_Int64 nId, const ScRangeList& rOldRanges )
+{
+ if ( pUnoRefUndoList )
+ pUnoRefUndoList->Add( nId, rOldRanges );
+}
+
+void ScDocument::UpdateReference(
+ sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc, bool bIncludeDraw, bool bUpdateNoteCaptionPos )
+{
+ if (!ValidRange(rCxt.maRange) && !(rCxt.meMode == URM_INSDEL &&
+ ((rCxt.mnColDelta < 0 && // convention from ScDocument::DeleteCol()
+ rCxt.maRange.aStart.Col() == GetMaxColCount() && rCxt.maRange.aEnd.Col() == GetMaxColCount()) ||
+ (rCxt.mnRowDelta < 0 && // convention from ScDocument::DeleteRow()
+ rCxt.maRange.aStart.Row() == GetMaxRowCount() && rCxt.maRange.aEnd.Row() == GetMaxRowCount()))))
+ return;
+
+ std::unique_ptr<sc::ExpandRefsSwitch> pExpandRefsSwitch;
+ if (rCxt.isInserted())
+ pExpandRefsSwitch.reset(new sc::ExpandRefsSwitch(*this, SC_MOD()->GetInputOptions().GetExpandRefs()));
+
+ size_t nFirstTab, nLastTab;
+ if (rCxt.meMode == URM_COPY)
+ {
+ nFirstTab = rCxt.maRange.aStart.Tab();
+ nLastTab = rCxt.maRange.aEnd.Tab();
+ }
+ else
+ {
+ // TODO: Have these methods use the context object directly.
+ ScRange aRange = rCxt.maRange;
+ UpdateRefMode eUpdateRefMode = rCxt.meMode;
+ SCCOL nDx = rCxt.mnColDelta;
+ SCROW nDy = rCxt.mnRowDelta;
+ SCTAB nDz = rCxt.mnTabDelta;
+ SCCOL nCol1 = rCxt.maRange.aStart.Col(), nCol2 = rCxt.maRange.aEnd.Col();
+ SCROW nRow1 = rCxt.maRange.aStart.Row(), nRow2 = rCxt.maRange.aEnd.Row();
+ SCTAB nTab1 = rCxt.maRange.aStart.Tab(), nTab2 = rCxt.maRange.aEnd.Tab();
+
+ xColNameRanges->UpdateReference( eUpdateRefMode, this, aRange, nDx, nDy, nDz );
+ xRowNameRanges->UpdateReference( eUpdateRefMode, this, aRange, nDx, nDy, nDz );
+ pDBCollection->UpdateReference( eUpdateRefMode, nCol1, nRow1, nTab1, nCol2, nRow2, nTab2, nDx, nDy, nDz );
+ if (pRangeName)
+ pRangeName->UpdateReference(rCxt);
+ if ( pDPCollection )
+ pDPCollection->UpdateReference( eUpdateRefMode, aRange, nDx, nDy, nDz );
+ UpdateChartRef( eUpdateRefMode, nCol1, nRow1, nTab1, nCol2, nRow2, nTab2, nDx, nDy, nDz );
+ UpdateRefAreaLinks( eUpdateRefMode, aRange, nDx, nDy, nDz );
+ if ( pValidationList )
+ {
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ pValidationList->UpdateReference(rCxt);
+ }
+ if ( pDetOpList )
+ pDetOpList->UpdateReference( this, eUpdateRefMode, aRange, nDx, nDy, nDz );
+ if ( pUnoBroadcaster )
+ pUnoBroadcaster->Broadcast( ScUpdateRefHint(
+ eUpdateRefMode, aRange, nDx, nDy, nDz ) );
+
+ nFirstTab = 0;
+ nLastTab = maTabs.size()-1;
+ }
+
+ for (size_t i = nFirstTab, n = maTabs.size() ; i <= nLastTab && i < n; ++i)
+ {
+ if (!maTabs[i])
+ continue;
+
+ maTabs[i]->UpdateReference(rCxt, pUndoDoc, bIncludeDraw, bUpdateNoteCaptionPos);
+ }
+
+ if ( bIsEmbedded )
+ {
+ SCCOL theCol1;
+ SCROW theRow1;
+ SCTAB theTab1;
+ SCCOL theCol2;
+ SCROW theRow2;
+ SCTAB theTab2;
+ theCol1 = aEmbedRange.aStart.Col();
+ theRow1 = aEmbedRange.aStart.Row();
+ theTab1 = aEmbedRange.aStart.Tab();
+ theCol2 = aEmbedRange.aEnd.Col();
+ theRow2 = aEmbedRange.aEnd.Row();
+ theTab2 = aEmbedRange.aEnd.Tab();
+
+ // TODO: Have ScRefUpdate::Update() use the context object directly.
+ UpdateRefMode eUpdateRefMode = rCxt.meMode;
+ SCCOL nDx = rCxt.mnColDelta;
+ SCROW nDy = rCxt.mnRowDelta;
+ SCTAB nDz = rCxt.mnTabDelta;
+ SCCOL nCol1 = rCxt.maRange.aStart.Col(), nCol2 = rCxt.maRange.aEnd.Col();
+ SCROW nRow1 = rCxt.maRange.aStart.Row(), nRow2 = rCxt.maRange.aEnd.Row();
+ SCTAB nTab1 = rCxt.maRange.aStart.Tab(), nTab2 = rCxt.maRange.aEnd.Tab();
+
+ if ( ScRefUpdate::Update( this, eUpdateRefMode, nCol1,nRow1,nTab1, nCol2,nRow2,nTab2,
+ nDx,nDy,nDz, theCol1,theRow1,theTab1, theCol2,theRow2,theTab2 ) )
+ {
+ aEmbedRange = ScRange( theCol1,theRow1,theTab1, theCol2,theRow2,theTab2 );
+ }
+ }
+
+ // After moving, no clipboard move ref-updates are possible
+ if (rCxt.meMode != URM_COPY && IsClipboardSource())
+ {
+ ScDocument* pClipDoc = ScModule::GetClipDoc();
+ if (pClipDoc)
+ pClipDoc->GetClipParam().mbCutMode = false;
+ }
+}
+
+void ScDocument::UpdateTranspose( const ScAddress& rDestPos, ScDocument* pClipDoc,
+ const ScMarkData& rMark, ScDocument* pUndoDoc )
+{
+ OSL_ENSURE(pClipDoc->bIsClip, "UpdateTranspose: No Clip");
+
+ ScRange aSource;
+ ScClipParam& rClipParam = pClipDoc->GetClipParam();
+ if (!rClipParam.maRanges.empty())
+ aSource = rClipParam.maRanges.front();
+ ScAddress aDest = rDestPos;
+
+ SCTAB nClipTab = 0;
+ for (SCTAB nDestTab=0; nDestTab< static_cast<SCTAB>(maTabs.size()) && maTabs[nDestTab]; nDestTab++)
+ if (rMark.GetTableSelect(nDestTab))
+ {
+ while (!pClipDoc->maTabs[nClipTab]) nClipTab = (nClipTab+1) % (MAXTAB+1);
+ aSource.aStart.SetTab( nClipTab );
+ aSource.aEnd.SetTab( nClipTab );
+ aDest.SetTab( nDestTab );
+
+ // Like UpdateReference
+ if (pRangeName)
+ pRangeName->UpdateTranspose( aSource, aDest ); // Before the cells!
+ for (SCTAB i=0; i< static_cast<SCTAB>(maTabs.size()); i++)
+ if (maTabs[i])
+ maTabs[i]->UpdateTranspose( aSource, aDest, pUndoDoc );
+
+ nClipTab = (nClipTab+1) % (MAXTAB+1);
+ }
+}
+
+void ScDocument::UpdateGrow( const ScRange& rArea, SCCOL nGrowX, SCROW nGrowY )
+{
+ //TODO: pDBCollection
+ //TODO: pPivotCollection
+ //TODO: UpdateChartRef
+
+ if (pRangeName)
+ pRangeName->UpdateGrow( rArea, nGrowX, nGrowY );
+
+ for (SCTAB i=0; i< static_cast<SCTAB>(maTabs.size()) && maTabs[i]; i++)
+ maTabs[i]->UpdateGrow( rArea, nGrowX, nGrowY );
+}
+
+void ScDocument::Fill(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, ScProgress* pProgress, const ScMarkData& rMark,
+ sal_uInt64 nFillCount, FillDir eFillDir, FillCmd eFillCmd, FillDateCmd eFillDateCmd,
+ double nStepValue, double nMaxValue)
+{
+ PutInOrder( nCol1, nCol2 );
+ PutInOrder( nRow1, nRow2 );
+ const ScRange& aRange = rMark.GetMarkArea();
+ SCTAB nMax = maTabs.size();
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+ if (maTabs[rTab])
+ {
+ maTabs[rTab]->Fill(nCol1, nRow1, nCol2, nRow2,
+ nFillCount, eFillDir, eFillCmd, eFillDateCmd,
+ nStepValue, nMaxValue, pProgress);
+ RefreshAutoFilter(aRange.aStart.Col(), aRange.aStart.Row(), aRange.aEnd.Col(), aRange.aEnd.Row(), rTab);
+ }
+ }
+}
+
+OUString ScDocument::GetAutoFillPreview( const ScRange& rSource, SCCOL nEndX, SCROW nEndY )
+{
+ SCTAB nTab = rSource.aStart.Tab();
+ if (nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetAutoFillPreview( rSource, nEndX, nEndY );
+
+ return OUString();
+}
+
+void ScDocument::AutoFormat( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow,
+ sal_uInt16 nFormatNo, const ScMarkData& rMark )
+{
+ PutInOrder( nStartCol, nEndCol );
+ PutInOrder( nStartRow, nEndRow );
+ SCTAB nMax = maTabs.size();
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+ if (maTabs[rTab])
+ maTabs[rTab]->AutoFormat( nStartCol, nStartRow, nEndCol, nEndRow, nFormatNo );
+ }
+}
+
+void ScDocument::GetAutoFormatData(SCTAB nTab, SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow,
+ ScAutoFormatData& rData)
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ {
+ if (maTabs[nTab])
+ {
+ PutInOrder(nStartCol, nEndCol);
+ PutInOrder(nStartRow, nEndRow);
+ maTabs[nTab]->GetAutoFormatData(nStartCol, nStartRow, nEndCol, nEndRow, rData);
+ }
+ }
+}
+
+void ScDocument::GetSearchAndReplaceStart( const SvxSearchItem& rSearchItem,
+ SCCOL& rCol, SCROW& rRow )
+{
+ SvxSearchCmd nCommand = rSearchItem.GetCommand();
+ bool bReplace = ( nCommand == SvxSearchCmd::REPLACE ||
+ nCommand == SvxSearchCmd::REPLACE_ALL );
+ if ( rSearchItem.GetBackward() )
+ {
+ if ( rSearchItem.GetRowDirection() )
+ {
+ if ( rSearchItem.GetPattern() )
+ {
+ rCol = MaxCol();
+ rRow = MaxRow()+1;
+ }
+ else if ( bReplace )
+ {
+ rCol = MaxCol();
+ rRow = MaxRow();
+ }
+ else
+ {
+ rCol = MaxCol()+1;
+ rRow = MaxRow();
+ }
+ }
+ else
+ {
+ if ( rSearchItem.GetPattern() )
+ {
+ rCol = MaxCol()+1;
+ rRow = MaxRow();
+ }
+ else if ( bReplace )
+ {
+ rCol = MaxCol();
+ rRow = MaxRow();
+ }
+ else
+ {
+ rCol = MaxCol();
+ rRow = MaxRow()+1;
+ }
+ }
+ }
+ else
+ {
+ if ( rSearchItem.GetRowDirection() )
+ {
+ if ( rSearchItem.GetPattern() )
+ {
+ rCol = 0;
+ rRow = SCROW(-1);
+ }
+ else if ( bReplace )
+ {
+ rCol = 0;
+ rRow = 0;
+ }
+ else
+ {
+ rCol = SCCOL(-1);
+ rRow = 0;
+ }
+ }
+ else
+ {
+ if ( rSearchItem.GetPattern() )
+ {
+ rCol = SCCOL(-1);
+ rRow = 0;
+ }
+ else if ( bReplace )
+ {
+ rCol = 0;
+ rRow = 0;
+ }
+ else
+ {
+ rCol = 0;
+ rRow = SCROW(-1);
+ }
+ }
+ }
+}
+
+// static
+bool ScDocument::IsEmptyCellSearch( const SvxSearchItem& rSearchItem )
+{
+ return !rSearchItem.GetPattern() && (rSearchItem.GetCellType() != SvxSearchCellType::NOTE)
+ && (rSearchItem.GetSearchOptions().searchString.isEmpty()
+ || (rSearchItem.GetRegExp() && rSearchItem.GetSearchOptions().searchString == "^$"));
+}
+
+bool ScDocument::SearchAndReplace(
+ const SvxSearchItem& rSearchItem, SCCOL& rCol, SCROW& rRow, SCTAB& rTab,
+ const ScMarkData& rMark, ScRangeList& rMatchedRanges,
+ OUString& rUndoStr, ScDocument* pUndoDoc)
+{
+ // FIXME: Manage separated marks per table!
+ bool bFound = false;
+ if (rTab >= static_cast<SCTAB>(maTabs.size()))
+ OSL_FAIL("table out of range");
+ if (ValidTab(rTab))
+ {
+ SCCOL nCol;
+ SCROW nRow;
+ SCTAB nTab;
+ SvxSearchCmd nCommand = rSearchItem.GetCommand();
+ if ( nCommand == SvxSearchCmd::FIND_ALL ||
+ nCommand == SvxSearchCmd::REPLACE_ALL )
+ {
+ SCTAB nMax = maTabs.size();
+ for (const auto& rMarkedTab : rMark)
+ {
+ if (rMarkedTab >= nMax)
+ break;
+ if (maTabs[rMarkedTab])
+ {
+ nCol = 0;
+ nRow = 0;
+ bFound |= maTabs[rMarkedTab]->SearchAndReplace(
+ rSearchItem, nCol, nRow, rMark, rMatchedRanges, rUndoStr, pUndoDoc);
+ }
+ }
+
+ // Mark is set completely inside already
+ }
+ else
+ {
+ nCol = rCol;
+ nRow = rRow;
+ if (rSearchItem.GetBackward())
+ {
+ for (nTab = rTab; (nTab >= 0) && !bFound; nTab--)
+ if (maTabs[nTab])
+ {
+ if (rMark.GetTableSelect(nTab))
+ {
+ bFound = maTabs[nTab]->SearchAndReplace(
+ rSearchItem, nCol, nRow, rMark, rMatchedRanges, rUndoStr, pUndoDoc);
+ if (bFound)
+ {
+ rCol = nCol;
+ rRow = nRow;
+ rTab = nTab;
+ }
+ else
+ {
+ ScDocument::GetSearchAndReplaceStart(
+ rSearchItem, nCol, nRow );
+
+ // notify LibreOfficeKit about changed page
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ OString aPayload = OString::number(nTab);
+ if (SfxViewShell* pViewShell = SfxViewShell::Current())
+ pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_SET_PART, aPayload.getStr());
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ for (nTab = rTab; (nTab < static_cast<SCTAB>(maTabs.size())) && !bFound; nTab++)
+ if (maTabs[nTab])
+ {
+ if (rMark.GetTableSelect(nTab))
+ {
+ bFound = maTabs[nTab]->SearchAndReplace(
+ rSearchItem, nCol, nRow, rMark, rMatchedRanges, rUndoStr, pUndoDoc);
+ if (bFound)
+ {
+ rCol = nCol;
+ rRow = nRow;
+ rTab = nTab;
+ }
+ else
+ {
+ ScDocument::GetSearchAndReplaceStart(
+ rSearchItem, nCol, nRow );
+
+ // notify LibreOfficeKit about changed page
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ OString aPayload = OString::number(nTab);
+ if(SfxViewShell* pViewShell = SfxViewShell::Current())
+ pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_SET_PART, aPayload.getStr());
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return bFound;
+}
+
+/**
+ * Adapt Outline
+ */
+bool ScDocument::UpdateOutlineCol( SCCOL nStartCol, SCCOL nEndCol, SCTAB nTab, bool bShow )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->UpdateOutlineCol( nStartCol, nEndCol, bShow );
+
+ OSL_FAIL("missing tab");
+ return false;
+}
+
+bool ScDocument::UpdateOutlineRow( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bShow )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->UpdateOutlineRow( nStartRow, nEndRow, bShow );
+
+ OSL_FAIL("missing tab");
+ return false;
+}
+
+void ScDocument::Sort(
+ SCTAB nTab, const ScSortParam& rSortParam, bool bKeepQuery, bool bUpdateRefs,
+ ScProgress* pProgress, sc::ReorderParam* pUndo )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ {
+ bool bOldEnableIdle = IsIdleEnabled();
+ EnableIdle(false);
+ maTabs[nTab]->Sort(rSortParam, bKeepQuery, bUpdateRefs, pProgress, pUndo);
+ EnableIdle(bOldEnableIdle);
+ }
+}
+
+void ScDocument::Reorder( const sc::ReorderParam& rParam )
+{
+ ScTable* pTab = FetchTable(rParam.maSortRange.aStart.Tab());
+ if (!pTab)
+ return;
+
+ bool bOldEnableIdle = IsIdleEnabled();
+ EnableIdle(false);
+ pTab->Reorder(rParam);
+ EnableIdle(bOldEnableIdle);
+}
+
+void ScDocument::PrepareQuery( SCTAB nTab, ScQueryParam& rQueryParam )
+{
+ if( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->PrepareQuery(rQueryParam);
+ else
+ {
+ OSL_FAIL("missing tab");
+ return;
+ }
+}
+
+SCSIZE ScDocument::Query(SCTAB nTab, const ScQueryParam& rQueryParam, bool bKeepSub)
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->Query(rQueryParam, bKeepSub);
+
+ OSL_FAIL("missing tab");
+ return 0;
+}
+
+OUString ScDocument::GetUpperCellString(SCCOL nCol, SCROW nRow, SCTAB nTab)
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetUpperCellString( nCol, nRow );
+ else
+ return OUString();
+}
+
+bool ScDocument::CreateQueryParam( const ScRange& rRange, ScQueryParam& rQueryParam )
+{
+ ScTable* pTab = FetchTable(rRange.aStart.Tab());
+ if (!pTab)
+ {
+ OSL_FAIL("missing tab");
+ return false;
+ }
+
+ return pTab->CreateQueryParam(
+ rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row(), rQueryParam);
+}
+
+bool ScDocument::HasAutoFilter( SCCOL nCurCol, SCROW nCurRow, SCTAB nCurTab )
+{
+ const ScDBData* pDBData = GetDBAtCursor( nCurCol, nCurRow, nCurTab, ScDBDataPortion::AREA );
+ bool bHasAutoFilter = (pDBData != nullptr);
+
+ if ( pDBData )
+ {
+ if ( pDBData->HasHeader() )
+ {
+ SCCOL nCol;
+ SCROW nRow;
+ ScMF nFlag;
+
+ ScQueryParam aParam;
+ pDBData->GetQueryParam( aParam );
+ nRow = aParam.nRow1;
+
+ for ( nCol=aParam.nCol1; nCol<=aParam.nCol2 && bHasAutoFilter; nCol++ )
+ {
+ nFlag = GetAttr( nCol, nRow, nCurTab, ATTR_MERGE_FLAG )->GetValue();
+
+ if ( !(nFlag & ScMF::Auto) )
+ bHasAutoFilter = false;
+ }
+ }
+ else
+ bHasAutoFilter = false;
+ }
+
+ return bHasAutoFilter;
+}
+
+bool ScDocument::HasColHeader( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow,
+ SCTAB nTab )
+{
+ return ValidTab(nTab) && maTabs[nTab] && maTabs[nTab]->HasColHeader( nStartCol, nStartRow, nEndCol, nEndRow );
+}
+
+bool ScDocument::HasRowHeader( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow,
+ SCTAB nTab )
+{
+ return ValidTab(nTab) && maTabs[nTab] && maTabs[nTab]->HasRowHeader( nStartCol, nStartRow, nEndCol, nEndRow );
+}
+
+void ScDocument::GetFilterSelCount( SCCOL nCol, SCROW nRow, SCTAB nTab, SCSIZE& nSelected, SCSIZE& nTotal )
+{
+ nSelected = 0;
+ nTotal = 0;
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ {
+ ScDBData* pDBData = GetDBAtCursor( nCol, nRow, nTab, ScDBDataPortion::AREA );
+ if( pDBData && pDBData->HasAutoFilter() )
+ pDBData->GetFilterSelCount( nSelected, nTotal );
+ }
+}
+
+/**
+ * Entries for AutoFilter listbox
+ */
+void ScDocument::GetFilterEntries(
+ SCCOL nCol, SCROW nRow, SCTAB nTab, ScFilterEntries& rFilterEntries )
+{
+ if ( !(ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] && pDBCollection) )
+ return;
+
+ ScDBData* pDBData = pDBCollection->GetDBAtCursor(nCol, nRow, nTab, ScDBDataPortion::AREA); //!??
+ if (!pDBData)
+ return;
+
+ pDBData->ExtendDataArea(*this);
+ SCTAB nAreaTab;
+ SCCOL nStartCol;
+ SCROW nStartRow;
+ SCCOL nEndCol;
+ SCROW nEndRow;
+ pDBData->GetArea( nAreaTab, nStartCol, nStartRow, nEndCol, nEndRow );
+
+ if (pDBData->HasHeader())
+ ++nStartRow;
+
+ ScQueryParam aParam;
+ pDBData->GetQueryParam( aParam );
+
+ // Return all filter entries, if a filter condition is connected with a boolean OR
+ bool bFilter = true;
+ SCSIZE nEntryCount = aParam.GetEntryCount();
+ for ( SCSIZE i = 0; i < nEntryCount && aParam.GetEntry(i).bDoQuery; ++i )
+ {
+ ScQueryEntry& rEntry = aParam.GetEntry(i);
+ if ( rEntry.eConnect != SC_AND )
+ {
+ bFilter = false;
+ break;
+ }
+ }
+
+ if ( bFilter )
+ {
+ maTabs[nTab]->GetFilteredFilterEntries( nCol, nStartRow, nEndRow, aParam, rFilterEntries, bFilter );
+ }
+ else
+ {
+ maTabs[nTab]->GetFilterEntries( nCol, nStartRow, nEndRow, rFilterEntries );
+ }
+
+ sortAndRemoveDuplicates( rFilterEntries.maStrData, aParam.bCaseSens);
+}
+
+/**
+ * Entries for Filter dialog
+ */
+void ScDocument::GetFilterEntriesArea(
+ SCCOL nCol, SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bCaseSens,
+ ScFilterEntries& rFilterEntries )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ {
+ maTabs[nTab]->GetFilterEntries( nCol, nStartRow, nEndRow, rFilterEntries, true );
+ sortAndRemoveDuplicates( rFilterEntries.maStrData, bCaseSens);
+ }
+}
+
+/**
+ * Entries for selection list listbox (no numbers/formulas)
+ */
+void ScDocument::GetDataEntries(
+ SCCOL nCol, SCROW nRow, SCTAB nTab,
+ std::vector<ScTypedStrData>& rStrings, bool bValidation )
+{
+ if( bValidation )
+ {
+ /* Try to generate the list from list validation. This part is skipped,
+ if bValidation==false, because in that case this function is called to get
+ cell values for auto completion on input. */
+ sal_uInt32 nValidation = GetAttr( nCol, nRow, nTab, ATTR_VALIDDATA )->GetValue();
+ if( nValidation )
+ {
+ const ScValidationData* pData = GetValidationEntry( nValidation );
+ if( pData && pData->FillSelectionList( rStrings, ScAddress( nCol, nRow, nTab ) ) )
+ {
+ if (pData->GetListType() == css::sheet::TableValidationVisibility::SORTEDASCENDING)
+ sortAndRemoveDuplicates(rStrings, true/*bCaseSens*/);
+
+ return;
+ }
+ }
+ }
+
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()))
+ return;
+
+ if (!maTabs[nTab])
+ return;
+
+ std::set<ScTypedStrData> aStrings;
+ if (maTabs[nTab]->GetDataEntries(nCol, nRow, aStrings))
+ {
+ rStrings.insert(rStrings.end(), aStrings.begin(), aStrings.end());
+ sortAndRemoveDuplicates(rStrings, true/*bCaseSens*/);
+ }
+}
+
+/**
+ * Entries for Formula auto input
+ */
+void ScDocument::GetFormulaEntries( ScTypedCaseStrSet& rStrings )
+{
+
+ // Range name
+ if ( pRangeName )
+ {
+ for (const auto& rEntry : *pRangeName)
+ rStrings.insert(ScTypedStrData(rEntry.second->GetName(), 0.0, 0.0, ScTypedStrData::Name));
+ }
+
+ // Database collection
+ if ( pDBCollection )
+ {
+ const ScDBCollection::NamedDBs& rDBs = pDBCollection->getNamedDBs();
+ for (const auto& rxDB : rDBs)
+ rStrings.insert(ScTypedStrData(rxDB->GetName(), 0.0, 0.0, ScTypedStrData::DbName));
+ }
+
+ // Content of name ranges
+ ScRangePairList* pLists[2];
+ pLists[0] = GetColNameRanges();
+ pLists[1] = GetRowNameRanges();
+ for (ScRangePairList* pList : pLists)
+ {
+ if (!pList)
+ continue;
+
+ for ( size_t i = 0, nPairs = pList->size(); i < nPairs; ++i )
+ {
+ const ScRangePair & rPair = (*pList)[i];
+ const ScRange & rRange = rPair.GetRange(0);
+ ScCellIterator aIter( *this, rRange );
+ for (bool bHas = aIter.first(); bHas; bHas = aIter.next())
+ {
+ if (!aIter.hasString())
+ continue;
+
+ OUString aStr = aIter.getString();
+ rStrings.insert(ScTypedStrData(aStr, 0.0, 0.0, ScTypedStrData::Header));
+ }
+ }
+ }
+}
+
+void ScDocument::GetEmbedded( ScRange& rRange ) const
+{
+ rRange = aEmbedRange;
+}
+
+tools::Rectangle ScDocument::GetEmbeddedRect() const // 1/100 mm
+{
+ tools::Rectangle aRect;
+ ScTable* pTable = nullptr;
+ if ( aEmbedRange.aStart.Tab() < static_cast<SCTAB>(maTabs.size()) )
+ pTable = maTabs[aEmbedRange.aStart.Tab()].get();
+ else
+ OSL_FAIL("table out of range");
+ if (!pTable)
+ {
+ OSL_FAIL("GetEmbeddedRect without a table");
+ }
+ else
+ {
+ SCCOL i;
+
+ for (i=0; i<aEmbedRange.aStart.Col(); i++)
+ aRect.AdjustLeft(pTable->GetColWidth(i) );
+ aRect.AdjustTop(pTable->GetRowHeight( 0, aEmbedRange.aStart.Row() - 1) );
+ aRect.SetRight( aRect.Left() );
+ for (i=aEmbedRange.aStart.Col(); i<=aEmbedRange.aEnd.Col(); i++)
+ aRect.AdjustRight(pTable->GetColWidth(i) );
+ aRect.SetBottom( aRect.Top() );
+ aRect.AdjustBottom(pTable->GetRowHeight( aEmbedRange.aStart.Row(), aEmbedRange.aEnd.Row()) );
+
+ aRect = o3tl::convert(aRect, o3tl::Length::twip, o3tl::Length::mm100);
+ }
+ return aRect;
+}
+
+void ScDocument::SetEmbedded( const ScRange& rRange )
+{
+ bIsEmbedded = true;
+ aEmbedRange = rRange;
+}
+
+void ScDocument::ResetEmbedded()
+{
+ bIsEmbedded = false;
+ aEmbedRange = ScRange();
+}
+
+/** Similar to ScViewData::AddPixelsWhile(), but add height twips and only
+ while result is less than nStopTwips.
+ @return true if advanced at least one row.
+ */
+static bool lcl_AddTwipsWhile( tools::Long & rTwips, tools::Long nStopTwips, SCROW & rPosY, SCROW nEndRow, const ScTable * pTable, bool bHiddenAsZero )
+{
+ SCROW nRow = rPosY;
+ bool bAdded = false;
+ bool bStop = false;
+ while (rTwips < nStopTwips && nRow <= nEndRow && !bStop)
+ {
+ SCROW nHeightEndRow;
+ sal_uInt16 nHeight = pTable->GetRowHeight( nRow, nullptr, &nHeightEndRow, bHiddenAsZero );
+ if (nHeightEndRow > nEndRow)
+ nHeightEndRow = nEndRow;
+ if (!nHeight)
+ nRow = nHeightEndRow + 1;
+ else
+ {
+ SCROW nRows = nHeightEndRow - nRow + 1;
+ sal_Int64 nAdd = static_cast<sal_Int64>(nHeight) * nRows;
+ if (nAdd + rTwips >= nStopTwips)
+ {
+ sal_Int64 nDiff = nAdd + rTwips - nStopTwips;
+ nRows -= static_cast<SCROW>(nDiff / nHeight);
+ nAdd = static_cast<sal_Int64>(nHeight) * nRows;
+ // We're looking for a value that satisfies loop condition.
+ if (nAdd + rTwips >= nStopTwips)
+ {
+ --nRows;
+ nAdd -= nHeight;
+ }
+ bStop = true;
+ }
+ rTwips += static_cast<tools::Long>(nAdd);
+ nRow += nRows;
+ }
+ }
+ if (nRow > rPosY)
+ {
+ --nRow;
+ bAdded = true;
+ }
+ rPosY = nRow;
+ return bAdded;
+}
+
+ScRange ScDocument::GetRange( SCTAB nTab, const tools::Rectangle& rMMRect, bool bHiddenAsZero ) const
+{
+ ScTable* pTable = nullptr;
+ if (nTab < static_cast<SCTAB>(maTabs.size()))
+ pTable = maTabs[nTab].get();
+ else
+ OSL_FAIL("table out of range");
+ if (!pTable)
+ {
+ OSL_FAIL("GetRange without a table");
+ return ScRange();
+ }
+
+ tools::Rectangle aPosRect = o3tl::convert(rMMRect, o3tl::Length::mm100, o3tl::Length::twip);
+ if ( IsNegativePage( nTab ) )
+ ScDrawLayer::MirrorRectRTL( aPosRect ); // Always with positive (LTR) values
+
+ tools::Long nSize;
+ tools::Long nTwips;
+ tools::Long nAdd;
+ bool bEnd;
+
+ nSize = 0;
+ nTwips = aPosRect.Left();
+
+ SCCOL nX1 = 0;
+ bEnd = false;
+ while (!bEnd)
+ {
+ nAdd = pTable->GetColWidth(nX1, bHiddenAsZero);
+ if (nSize+nAdd <= nTwips+1 && nX1<MaxCol())
+ {
+ nSize += nAdd;
+ ++nX1;
+ }
+ else
+ bEnd = true;
+ }
+
+
+ SCCOL nX2 = nX1;
+ if (!aPosRect.IsEmpty())
+ {
+ bEnd = false;
+ nTwips = aPosRect.Right();
+ while (!bEnd)
+ {
+ nAdd = pTable->GetColWidth(nX2, bHiddenAsZero);
+ if (nSize+nAdd < nTwips && nX2<MaxCol())
+ {
+ nSize += nAdd;
+ ++nX2;
+ }
+ else
+ bEnd = true;
+ }
+ }
+
+ nSize = 0;
+ nTwips = aPosRect.Top();
+
+ SCROW nY1 = 0;
+ // Was if(nSize+nAdd<=nTwips+1) inside loop => if(nSize+nAdd<nTwips+2)
+ if (lcl_AddTwipsWhile( nSize, nTwips+2, nY1, MaxRow(), pTable, bHiddenAsZero) && nY1 < MaxRow())
+ ++nY1; // original loop ended on last matched +1 unless that was rDoc.MaxRow()
+
+ SCROW nY2 = nY1;
+ if (!aPosRect.IsEmpty())
+ {
+ nTwips = aPosRect.Bottom();
+ // Was if(nSize+nAdd<nTwips) inside loop => if(nSize+nAdd<nTwips)
+ if (lcl_AddTwipsWhile( nSize, nTwips, nY2, MaxRow(), pTable, bHiddenAsZero) && nY2 < MaxRow())
+ ++nY2; // original loop ended on last matched +1 unless that was rDoc.MaxRow()
+ }
+
+ return ScRange( nX1,nY1,nTab, nX2,nY2,nTab );
+}
+
+void ScDocument::SetEmbedded( SCTAB nTab, const tools::Rectangle& rRect ) // From VisArea (1/100 mm)
+{
+ bIsEmbedded = true;
+ aEmbedRange = GetRange( nTab, rRect );
+}
+
+ScDocProtection* ScDocument::GetDocProtection() const
+{
+ return pDocProtection.get();
+}
+
+void ScDocument::SetDocProtection(const ScDocProtection* pProtect)
+{
+ if (pProtect)
+ pDocProtection.reset(new ScDocProtection(*pProtect));
+ else
+ pDocProtection.reset();
+}
+
+bool ScDocument::IsDocProtected() const
+{
+ return pDocProtection && pDocProtection->isProtected();
+}
+
+bool ScDocument::IsDocEditable() const
+{
+ // Import into read-only document is possible
+ return !IsDocProtected() && ( bImportingXML || mbChangeReadOnlyEnabled || !mpShell || !mpShell->IsReadOnly() );
+}
+
+bool ScDocument::IsTabProtected( SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->IsProtected();
+
+ OSL_FAIL("Wrong table number");
+ return false;
+}
+
+const ScTableProtection* ScDocument::GetTabProtection(SCTAB nTab) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetProtection();
+
+ return nullptr;
+}
+
+void ScDocument::SetTabProtection(SCTAB nTab, const ScTableProtection* pProtect)
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()))
+ return;
+
+ maTabs[nTab]->SetProtection(pProtect);
+}
+
+void ScDocument::CopyTabProtection(SCTAB nTabSrc, SCTAB nTabDest)
+{
+ if (!ValidTab(nTabSrc) || nTabSrc >= static_cast<SCTAB>(maTabs.size()) || nTabDest >= static_cast<SCTAB>(maTabs.size()) || !ValidTab(nTabDest))
+ return;
+
+ maTabs[nTabDest]->SetProtection( maTabs[nTabSrc]->GetProtection() );
+}
+
+const ScDocOptions& ScDocument::GetDocOptions() const
+{
+ assert(pDocOptions && "No DocOptions! :-(");
+ return *pDocOptions;
+}
+
+void ScDocument::SetDocOptions( const ScDocOptions& rOpt )
+{
+ assert(pDocOptions && "No DocOptions! :-(");
+
+ *pDocOptions = rOpt;
+ if (mxPoolHelper)
+ mxPoolHelper->SetFormTableOpt(rOpt);
+}
+
+const ScViewOptions& ScDocument::GetViewOptions() const
+{
+ assert(pViewOptions && "No ViewOptions! :-(");
+ return *pViewOptions;
+}
+
+void ScDocument::SetViewOptions( const ScViewOptions& rOpt )
+{
+ assert(pViewOptions && "No ViewOptions! :-(");
+ *pViewOptions = rOpt;
+}
+
+void ScDocument::GetLanguage( LanguageType& rLatin, LanguageType& rCjk, LanguageType& rCtl ) const
+{
+ rLatin = eLanguage;
+ rCjk = eCjkLanguage;
+ rCtl = eCtlLanguage;
+}
+
+void ScDocument::SetLanguage( LanguageType eLatin, LanguageType eCjk, LanguageType eCtl )
+{
+ eLanguage = eLatin;
+ eCjkLanguage = eCjk;
+ eCtlLanguage = eCtl;
+ if ( mxPoolHelper.is() )
+ {
+ ScDocumentPool* pPool = mxPoolHelper->GetDocPool();
+ pPool->SetPoolDefaultItem( SvxLanguageItem( eLanguage, ATTR_FONT_LANGUAGE ) );
+ pPool->SetPoolDefaultItem( SvxLanguageItem( eCjkLanguage, ATTR_CJK_FONT_LANGUAGE ) );
+ pPool->SetPoolDefaultItem( SvxLanguageItem( eCtlLanguage, ATTR_CTL_FONT_LANGUAGE ) );
+ }
+
+ UpdateDrawLanguages(); // Set edit engine defaults in drawing layer pool
+}
+
+tools::Rectangle ScDocument::GetMMRect( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, SCTAB nTab, bool bHiddenAsZero ) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ {
+ OSL_FAIL("GetMMRect: wrong table");
+ return tools::Rectangle(0,0,0,0);
+ }
+
+ SCCOL i;
+ tools::Rectangle aRect;
+
+ for (i=0; i<nStartCol; i++)
+ aRect.AdjustLeft(GetColWidth(i,nTab, bHiddenAsZero ) );
+ aRect.AdjustTop(GetRowHeight( 0, nStartRow-1, nTab, bHiddenAsZero ) );
+
+ aRect.SetRight( aRect.Left() );
+ aRect.SetBottom( aRect.Top() );
+
+ for (i=nStartCol; i<=nEndCol; i++)
+ aRect.AdjustRight(GetColWidth(i,nTab, bHiddenAsZero) );
+ aRect.AdjustBottom(GetRowHeight( nStartRow, nEndRow, nTab, bHiddenAsZero ) );
+
+ aRect = o3tl::convert(aRect, o3tl::Length::twip, o3tl::Length::mm100);
+
+ if ( IsNegativePage( nTab ) )
+ ScDrawLayer::MirrorRectRTL( aRect );
+
+ return aRect;
+}
+
+void ScDocument::SetExtDocOptions( std::unique_ptr<ScExtDocOptions> pNewOptions )
+{
+ pExtDocOptions = std::move(pNewOptions);
+}
+
+void ScDocument::SetClipOptions(std::unique_ptr<ScClipOptions> pClipOptions)
+{
+ mpClipOptions = std::move(pClipOptions);
+}
+
+void ScDocument::DoMergeContents( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow, SCTAB nTab )
+{
+ OUStringBuffer aTotal;
+ OUString aCellStr;
+ SCCOL nCol;
+ SCROW nRow;
+ for (nRow=nStartRow; nRow<=nEndRow; nRow++)
+ for (nCol=nStartCol; nCol<=nEndCol; nCol++)
+ {
+ aCellStr = GetString(nCol, nRow, nTab);
+ if (!aCellStr.isEmpty())
+ {
+ if (!aTotal.isEmpty())
+ aTotal.append(' ');
+ aTotal.append(aCellStr);
+ }
+ if (nCol != nStartCol || nRow != nStartRow)
+ SetString(nCol,nRow,nTab,"");
+ }
+
+ SetString(nStartCol,nStartRow,nTab,aTotal.makeStringAndClear());
+}
+
+void ScDocument::DoEmptyBlock( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow, SCTAB nTab )
+{
+ SCCOL nCol;
+ SCROW nRow;
+ for (nRow=nStartRow; nRow<=nEndRow; nRow++)
+ for (nCol=nStartCol; nCol<=nEndCol; nCol++)
+ { // empty block except first cell
+ if (nCol != nStartCol || nRow != nStartRow)
+ SetString(nCol,nRow,nTab,"");
+ }
+}
+
+void ScDocument::DoMerge( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow, SCTAB nTab, bool bDeleteCaptions )
+{
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ pTab->SetMergedCells(nStartCol, nStartRow, nEndCol, nEndRow);
+
+ // Remove all covered notes (removed captions are collected by drawing undo if active)
+ InsertDeleteFlags nDelFlag = InsertDeleteFlags::NOTE | (bDeleteCaptions ? InsertDeleteFlags::NONE : InsertDeleteFlags::NOCAPTIONS);
+ if( nStartCol < nEndCol )
+ DeleteAreaTab( nStartCol + 1, nStartRow, nEndCol, nStartRow, nTab, nDelFlag );
+ if( nStartRow < nEndRow )
+ DeleteAreaTab( nStartCol, nStartRow + 1, nEndCol, nEndRow, nTab, nDelFlag );
+}
+
+void ScDocument::RemoveMerge( SCCOL nCol, SCROW nRow, SCTAB nTab )
+{
+ const ScMergeAttr* pAttr = GetAttr( nCol, nRow, nTab, ATTR_MERGE );
+
+ if ( pAttr->GetColMerge() <= 1 && pAttr->GetRowMerge() <= 1 )
+ return;
+
+ SCCOL nEndCol = nCol + pAttr->GetColMerge() - 1;
+ SCROW nEndRow = nRow + pAttr->GetRowMerge() - 1;
+
+ RemoveFlagsTab( nCol, nRow, nEndCol, nEndRow, nTab, ScMF::Hor | ScMF::Ver );
+
+ const ScMergeAttr* pDefAttr = &mxPoolHelper->GetDocPool()->GetDefaultItem( ATTR_MERGE );
+ ApplyAttr( nCol, nRow, nTab, *pDefAttr );
+}
+
+void ScDocument::ExtendPrintArea( OutputDevice* pDev, SCTAB nTab,
+ SCCOL nStartCol, SCROW nStartRow, SCCOL& rEndCol, SCROW nEndRow ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->ExtendPrintArea( pDev, nStartCol, nStartRow, rEndCol, nEndRow );
+}
+
+SCSIZE ScDocument::GetPatternCount( SCTAB nTab, SCCOL nCol ) const
+{
+ if( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetPatternCount( nCol );
+ else
+ return 0;
+}
+
+SCSIZE ScDocument::GetPatternCount( SCTAB nTab, SCCOL nCol, SCROW nRow1, SCROW nRow2 ) const
+{
+ if( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetPatternCount( nCol, nRow1, nRow2 );
+ else
+ return 0;
+}
+
+void ScDocument::ReservePatternCount( SCTAB nTab, SCCOL nCol, SCSIZE nReserve )
+{
+ if( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->ReservePatternCount( nCol, nReserve );
+}
+
+void ScDocument::GetSortParam( ScSortParam& rParam, SCTAB nTab )
+{
+ rParam = mSheetSortParams[ nTab ];
+}
+
+void ScDocument::SetSortParam( const ScSortParam& rParam, SCTAB nTab )
+{
+ mSheetSortParams[ nTab ] = rParam;
+}
+
+SCCOL ScDocument::ClampToAllocatedColumns(SCTAB nTab, SCCOL nCol) const
+{
+ return maTabs[nTab]->ClampToAllocatedColumns(nCol);
+}
+
+SCCOL ScDocument::GetAllocatedColumnsCount(SCTAB nTab) const
+{
+ return maTabs[nTab]->GetAllocatedColumnsCount();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/documen4.cxx b/sc/source/core/data/documen4.cxx
new file mode 100644
index 000000000..9aa39d66c
--- /dev/null
+++ b/sc/source/core/data/documen4.cxx
@@ -0,0 +1,1367 @@
+/* -*- 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 <formula/token.hxx>
+#include <sal/log.hxx>
+#include <unotools/configmgr.hxx>
+#include <osl/diagnose.h>
+#include <o3tl/string_view.hxx>
+
+#include <document.hxx>
+#include <table.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <subtotal.hxx>
+#include <docoptio.hxx>
+#include <markdata.hxx>
+#include <validat.hxx>
+#include <scitems.hxx>
+#include <stlpool.hxx>
+#include <poolhelp.hxx>
+#include <detdata.hxx>
+#include <patattr.hxx>
+#include <chgtrack.hxx>
+#include <progress.hxx>
+#include <paramisc.hxx>
+#include <compiler.hxx>
+#include <externalrefmgr.hxx>
+#include <attrib.hxx>
+#include <formulacell.hxx>
+#include <tokenarray.hxx>
+#include <tokenstringcontext.hxx>
+#include <memory>
+
+using namespace formula;
+
+/** (Goal Seek) Find a value of x that is a root of f(x)
+
+ This function is used internally for the goal seek operation. It uses the
+ Regula Falsi (aka false position) algorithm to find a root of f(x). The
+ start value and the target value are to be given by the user in the
+ goal seek dialog. The f(x) in this case is defined as the formula in the
+ formula cell minus target value. This function may also perform additional
+ search in the horizontal directions when the f(x) is discrete in order to
+ ensure a non-zero slope necessary for deriving a subsequent x that is
+ reasonably close to the root of interest.
+
+ @change 24.10.2004 by Kohei Yoshida (kohei@openoffice.org)
+
+ @see #i28955#
+
+ @change 6 Aug 2013, fdo37341
+*/
+bool ScDocument::Solver(SCCOL nFCol, SCROW nFRow, SCTAB nFTab,
+ SCCOL nVCol, SCROW nVRow, SCTAB nVTab,
+ const OUString& sValStr, double& nX)
+{
+ bool bRet = false;
+ nX = 0.0;
+ if ( ValidColRow( nFCol, nFRow ) && ValidTab( nFTab ) &&
+ ValidColRow( nVCol, nVRow ) && ValidTab( nVTab ) &&
+ nFTab < static_cast<SCTAB>( maTabs.size() ) && maTabs[nFTab] &&
+ nVTab < static_cast<SCTAB>( maTabs.size() ) && maTabs[nVTab] )
+ {
+ CellType eFType = GetCellType(nFCol, nFRow, nFTab);
+ CellType eVType = GetCellType(nVCol, nVRow, nVTab);
+ // #i108005# convert target value to number using default format,
+ // as previously done in ScInterpreter::GetDouble
+ ScFormulaCell* pFormula = nullptr;
+ double fTargetVal = 0.0;
+ sal_uInt32 nFIndex = 0;
+ if ( eFType == CELLTYPE_FORMULA && eVType == CELLTYPE_VALUE &&
+ GetFormatTable()->IsNumberFormat( sValStr, nFIndex, fTargetVal ) )
+ {
+ ScAddress aFormulaAdr( nFCol, nFRow, nFTab );
+ pFormula = GetFormulaCell( aFormulaAdr );
+ }
+ if (pFormula)
+ {
+ bool bDoneIteration = false;
+ ScAddress aValueAdr( nVCol, nVRow, nVTab );
+ double* pVCell = GetValueCell( aValueAdr );
+
+ ScRange aVRange( aValueAdr, aValueAdr ); // for SetDirty
+ // Original value to be restored later if necessary
+ double fSaveVal = *pVCell;
+
+ const sal_uInt16 nMaxIter = 100;
+ const double fEps = 1E-10;
+ const double fDelta = 1E-6;
+
+ double fBestX, fXPrev;
+ double fBestF, fFPrev;
+ fBestX = fXPrev = fSaveVal;
+
+ pFormula->Interpret();
+ bool bError = ( pFormula->GetErrCode() != FormulaError::NONE );
+ // bError always corresponds with fF
+
+ fFPrev = pFormula->GetValue() - fTargetVal;
+
+ fBestF = fabs( fFPrev );
+ if ( fBestF < fDelta )
+ bDoneIteration = true;
+
+ double fX = fXPrev + fEps;
+ double fF = fFPrev;
+ double fSlope;
+
+ sal_uInt16 nIter = 0;
+
+ bool bHorMoveError = false;
+ // Conform Regula Falsi Method
+ while ( !bDoneIteration && ( nIter++ < nMaxIter ) )
+ {
+ *pVCell = fX;
+ SetDirty( aVRange, false );
+ pFormula->Interpret();
+ bError = ( pFormula->GetErrCode() != FormulaError::NONE );
+ fF = pFormula->GetValue() - fTargetVal;
+
+ if ( fF == fFPrev && !bError )
+ {
+ // HORIZONTAL SEARCH: Keep moving x in both directions until the f(x)
+ // becomes different from the previous f(x). This routine is needed
+ // when a given function is discrete, in which case the resulting slope
+ // may become zero which ultimately causes the goal seek operation
+ // to fail. #i28955#
+
+ sal_uInt16 nHorIter = 0;
+ const double fHorStepAngle = 5.0;
+ const double fHorMaxAngle = 80.0;
+ int const nHorMaxIter = static_cast<int>( fHorMaxAngle / fHorStepAngle );
+ bool bDoneHorMove = false;
+
+ while ( !bDoneHorMove && !bHorMoveError && nHorIter++ < nHorMaxIter )
+ {
+ double fHorAngle = fHorStepAngle * static_cast<double>( nHorIter );
+ double fHorTangent = std::tan(basegfx::deg2rad(fHorAngle));
+
+ sal_uInt16 nIdx = 0;
+ while( nIdx++ < 2 && !bDoneHorMove )
+ {
+ double fHorX;
+ if ( nIdx == 1 )
+ fHorX = fX + fabs( fF ) * fHorTangent;
+ else
+ fHorX = fX - fabs( fF ) * fHorTangent;
+
+ *pVCell = fHorX;
+ SetDirty( aVRange, false );
+ pFormula->Interpret();
+ bHorMoveError = ( pFormula->GetErrCode() != FormulaError::NONE );
+ if ( bHorMoveError )
+ break;
+
+ fF = pFormula->GetValue() - fTargetVal;
+ if ( fF != fFPrev )
+ {
+ fX = fHorX;
+ bDoneHorMove = true;
+ }
+ }
+ }
+ if ( !bDoneHorMove )
+ bHorMoveError = true;
+ }
+
+ if ( bError )
+ {
+ // move closer to last valid value (fXPrev), keep fXPrev & fFPrev
+ double fDiff = ( fXPrev - fX ) / 2;
+ if ( fabs( fDiff ) < fEps )
+ fDiff = ( fDiff < 0.0 ? - fEps : fEps );
+ fX += fDiff;
+ }
+ else if ( bHorMoveError )
+ break;
+ else if ( fabs(fF) < fDelta )
+ {
+ // converged to root
+ fBestX = fX;
+ bDoneIteration = true;
+ }
+ else
+ {
+ if ( fabs(fF) + fDelta < fBestF )
+ {
+ fBestX = fX;
+ fBestF = fabs( fF );
+ }
+
+ if ( ( fXPrev - fX ) != 0 )
+ {
+ fSlope = ( fFPrev - fF ) / ( fXPrev - fX );
+ if ( fabs( fSlope ) < fEps )
+ fSlope = fSlope < 0.0 ? -fEps : fEps;
+ }
+ else
+ fSlope = fEps;
+
+ fXPrev = fX;
+ fFPrev = fF;
+ fX = fX - ( fF / fSlope );
+ }
+ }
+
+ // Try a nice rounded input value if possible.
+ const double fNiceDelta = ( bDoneIteration && fabs( fBestX ) >= 1e-3 ? 1e-3 : fDelta );
+ nX = ::rtl::math::approxFloor( ( fBestX / fNiceDelta ) + 0.5 ) * fNiceDelta;
+
+ if ( bDoneIteration )
+ {
+ *pVCell = nX;
+ SetDirty( aVRange, false );
+ pFormula->Interpret();
+ if ( fabs( pFormula->GetValue() - fTargetVal ) > fabs( fF ) )
+ nX = fBestX;
+ bRet = true;
+ }
+ else if ( bError || bHorMoveError )
+ {
+ nX = fBestX;
+ }
+ *pVCell = fSaveVal;
+ SetDirty( aVRange, false );
+ pFormula->Interpret();
+ if ( !bDoneIteration )
+ {
+ SetError( nVCol, nVRow, nVTab, FormulaError::NotAvailable );
+ }
+ }
+ else
+ {
+ SetError( nVCol, nVRow, nVTab, FormulaError::NotAvailable );
+ }
+ }
+ return bRet;
+}
+
+void ScDocument::InsertMatrixFormula(SCCOL nCol1, SCROW nRow1,
+ SCCOL nCol2, SCROW nRow2,
+ const ScMarkData& rMark,
+ const OUString& rFormula,
+ const ScTokenArray* pArr,
+ const formula::FormulaGrammar::Grammar eGram )
+{
+ PutInOrder(nCol1, nCol2);
+ PutInOrder(nRow1, nRow2);
+ nCol2 = std::min<SCCOL>(nCol2, MaxCol());
+ nRow2 = std::min<SCROW>(nRow2, MaxRow());
+ if (!rMark.GetSelectCount())
+ {
+ SAL_WARN("sc", "ScDocument::InsertMatrixFormula: No table marked");
+ return;
+ }
+ if (utl::ConfigManager::IsFuzzing())
+ {
+ // just too slow
+ if (nCol2 - nCol1 > 64)
+ return;
+ if (nRow2 - nRow1 > 64)
+ return;
+ }
+ assert( ValidColRow( nCol1, nRow1) && ValidColRow( nCol2, nRow2));
+
+ SCTAB nTab1 = *rMark.begin();
+
+ ScFormulaCell* pCell;
+ ScAddress aPos( nCol1, nRow1, nTab1 );
+ if (pArr)
+ pCell = new ScFormulaCell(*this, aPos, *pArr, eGram, ScMatrixMode::Formula);
+ else
+ pCell = new ScFormulaCell(*this, aPos, rFormula, eGram, ScMatrixMode::Formula);
+ pCell->SetMatColsRows( nCol2 - nCol1 + 1, nRow2 - nRow1 + 1 );
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+
+ if (!maTabs[rTab])
+ continue;
+
+ if (rTab == nTab1)
+ {
+ pCell = maTabs[rTab]->SetFormulaCell(nCol1, nRow1, pCell);
+ if (!pCell) //NULL if nCol1/nRow1 is invalid, which it can't be here
+ break;
+ }
+ else
+ maTabs[rTab]->SetFormulaCell(
+ nCol1, nRow1,
+ new ScFormulaCell(
+ *pCell, *this, ScAddress(nCol1, nRow1, rTab), ScCloneFlags::StartListening));
+ }
+
+ ScSingleRefData aRefData;
+ aRefData.InitFlags();
+ aRefData.SetRelCol(0);
+ aRefData.SetRelRow(0);
+ aRefData.SetRelTab(0); // 2D matrix, always same sheet
+
+ ScTokenArray aArr(*this); // consists only of one single reference token.
+ formula::FormulaToken* t = aArr.AddMatrixSingleReference(aRefData);
+
+ for (const SCTAB& nTab : rMark)
+ {
+ if (nTab >= nMax)
+ break;
+
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ continue;
+
+ for (SCCOL nCol : GetWritableColumnsRange(nTab, nCol1, nCol2))
+ {
+ aRefData.SetRelCol(nCol1 - nCol);
+ for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
+ {
+ if (nCol == nCol1 && nRow == nRow1)
+ // Skip the base position.
+ continue;
+
+ // Reference in each cell must point to the origin cell relative to the current cell.
+ aRefData.SetRelRow(nRow1 - nRow);
+ *t->GetSingleRef() = aRefData;
+ // Token array must be cloned so that each formula cell receives its own copy.
+ ScTokenArray aTokArr(aArr.CloneValue());
+ aPos = ScAddress(nCol, nRow, nTab);
+ pCell = new ScFormulaCell(*this, aPos, aTokArr, eGram, ScMatrixMode::Reference);
+ pTab->SetFormulaCell(nCol, nRow, pCell);
+ }
+ }
+ }
+}
+
+void ScDocument::InsertTableOp(const ScTabOpParam& rParam, // multiple (repeated?) operation
+ SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ const ScMarkData& rMark)
+{
+ PutInOrder(nCol1, nCol2);
+ PutInOrder(nRow1, nRow2);
+ assert( ValidColRow( nCol1, nRow1) && ValidColRow( nCol2, nRow2));
+ SCTAB i, nTab1;
+ SCCOL j;
+ SCROW k;
+ i = 0;
+ bool bStop = false;
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+
+ if (maTabs[rTab])
+ {
+ i = rTab;
+ bStop = true;
+ break;
+ }
+ }
+ nTab1 = i;
+ if (!bStop)
+ {
+ OSL_FAIL("ScDocument::InsertTableOp: No table marked");
+ return;
+ }
+
+ ScRefAddress aRef;
+ OUStringBuffer aForString;
+ aForString.append('=');
+ aForString.append(ScCompiler::GetNativeSymbol(ocTableOp));
+ aForString.append(ScCompiler::GetNativeSymbol( ocOpen));
+
+ const OUString& sSep = ScCompiler::GetNativeSymbol( ocSep);
+ if (rParam.meMode == ScTabOpParam::Column) // column only
+ {
+ aRef.Set( rParam.aRefFormulaCell.GetAddress(), true, false, false );
+ aForString.append(aRef.GetRefString(*this, nTab1));
+ aForString.append(sSep);
+ aForString.append(rParam.aRefColCell.GetRefString(*this, nTab1));
+ aForString.append(sSep);
+ aRef.Set( nCol1, nRow1, nTab1, false, true, true );
+ aForString.append(aRef.GetRefString(*this, nTab1));
+ nCol1++;
+ nCol2 = std::min( nCol2, static_cast<SCCOL>(rParam.aRefFormulaEnd.Col() -
+ rParam.aRefFormulaCell.Col() + nCol1 + 1));
+ }
+ else if (rParam.meMode == ScTabOpParam::Row) // row only
+ {
+ aRef.Set( rParam.aRefFormulaCell.GetAddress(), false, true, false );
+ aForString.append(aRef.GetRefString(*this, nTab1));
+ aForString.append(sSep);
+ aForString.append(rParam.aRefRowCell.GetRefString(*this, nTab1));
+ aForString.append(sSep);
+ aRef.Set( nCol1, nRow1, nTab1, true, false, true );
+ aForString.append(aRef.GetRefString(*this, nTab1));
+ nRow1++;
+ nRow2 = std::min( nRow2, static_cast<SCROW>(rParam.aRefFormulaEnd.Row() -
+ rParam.aRefFormulaCell.Row() + nRow1 + 1));
+ }
+ else // both
+ {
+ aForString.append(rParam.aRefFormulaCell.GetRefString(*this, nTab1));
+ aForString.append(sSep);
+ aForString.append(rParam.aRefColCell.GetRefString(*this, nTab1));
+ aForString.append(sSep);
+ aRef.Set( nCol1, nRow1 + 1, nTab1, false, true, true );
+ aForString.append(aRef.GetRefString(*this, nTab1));
+ aForString.append(sSep);
+ aForString.append(rParam.aRefRowCell.GetRefString(*this, nTab1));
+ aForString.append(sSep);
+ aRef.Set( nCol1 + 1, nRow1, nTab1, true, false, true );
+ aForString.append(aRef.GetRefString(*this, nTab1));
+ nCol1++; nRow1++;
+ }
+ aForString.append(ScCompiler::GetNativeSymbol( ocClose ));
+
+ ScFormulaCell aRefCell( *this, ScAddress( nCol1, nRow1, nTab1 ), aForString.makeStringAndClear(),
+ formula::FormulaGrammar::GRAM_NATIVE, ScMatrixMode::NONE );
+ for( j = nCol1; j <= nCol2; j++ )
+ for( k = nRow1; k <= nRow2; k++ )
+ for (i = 0; i < static_cast<SCTAB>(maTabs.size()); i++)
+ {
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+ if( maTabs[rTab] )
+ maTabs[rTab]->SetFormulaCell(
+ j, k, new ScFormulaCell(aRefCell, *this, ScAddress(j, k, rTab), ScCloneFlags::StartListening));
+ }
+ }
+}
+
+namespace {
+
+bool setCacheTableReferenced(const ScDocument& rDoc, formula::FormulaToken& rToken, ScExternalRefManager& rRefMgr, const ScAddress& rPos)
+{
+ switch (rToken.GetType())
+ {
+ case svExternalSingleRef:
+ return rRefMgr.setCacheTableReferenced(
+ rToken.GetIndex(), rToken.GetString().getString(), 1);
+ case svExternalDoubleRef:
+ {
+ const ScComplexRefData& rRef = *rToken.GetDoubleRef();
+ ScRange aAbs = rRef.toAbs(rDoc, rPos);
+ size_t nSheets = aAbs.aEnd.Tab() - aAbs.aStart.Tab() + 1;
+ return rRefMgr.setCacheTableReferenced(
+ rToken.GetIndex(), rToken.GetString().getString(), nSheets);
+ }
+ case svExternalName:
+ /* TODO: external names aren't supported yet, but would
+ * have to be marked as well, if so. Mechanism would be
+ * different. */
+ OSL_FAIL("ScDocument::MarkUsedExternalReferences: implement the svExternalName case!");
+ break;
+ default:
+ break;
+ }
+ return false;
+}
+
+}
+
+bool ScDocument::MarkUsedExternalReferences( const ScTokenArray& rArr, const ScAddress& rPos )
+{
+ if (!rArr.GetLen())
+ return false;
+
+ ScExternalRefManager* pRefMgr = nullptr;
+ formula::FormulaTokenArrayPlainIterator aIter( rArr );
+ bool bAllMarked = false;
+ while (!bAllMarked)
+ {
+ formula::FormulaToken* t = aIter.GetNextReferenceOrName();
+ if (!t)
+ break;
+ if (t->IsExternalRef())
+ {
+ if (!pRefMgr)
+ pRefMgr = GetExternalRefManager();
+
+ bAllMarked = setCacheTableReferenced(*this, *t, *pRefMgr, rPos);
+ }
+ else if (t->GetType() == svIndex)
+ {
+ // this is a named range. Check if the range contains an external
+ // reference.
+ ScRangeData* pRangeData = GetRangeName()->findByIndex(t->GetIndex());
+ if (!pRangeData)
+ continue;
+
+ ScTokenArray* pArray = pRangeData->GetCode();
+ formula::FormulaTokenArrayPlainIterator aArrayIter(*pArray);
+ for (t = aArrayIter.First(); t; t = aArrayIter.Next())
+ {
+ if (!t->IsExternalRef())
+ continue;
+
+ if (!pRefMgr)
+ pRefMgr = GetExternalRefManager();
+
+ bAllMarked = setCacheTableReferenced(*this, *t, *pRefMgr, rPos);
+ }
+ }
+ }
+ return bAllMarked;
+}
+
+bool ScDocument::GetNextSpellingCell(SCCOL& nCol, SCROW& nRow, SCTAB nTab,
+ bool bInSel, const ScMarkData& rMark) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetNextSpellingCell( nCol, nRow, bInSel, rMark );
+ else
+ return false;
+}
+
+bool ScDocument::GetNextMarkedCell( SCCOL& rCol, SCROW& rRow, SCTAB nTab,
+ const ScMarkData& rMark )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetNextMarkedCell( rCol, rRow, rMark );
+ else
+ return false;
+}
+
+void ScDocument::ReplaceStyle(const SvxSearchItem& rSearchItem,
+ SCCOL nCol, SCROW nRow, SCTAB nTab,
+ const ScMarkData& rMark)
+{
+ if (nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->ReplaceStyle(rSearchItem, nCol, nRow, rMark, true/*bIsUndoP*/);
+}
+
+void ScDocument::CompileDBFormula()
+{
+ sc::CompileFormulaContext aCxt(*this);
+ for (auto& rxTab : maTabs)
+ {
+ if (rxTab)
+ rxTab->CompileDBFormula(aCxt);
+ }
+}
+
+void ScDocument::CompileColRowNameFormula()
+{
+ sc::CompileFormulaContext aCxt(*this);
+ for (auto& rxTab : maTabs)
+ {
+ if (rxTab)
+ rxTab->CompileColRowNameFormula(aCxt);
+ }
+}
+
+void ScDocument::InvalidateTableArea()
+{
+ for (auto& rxTab : maTabs)
+ {
+ if (!rxTab)
+ break;
+ rxTab->InvalidateTableArea();
+ if ( rxTab->IsScenario() )
+ rxTab->InvalidateScenarioRanges();
+ }
+}
+
+sal_Int32 ScDocument::GetMaxStringLen( SCTAB nTab, SCCOL nCol,
+ SCROW nRowStart, SCROW nRowEnd, rtl_TextEncoding eCharSet ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetMaxStringLen( nCol, nRowStart, nRowEnd, eCharSet );
+ else
+ return 0;
+}
+
+sal_Int32 ScDocument::GetMaxNumberStringLen( sal_uInt16& nPrecision, SCTAB nTab,
+ SCCOL nCol,
+ SCROW nRowStart, SCROW nRowEnd ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetMaxNumberStringLen( nPrecision, nCol,
+ nRowStart, nRowEnd );
+ else
+ return 0;
+}
+
+bool ScDocument::GetSelectionFunction( ScSubTotalFunc eFunc,
+ const ScAddress& rCursor, const ScMarkData& rMark,
+ double& rResult )
+{
+ ScFunctionData aData(eFunc);
+
+ ScMarkData aMark(rMark);
+ aMark.MarkToMulti();
+ if (!aMark.IsMultiMarked() && !aMark.IsCellMarked(rCursor.Col(), rCursor.Row()))
+ aMark.SetMarkArea(rCursor);
+
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ ScMarkData::const_iterator itr = aMark.begin(), itrEnd = aMark.end();
+
+ for (; itr != itrEnd && *itr < nMax && !aData.getError(); ++itr)
+ if (maTabs[*itr])
+ maTabs[*itr]->UpdateSelectionFunction(aData, aMark);
+
+ rResult = aData.getResult();
+ if (aData.getError())
+ rResult = 0.0;
+
+ return !aData.getError();
+}
+
+double ScDocument::RoundValueAsShown( double fVal, sal_uInt32 nFormat, const ScInterpreterContext* pContext ) const
+{
+ const SvNumberFormatter* pFormatter = pContext ? pContext->GetFormatTable() : GetFormatTable();
+ const SvNumberformat* pFormat = pFormatter->GetEntry( nFormat );
+ if (!pFormat)
+ return fVal;
+ SvNumFormatType nType = pFormat->GetMaskedType();
+ if (nType != SvNumFormatType::DATE && nType != SvNumFormatType::TIME && nType != SvNumFormatType::DATETIME )
+ {
+ // MSVC doesn't recognize all paths init nPrecision and wails about
+ // "potentially uninitialized local variable 'nPrecision' used"
+ // so init to some random sensible value preserving all decimals.
+ short nPrecision = 20;
+ bool bStdPrecision = ((nFormat % SV_COUNTRY_LANGUAGE_OFFSET) == 0);
+ if (!bStdPrecision)
+ {
+ sal_uInt16 nIdx = pFormat->GetSubformatIndex( fVal );
+ nPrecision = static_cast<short>(pFormat->GetFormatPrecision( nIdx ));
+ switch ( nType )
+ {
+ case SvNumFormatType::PERCENT: // 0.41% == 0.0041
+ nPrecision += 2;
+ break;
+ case SvNumFormatType::SCIENTIFIC: // 1.23e-3 == 0.00123
+ {
+ short nExp = 0;
+ if ( fVal > 0.0 )
+ nExp = static_cast<short>(floor( log10( fVal ) ));
+ else if ( fVal < 0.0 )
+ nExp = static_cast<short>(floor( log10( -fVal ) ));
+ nPrecision -= nExp;
+ short nInteger = static_cast<short>(pFormat->GetFormatIntegerDigits( nIdx ));
+ if ( nInteger > 1 ) // Engineering notation
+ {
+ short nIncrement = nExp % nInteger;
+ if ( nIncrement != 0 )
+ {
+ nPrecision += nIncrement;
+ if (nExp < 0 )
+ nPrecision += nInteger;
+ }
+ }
+ break;
+ }
+ case SvNumFormatType::FRACTION: // get value of fraction representation
+ {
+ return pFormat->GetRoundFractionValue( fVal );
+ }
+ case SvNumFormatType::NUMBER:
+ case SvNumFormatType::CURRENCY:
+ { // tdf#106253 Thousands divisors for format "0,"
+ const sal_uInt16 nTD = pFormat->GetThousandDivisorPrecision( nIdx );
+ if (nTD == SvNumberFormatter::UNLIMITED_PRECISION)
+ // Format contains General keyword, handled below.
+ bStdPrecision = true;
+ else
+ nPrecision -= nTD;
+ break;
+ }
+ default: break;
+ }
+ }
+ if (bStdPrecision)
+ {
+ nPrecision = static_cast<short>(GetDocOptions().GetStdPrecision());
+ // #i115512# no rounding for automatic decimals
+ if (nPrecision == static_cast<short>(SvNumberFormatter::UNLIMITED_PRECISION))
+ return fVal;
+ }
+ double fRound = ::rtl::math::round( fVal, nPrecision );
+ if ( ::rtl::math::approxEqual( fVal, fRound ) )
+ return fVal; // rounding might introduce some error
+ else
+ return fRound;
+ }
+ else
+ return fVal;
+}
+
+// conditional formats and validation ranges
+
+sal_uLong ScDocument::AddCondFormat( std::unique_ptr<ScConditionalFormat> pNew, SCTAB nTab )
+{
+ if(!pNew)
+ return 0;
+
+ if(ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->AddCondFormat( std::move(pNew) );
+
+ return 0;
+}
+
+sal_uLong ScDocument::AddValidationEntry( const ScValidationData& rNew )
+{
+ if (rNew.IsEmpty())
+ return 0; // empty is always 0
+
+ if (!pValidationList)
+ {
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ pValidationList.reset(new ScValidationDataList);
+ }
+
+ sal_uLong nMax = 0;
+ for( const auto& rxData : *pValidationList )
+ {
+ const ScValidationData* pData = rxData.get();
+ sal_uLong nKey = pData->GetKey();
+ if ( pData->EqualEntries( rNew ) )
+ return nKey;
+ if ( nKey > nMax )
+ nMax = nKey;
+ }
+
+ // might be called from ScPatternAttr::PutInPool; thus clone (real copy)
+
+ sal_uLong nNewKey = nMax + 1;
+ std::unique_ptr<ScValidationData> pInsert(rNew.Clone(this));
+ pInsert->SetKey( nNewKey );
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ pValidationList->InsertNew( std::move(pInsert) );
+ return nNewKey;
+}
+
+const SfxPoolItem* ScDocument::GetEffItem(
+ SCCOL nCol, SCROW nRow, SCTAB nTab, sal_uInt16 nWhich ) const
+{
+ const ScPatternAttr* pPattern = GetPattern( nCol, nRow, nTab );
+ if ( pPattern )
+ {
+ const SfxItemSet& rSet = pPattern->GetItemSet();
+ if ( rSet.GetItemState( ATTR_CONDITIONAL ) == SfxItemState::SET )
+ {
+ const ScCondFormatIndexes& rIndex = pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData();
+ ScConditionalFormatList* pCondFormList = GetCondFormList( nTab );
+ if (!rIndex.empty() && pCondFormList)
+ {
+ for(const auto& rItem : rIndex)
+ {
+ const ScConditionalFormat* pForm = pCondFormList->GetFormat( rItem );
+ if ( pForm )
+ {
+ ScAddress aPos(nCol, nRow, nTab);
+ ScRefCellValue aCell(const_cast<ScDocument&>(*this), aPos);
+ const OUString& aStyle = pForm->GetCellStyle(aCell, aPos);
+ if (!aStyle.isEmpty())
+ {
+ SfxStyleSheetBase* pStyleSheet = mxPoolHelper->GetStylePool()->Find(
+ aStyle, SfxStyleFamily::Para );
+ const SfxPoolItem* pItem = nullptr;
+ if ( pStyleSheet && pStyleSheet->GetItemSet().GetItemState(
+ nWhich, true, &pItem ) == SfxItemState::SET )
+ return pItem;
+ }
+ }
+ }
+ }
+ }
+ return &rSet.Get( nWhich );
+ }
+ OSL_FAIL("no pattern");
+ return nullptr;
+}
+
+const SfxItemSet* ScDocument::GetCondResult( SCCOL nCol, SCROW nRow, SCTAB nTab, ScRefCellValue* pCell ) const
+{
+ ScConditionalFormatList* pFormatList = GetCondFormList(nTab);
+ if (!pFormatList)
+ return nullptr;
+
+ ScAddress aPos(nCol, nRow, nTab);
+ ScRefCellValue aCell;
+ if( pCell == nullptr )
+ {
+ aCell.assign(const_cast<ScDocument&>(*this), aPos);
+ pCell = &aCell;
+ }
+ const ScPatternAttr* pPattern = GetPattern( nCol, nRow, nTab );
+ const ScCondFormatIndexes& rIndex =
+ pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData();
+
+ return GetCondResult(*pCell, aPos, *pFormatList, rIndex);
+}
+
+const SfxItemSet* ScDocument::GetCondResult(
+ ScRefCellValue& rCell, const ScAddress& rPos, const ScConditionalFormatList& rList,
+ const ScCondFormatIndexes& rIndex ) const
+{
+ for (const auto& rItem : rIndex)
+ {
+ const ScConditionalFormat* pForm = rList.GetFormat(rItem);
+ if (!pForm)
+ continue;
+
+ const OUString& aStyle = pForm->GetCellStyle(rCell, rPos);
+ if (!aStyle.isEmpty())
+ {
+ SfxStyleSheetBase* pStyleSheet =
+ mxPoolHelper->GetStylePool()->Find(aStyle, SfxStyleFamily::Para);
+
+ if (pStyleSheet)
+ return &pStyleSheet->GetItemSet();
+
+ // if style is not there, treat like no condition
+ }
+ }
+
+ return nullptr;
+}
+
+ScConditionalFormat* ScDocument::GetCondFormat(
+ SCCOL nCol, SCROW nRow, SCTAB nTab ) const
+{
+ sal_uInt32 nIndex = 0;
+ const ScCondFormatIndexes& rCondFormats = GetAttr(nCol, nRow, nTab, ATTR_CONDITIONAL)->GetCondFormatData();
+
+ if(!rCondFormats.empty())
+ nIndex = rCondFormats[0];
+
+ if (nIndex)
+ {
+ ScConditionalFormatList* pCondFormList = GetCondFormList(nTab);
+ if (pCondFormList)
+ return pCondFormList->GetFormat( nIndex );
+ else
+ {
+ OSL_FAIL("pCondFormList is 0");
+ }
+ }
+
+ return nullptr;
+}
+
+ScConditionalFormatList* ScDocument::GetCondFormList(SCTAB nTab) const
+{
+ if(ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetCondFormList();
+
+ return nullptr;
+}
+
+void ScDocument::SetCondFormList( ScConditionalFormatList* pList, SCTAB nTab )
+{
+ if(ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->SetCondFormList(pList);
+}
+
+const ScValidationData* ScDocument::GetValidationEntry( sal_uLong nIndex ) const
+{
+ if ( pValidationList )
+ return pValidationList->GetData( nIndex );
+ else
+ return nullptr;
+}
+
+void ScDocument::DeleteConditionalFormat(sal_uLong nOldIndex, SCTAB nTab)
+{
+ if(ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->DeleteConditionalFormat(nOldIndex);
+}
+
+bool ScDocument::HasDetectiveOperations() const
+{
+ return pDetOpList && pDetOpList->Count();
+}
+
+void ScDocument::AddDetectiveOperation( const ScDetOpData& rData )
+{
+ if (!pDetOpList)
+ pDetOpList.reset(new ScDetOpList);
+
+ pDetOpList->Append( rData );
+}
+
+void ScDocument::ClearDetectiveOperations()
+{
+ pDetOpList.reset(); // deletes also the entries
+}
+
+void ScDocument::SetDetOpList(std::unique_ptr<ScDetOpList> pNew)
+{
+ pDetOpList = std::move(pNew);
+}
+
+// Comparison of Documents
+
+// Pfriemel-Factors
+#define SC_DOCCOMP_MAXDIFF 256
+#define SC_DOCCOMP_MINGOOD 128
+#define SC_DOCCOMP_COLUMNS 10
+#define SC_DOCCOMP_ROWS 100
+
+sal_uInt16 ScDocument::RowDifferences( SCROW nThisRow, SCTAB nThisTab,
+ ScDocument& rOtherDoc, SCROW nOtherRow, SCTAB nOtherTab,
+ SCCOL nMaxCol, const SCCOLROW* pOtherCols )
+{
+ sal_uLong nDif = 0;
+ sal_uLong nUsed = 0;
+ for (SCCOL nThisCol=0; nThisCol<=nMaxCol; nThisCol++)
+ {
+ SCCOL nOtherCol;
+ if ( pOtherCols )
+ nOtherCol = static_cast<SCCOL>(pOtherCols[nThisCol]);
+ else
+ nOtherCol = nThisCol;
+
+ if (ValidCol(nOtherCol)) // only compare columns that are common to both docs
+ {
+ ScRefCellValue aThisCell(*this, ScAddress(nThisCol, nThisRow, nThisTab));
+ ScRefCellValue aOtherCell(rOtherDoc, ScAddress(nOtherCol, nOtherRow, nOtherTab));
+ if (!aThisCell.equalsWithoutFormat(aOtherCell))
+ {
+ if (!aThisCell.isEmpty() && !aOtherCell.isEmpty())
+ nDif += 3;
+ else
+ nDif += 4; // content <-> empty counts more
+ }
+
+ if (!aThisCell.isEmpty() || !aOtherCell.isEmpty())
+ ++nUsed;
+ }
+ }
+
+ if (nUsed > 0)
+ return static_cast<sal_uInt16>((nDif*64)/nUsed); // max.256 (SC_DOCCOMP_MAXDIFF)
+
+ OSL_ENSURE(!nDif,"Diff without Used");
+ return 0;
+}
+
+sal_uInt16 ScDocument::ColDifferences( SCCOL nThisCol, SCTAB nThisTab,
+ ScDocument& rOtherDoc, SCCOL nOtherCol, SCTAB nOtherTab,
+ SCROW nMaxRow, const SCCOLROW* pOtherRows )
+{
+
+ //TODO: optimize e.g. with iterator?
+
+ sal_uInt64 nDif = 0;
+ sal_uInt64 nUsed = 0;
+ for (SCROW nThisRow=0; nThisRow<=nMaxRow; nThisRow++)
+ {
+ SCROW nOtherRow;
+ if ( pOtherRows )
+ nOtherRow = pOtherRows[nThisRow];
+ else
+ nOtherRow = nThisRow;
+
+ if (ValidRow(nOtherRow)) // only compare rows that are common to both docs
+ {
+ ScRefCellValue aThisCell(*this, ScAddress(nThisCol, nThisRow, nThisTab));
+ ScRefCellValue aOtherCell(rOtherDoc, ScAddress(nOtherCol, nOtherRow, nOtherTab));
+ if (!aThisCell.equalsWithoutFormat(aOtherCell))
+ {
+ if (!aThisCell.isEmpty() && !aOtherCell.isEmpty())
+ nDif += 3;
+ else
+ nDif += 4; // content <-> empty counts more
+ }
+
+ if (!aThisCell.isEmpty() || !aOtherCell.isEmpty())
+ ++nUsed;
+ }
+ }
+
+ if (nUsed > 0)
+ return static_cast<sal_uInt16>((nDif*64)/nUsed); // max.256
+
+ OSL_ENSURE(!nDif,"Diff without Used");
+ return 0;
+}
+
+void ScDocument::FindOrder( SCCOLROW* pOtherRows, SCCOLROW nThisEndRow, SCCOLROW nOtherEndRow,
+ bool bColumns, ScDocument& rOtherDoc, SCTAB nThisTab, SCTAB nOtherTab,
+ SCCOLROW nEndCol, const SCCOLROW* pTranslate, ScProgress* pProgress, sal_uInt64 nProAdd )
+{
+ // bColumns=true: rows are columns and vice versa
+
+ SCCOLROW nMaxCont; // continue by how much
+ SCCOLROW nMinGood; // what is a hit (incl.)
+ if ( bColumns )
+ {
+ nMaxCont = SC_DOCCOMP_COLUMNS; // 10 columns
+ nMinGood = SC_DOCCOMP_MINGOOD;
+
+ //TODO: additional pass with nMinGood = 0 ????
+
+ }
+ else
+ {
+ nMaxCont = SC_DOCCOMP_ROWS; // 100 rows
+ nMinGood = SC_DOCCOMP_MINGOOD;
+ }
+ bool bUseTotal = bColumns && !pTranslate; // only for the 1st pass
+
+ SCCOLROW nOtherRow = 0;
+ sal_uInt16 nComp;
+ SCCOLROW nThisRow;
+ bool bTotal = false; // hold for several nThisRow
+ SCCOLROW nUnknown = 0;
+ for (nThisRow = 0; nThisRow <= nThisEndRow; nThisRow++)
+ {
+ SCCOLROW nTempOther = nOtherRow;
+ bool bFound = false;
+ sal_uInt16 nBest = SC_DOCCOMP_MAXDIFF;
+ SCCOLROW nMax = std::min( nOtherEndRow, static_cast<SCCOLROW>(( nTempOther + nMaxCont + nUnknown )) );
+ for (SCCOLROW i=nTempOther; i<=nMax && nBest>0; i++) // stop at 0
+ {
+ if (bColumns)
+ nComp = ColDifferences( static_cast<SCCOL>(nThisRow), nThisTab, rOtherDoc, static_cast<SCCOL>(i), nOtherTab, nEndCol, pTranslate );
+ else
+ nComp = RowDifferences( nThisRow, nThisTab, rOtherDoc, i, nOtherTab, static_cast<SCCOL>(nEndCol), pTranslate );
+ if ( nComp < nBest && ( nComp <= nMinGood || bTotal ) )
+ {
+ nTempOther = i;
+ nBest = nComp;
+ bFound = true;
+ }
+ if ( nComp < SC_DOCCOMP_MAXDIFF || bFound )
+ bTotal = false;
+ else if ( i == nTempOther && bUseTotal )
+ bTotal = true; // only at the very top
+ }
+ if ( bFound )
+ {
+ pOtherRows[nThisRow] = nTempOther;
+ nOtherRow = nTempOther + 1;
+ nUnknown = 0;
+ }
+ else
+ {
+ pOtherRows[nThisRow] = SCROW_MAX;
+ ++nUnknown;
+ }
+
+ if (pProgress)
+ pProgress->SetStateOnPercent(nProAdd+static_cast<sal_uLong>(nThisRow));
+ }
+
+ // fill in blocks that don't match
+
+ SCROW nFillStart = 0;
+ SCROW nFillPos = 0;
+ bool bInFill = false;
+ for (nThisRow = 0; nThisRow <= nThisEndRow+1; nThisRow++)
+ {
+ SCROW nThisOther = ( nThisRow <= nThisEndRow ) ? pOtherRows[nThisRow] : (nOtherEndRow+1);
+ if ( ValidRow(nThisOther) )
+ {
+ if ( bInFill )
+ {
+ if ( nThisOther > nFillStart ) // is there something to distribute?
+ {
+ SCROW nDiff1 = nThisOther - nFillStart;
+ SCROW nDiff2 = nThisRow - nFillPos;
+ SCROW nMinDiff = std::min(nDiff1, nDiff2);
+ for (SCROW i=0; i<nMinDiff; i++)
+ pOtherRows[nFillPos+i] = nFillStart+i;
+ }
+
+ bInFill = false;
+ }
+ nFillStart = nThisOther + 1;
+ nFillPos = nThisRow + 1;
+ }
+ else
+ bInFill = true;
+ }
+}
+
+void ScDocument::CompareDocument( ScDocument& rOtherDoc )
+{
+ if (!pChangeTrack)
+ return;
+
+ SCTAB nThisCount = GetTableCount();
+ SCTAB nOtherCount = rOtherDoc.GetTableCount();
+ std::unique_ptr<SCTAB[]> pOtherTabs(new SCTAB[nThisCount]);
+ SCTAB nThisTab;
+
+ // compare tables with identical names
+ OUString aThisName;
+ OUString aOtherName;
+ for (nThisTab=0; nThisTab<nThisCount; nThisTab++)
+ {
+ SCTAB nOtherTab = SCTAB_MAX;
+ if (!IsScenario(nThisTab)) // skip scenarios
+ {
+ GetName( nThisTab, aThisName );
+ for (SCTAB nTemp=0; nTemp<nOtherCount && nOtherTab>MAXTAB; nTemp++)
+ if (!rOtherDoc.IsScenario(nTemp))
+ {
+ rOtherDoc.GetName( nTemp, aOtherName );
+ if ( aThisName == aOtherName )
+ nOtherTab = nTemp;
+ }
+ }
+ pOtherTabs[nThisTab] = nOtherTab;
+ }
+ // fill in, so that un-named tables don't get lost
+ SCTAB nFillStart = 0;
+ SCTAB nFillPos = 0;
+ bool bInFill = false;
+ for (nThisTab = 0; nThisTab <= nThisCount; nThisTab++)
+ {
+ SCTAB nThisOther = ( nThisTab < nThisCount ) ? pOtherTabs[nThisTab] : nOtherCount;
+ if ( ValidTab(nThisOther) )
+ {
+ if ( bInFill )
+ {
+ if ( nThisOther > nFillStart ) // is there something to distribute?
+ {
+ SCTAB nDiff1 = nThisOther - nFillStart;
+ SCTAB nDiff2 = nThisTab - nFillPos;
+ SCTAB nMinDiff = std::min(nDiff1, nDiff2);
+ for (SCTAB i=0; i<nMinDiff; i++)
+ if ( !IsScenario(nFillPos+i) && !rOtherDoc.IsScenario(nFillStart+i) )
+ pOtherTabs[nFillPos+i] = nFillStart+i;
+ }
+
+ bInFill = false;
+ }
+ nFillStart = nThisOther + 1;
+ nFillPos = nThisTab + 1;
+ }
+ else
+ bInFill = true;
+ }
+
+ // compare tables in the original order
+
+ for (nThisTab=0; nThisTab<nThisCount; nThisTab++)
+ {
+ SCTAB nOtherTab = pOtherTabs[nThisTab];
+ if ( ValidTab(nOtherTab) )
+ {
+ SCCOL nThisEndCol = 0;
+ SCROW nThisEndRow = 0;
+ SCCOL nOtherEndCol = 0;
+ SCROW nOtherEndRow = 0;
+ GetCellArea( nThisTab, nThisEndCol, nThisEndRow );
+ rOtherDoc.GetCellArea( nOtherTab, nOtherEndCol, nOtherEndRow );
+ SCCOL nEndCol = std::max(nThisEndCol, nOtherEndCol);
+ SCROW nEndRow = std::max(nThisEndRow, nOtherEndRow);
+ SCCOL nThisCol;
+ SCROW nThisRow;
+ sal_uLong n1,n2; // for AppendDeleteRange
+
+ //TODO: one Progress over all tables ???
+
+ OUString aTabName;
+ GetName( nThisTab, aTabName );
+ OUString aTemplate = ScResId(STR_PROGRESS_COMPARING);
+ sal_Int32 nIndex = 0;
+ OUString aProText = o3tl::getToken(aTemplate, 0, '#', nIndex ) +
+ aTabName +
+ o3tl::getToken(aTemplate, 0, '#', nIndex );
+ ScProgress aProgress( GetDocumentShell(), aProText, 3*nThisEndRow, true ); // 2x FindOrder, 1x here
+ tools::Long nProgressStart = 2*nThisEndRow; // start for here
+
+ std::unique_ptr<SCCOLROW[]> pTempRows(new SCCOLROW[nThisEndRow+1]);
+ std::unique_ptr<SCCOLROW[]> pOtherRows(new SCCOLROW[nThisEndRow+1]);
+ std::unique_ptr<SCCOLROW[]> pOtherCols(new SCCOLROW[nThisEndCol+1]);
+
+ // find inserted/deleted columns/rows:
+ // Two attempts:
+ // 1) compare original rows (pTempRows)
+ // 2) compare original columns (pOtherCols)
+ // with this column order compare rows (pOtherRows)
+
+ //TODO: compare columns twice with different nMinGood ???
+
+ // 1
+ FindOrder( pTempRows.get(), nThisEndRow, nOtherEndRow, false,
+ rOtherDoc, nThisTab, nOtherTab, nEndCol, nullptr, &aProgress, 0 );
+ // 2
+ FindOrder( pOtherCols.get(), nThisEndCol, nOtherEndCol, true,
+ rOtherDoc, nThisTab, nOtherTab, nEndRow, nullptr, nullptr, 0 );
+ FindOrder( pOtherRows.get(), nThisEndRow, nOtherEndRow, false,
+ rOtherDoc, nThisTab, nOtherTab, nThisEndCol,
+ pOtherCols.get(), &aProgress, nThisEndRow );
+
+ sal_uLong nMatch1 = 0; // pTempRows, no columns
+ for (nThisRow = 0; nThisRow<=nThisEndRow; nThisRow++)
+ if (ValidRow(pTempRows[nThisRow]))
+ nMatch1 += SC_DOCCOMP_MAXDIFF -
+ RowDifferences( nThisRow, nThisTab, rOtherDoc, pTempRows[nThisRow],
+ nOtherTab, nEndCol, nullptr );
+
+ sal_uLong nMatch2 = 0; // pOtherRows, pOtherCols
+ for (nThisRow = 0; nThisRow<=nThisEndRow; nThisRow++)
+ if (ValidRow(pOtherRows[nThisRow]))
+ nMatch2 += SC_DOCCOMP_MAXDIFF -
+ RowDifferences( nThisRow, nThisTab, rOtherDoc, pOtherRows[nThisRow],
+ nOtherTab, nThisEndCol, pOtherCols.get() );
+
+ if ( nMatch1 >= nMatch2 ) // without columns ?
+ {
+ // reset columns
+ for (nThisCol = 0; nThisCol<=nThisEndCol; nThisCol++)
+ pOtherCols[nThisCol] = nThisCol;
+
+ // swap row-arrays (they get both deleted anyway)
+ pTempRows.swap(pOtherRows);
+ }
+ else
+ {
+ // remains for pOtherCols, pOtherRows
+ }
+
+ // Generate Change-Actions
+ // 1) columns from the right
+ // 2) rows from below
+ // 3) single cells in normal order
+
+ // Actions for inserted/deleted columns
+
+ SCCOL nLastOtherCol = static_cast<SCCOL>(nOtherEndCol + 1);
+ // nThisEndCol ... 0
+ for ( nThisCol = nThisEndCol+1; nThisCol > 0; )
+ {
+ --nThisCol;
+ SCCOL nOtherCol = static_cast<SCCOL>(pOtherCols[nThisCol]);
+ if ( ValidCol(nOtherCol) && nOtherCol+1 < nLastOtherCol )
+ {
+ // gap -> deleted
+ ScRange aDelRange( nOtherCol+1, 0, nOtherTab,
+ nLastOtherCol-1, MaxRow(), nOtherTab );
+ pChangeTrack->AppendDeleteRange( aDelRange, &rOtherDoc, n1, n2 );
+ }
+ if ( nOtherCol > MaxCol() ) // inserted
+ {
+ // combine
+ if ( nThisCol == nThisEndCol || ValidCol(static_cast<SCCOL>(pOtherCols[nThisCol+1])) )
+ {
+ SCCOL nFirstNew = nThisCol;
+ while ( nFirstNew > 0 && pOtherCols[nFirstNew-1] > MaxCol() )
+ --nFirstNew;
+ SCCOL nDiff = nThisCol - nFirstNew;
+ ScRange aRange( nLastOtherCol, 0, nOtherTab,
+ nLastOtherCol+nDiff, MaxRow(), nOtherTab );
+ pChangeTrack->AppendInsert( aRange );
+ }
+ }
+ else
+ nLastOtherCol = nOtherCol;
+ }
+ if ( nLastOtherCol > 0 ) // deleted at the very top
+ {
+ ScRange aDelRange( 0, 0, nOtherTab,
+ nLastOtherCol-1, MaxRow(), nOtherTab );
+ pChangeTrack->AppendDeleteRange( aDelRange, &rOtherDoc, n1, n2 );
+ }
+
+ // Actions for inserted/deleted rows
+
+ SCROW nLastOtherRow = nOtherEndRow + 1;
+ // nThisEndRow ... 0
+ for ( nThisRow = nThisEndRow+1; nThisRow > 0; )
+ {
+ --nThisRow;
+ SCROW nOtherRow = pOtherRows[nThisRow];
+ if ( ValidRow(nOtherRow) && nOtherRow+1 < nLastOtherRow )
+ {
+ // gap -> deleted
+ ScRange aDelRange( 0, nOtherRow+1, nOtherTab,
+ MaxCol(), nLastOtherRow-1, nOtherTab );
+ pChangeTrack->AppendDeleteRange( aDelRange, &rOtherDoc, n1, n2 );
+ }
+ if ( nOtherRow > MaxRow() ) // inserted
+ {
+ // combine
+ if ( nThisRow == nThisEndRow || ValidRow(pOtherRows[nThisRow+1]) )
+ {
+ SCROW nFirstNew = nThisRow;
+ while ( nFirstNew > 0 && pOtherRows[nFirstNew-1] > MaxRow() )
+ --nFirstNew;
+ SCROW nDiff = nThisRow - nFirstNew;
+ ScRange aRange( 0, nLastOtherRow, nOtherTab,
+ MaxCol(), nLastOtherRow+nDiff, nOtherTab );
+ pChangeTrack->AppendInsert( aRange );
+ }
+ }
+ else
+ nLastOtherRow = nOtherRow;
+ }
+ if ( nLastOtherRow > 0 ) // deleted at the very top
+ {
+ ScRange aDelRange( 0, 0, nOtherTab,
+ MaxCol(), nLastOtherRow-1, nOtherTab );
+ pChangeTrack->AppendDeleteRange( aDelRange, &rOtherDoc, n1, n2 );
+ }
+
+ // walk rows to find single cells
+
+ for (nThisRow = 0; nThisRow <= nThisEndRow; nThisRow++)
+ {
+ SCROW nOtherRow = pOtherRows[nThisRow];
+ for (nThisCol = 0; nThisCol <= nThisEndCol; nThisCol++)
+ {
+ SCCOL nOtherCol = static_cast<SCCOL>(pOtherCols[nThisCol]);
+ ScAddress aThisPos( nThisCol, nThisRow, nThisTab );
+ ScCellValue aThisCell;
+ aThisCell.assign(*this, aThisPos);
+ ScCellValue aOtherCell; // start empty
+ if ( ValidCol(nOtherCol) && ValidRow(nOtherRow) )
+ {
+ ScAddress aOtherPos( nOtherCol, nOtherRow, nOtherTab );
+ aOtherCell.assign(rOtherDoc, aOtherPos);
+ }
+
+ if (!aThisCell.equalsWithoutFormat(aOtherCell))
+ {
+ ScRange aRange( aThisPos );
+ ScChangeActionContent* pAction = new ScChangeActionContent( aRange );
+ pAction->SetOldValue(aOtherCell, &rOtherDoc, this);
+ pAction->SetNewValue(aThisCell, this);
+ pChangeTrack->Append( pAction );
+ }
+ }
+ aProgress.SetStateOnPercent(nProgressStart+nThisRow);
+ }
+ }
+ }
+}
+
+sal_Unicode ScDocument::GetSheetSeparator() const
+{
+ const ScCompiler::Convention* pConv = ScCompiler::GetRefConvention(
+ FormulaGrammar::extractRefConvention( GetGrammar()));
+ assert(pConv);
+ return pConv ? pConv->getSpecialSymbol( ScCompiler::Convention::SHEET_SEPARATOR) : '.';
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/documen5.cxx b/sc/source/core/data/documen5.cxx
new file mode 100644
index 000000000..c4f76433b
--- /dev/null
+++ b/sc/source/core/data/documen5.cxx
@@ -0,0 +1,657 @@
+/* -*- 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/util/XModifiable.hpp>
+#include <com/sun/star/chart/ChartDataRowSource.hpp>
+#include <com/sun/star/chart2/XChartDocument.hpp>
+#include <com/sun/star/chart2/data/XDataProvider.hpp>
+#include <com/sun/star/chart2/data/XDataReceiver.hpp>
+#include <com/sun/star/embed/XEmbeddedObject.hpp>
+
+#include <sfx2/objsh.hxx>
+#include <svx/svditer.hxx>
+#include <svx/svdoole2.hxx>
+#include <svtools/embedhlp.hxx>
+
+#include <document.hxx>
+#include <table.hxx>
+#include <drwlayer.hxx>
+#include <chartlis.hxx>
+#include <chartlock.hxx>
+#include <refupdat.hxx>
+
+#include <miscuno.hxx>
+#include <chart2uno.hxx>
+#include <charthelper.hxx>
+
+using namespace ::com::sun::star;
+
+static void lcl_GetChartParameters( const uno::Reference< chart2::XChartDocument >& xChartDoc,
+ OUString& rRanges, chart::ChartDataRowSource& rDataRowSource,
+ bool& rHasCategories, bool& rFirstCellAsLabel )
+{
+ rHasCategories = rFirstCellAsLabel = false; // default if not in sequence
+
+ uno::Reference< chart2::data::XDataReceiver > xReceiver( xChartDoc, uno::UNO_QUERY );
+
+ uno::Reference< chart2::data::XDataSource > xDataSource = xReceiver->getUsedData();
+ uno::Reference< chart2::data::XDataProvider > xProvider = xChartDoc->getDataProvider();
+
+ if ( !xProvider.is() )
+ return;
+
+ const uno::Sequence< beans::PropertyValue > aArgs( xProvider->detectArguments( xDataSource ) );
+
+ for (const beans::PropertyValue& rProp : aArgs)
+ {
+ OUString aPropName(rProp.Name);
+
+ if ( aPropName == "CellRangeRepresentation" )
+ rProp.Value >>= rRanges;
+ else if ( aPropName == "DataRowSource" )
+ rDataRowSource = static_cast<chart::ChartDataRowSource>(ScUnoHelpFunctions::GetEnumFromAny( rProp.Value ));
+ else if ( aPropName == "HasCategories" )
+ rHasCategories = ScUnoHelpFunctions::GetBoolFromAny( rProp.Value );
+ else if ( aPropName == "FirstCellAsLabel" )
+ rFirstCellAsLabel = ScUnoHelpFunctions::GetBoolFromAny( rProp.Value );
+ }
+}
+
+static void lcl_SetChartParameters( const uno::Reference< chart2::data::XDataReceiver >& xReceiver,
+ const OUString& rRanges, chart::ChartDataRowSource eDataRowSource,
+ bool bHasCategories, bool bFirstCellAsLabel )
+{
+ if ( !xReceiver.is() )
+ return;
+
+ uno::Sequence< beans::PropertyValue > aArgs{
+ beans::PropertyValue(
+ "CellRangeRepresentation", -1,
+ uno::Any( rRanges ), beans::PropertyState_DIRECT_VALUE ),
+ beans::PropertyValue(
+ "HasCategories", -1,
+ uno::Any( bHasCategories ), beans::PropertyState_DIRECT_VALUE ),
+ beans::PropertyValue(
+ "FirstCellAsLabel", -1,
+ uno::Any( bFirstCellAsLabel ), beans::PropertyState_DIRECT_VALUE ),
+ beans::PropertyValue(
+ "DataRowSource", -1,
+ uno::Any( eDataRowSource ), beans::PropertyState_DIRECT_VALUE )
+ };
+ xReceiver->setArguments( aArgs );
+}
+
+bool ScDocument::HasChartAtPoint( SCTAB nTab, const Point& rPos, OUString& rName )
+{
+ if (mpDrawLayer && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ {
+ SdrPage* pPage = mpDrawLayer->GetPage(static_cast<sal_uInt16>(nTab));
+ OSL_ENSURE(pPage,"Page ?");
+
+ SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups );
+ SdrObject* pObject = aIter.Next();
+ while (pObject)
+ {
+ if ( pObject->GetObjIdentifier() == SdrObjKind::OLE2 &&
+ pObject->GetCurrentBoundRect().Contains(rPos) )
+ {
+ // also Chart-Objects that are not in the Collection
+
+ if (IsChart(pObject))
+ {
+ rName = static_cast<SdrOle2Obj*>(pObject)->GetPersistName();
+ return true;
+ }
+ }
+ pObject = aIter.Next();
+ }
+ }
+
+ rName.clear();
+ return false; // nothing found
+}
+
+void ScDocument::UpdateChartArea( const OUString& rChartName,
+ const ScRange& rNewArea, bool bColHeaders, bool bRowHeaders,
+ bool bAdd )
+{
+ ScRangeListRef aRLR( new ScRangeList(rNewArea) );
+ UpdateChartArea( rChartName, aRLR, bColHeaders, bRowHeaders, bAdd );
+}
+
+uno::Reference< chart2::XChartDocument > ScDocument::GetChartByName( std::u16string_view rChartName )
+{
+ uno::Reference< chart2::XChartDocument > xReturn;
+
+ if (mpDrawLayer)
+ {
+ sal_uInt16 nCount = mpDrawLayer->GetPageCount();
+ SCTAB nSize = static_cast<SCTAB>(maTabs.size());
+ for (sal_uInt16 nTab=0; nTab<nCount && nTab < nSize; nTab++)
+ {
+ SdrPage* pPage = mpDrawLayer->GetPage(nTab);
+ OSL_ENSURE(pPage,"Page ?");
+
+ SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups );
+ SdrObject* pObject = aIter.Next();
+ while (pObject)
+ {
+ if ( pObject->GetObjIdentifier() == SdrObjKind::OLE2 &&
+ static_cast<SdrOle2Obj*>(pObject)->GetPersistName() == rChartName )
+ {
+ xReturn.set( ScChartHelper::GetChartFromSdrObject( pObject ) );
+ return xReturn;
+ }
+ pObject = aIter.Next();
+ }
+ }
+ }
+ return xReturn;
+}
+
+void ScDocument::GetChartRanges( std::u16string_view rChartName, ::std::vector< ScRangeList >& rRangesVector, const ScDocument& rSheetNameDoc )
+{
+ rRangesVector.clear();
+ uno::Reference< chart2::XChartDocument > xChartDoc( GetChartByName( rChartName ) );
+ if ( xChartDoc.is() )
+ {
+ std::vector< OUString > aRangeStrings;
+ ScChartHelper::GetChartRanges( xChartDoc, aRangeStrings );
+ for(const OUString & aRangeString : aRangeStrings)
+ {
+ ScRangeList aRanges;
+ aRanges.Parse( aRangeString, rSheetNameDoc, rSheetNameDoc.GetAddressConvention() );
+ rRangesVector.push_back(aRanges);
+ }
+ }
+}
+
+void ScDocument::SetChartRanges( std::u16string_view rChartName, const ::std::vector< ScRangeList >& rRangesVector )
+{
+ uno::Reference< chart2::XChartDocument > xChartDoc( GetChartByName( rChartName ) );
+ if ( !xChartDoc.is() )
+ return;
+
+ sal_Int32 nCount = static_cast<sal_Int32>( rRangesVector.size() );
+ uno::Sequence< OUString > aRangeStrings(nCount);
+ auto aRangeStringsRange = asNonConstRange(aRangeStrings);
+ for( sal_Int32 nN=0; nN<nCount; nN++ )
+ {
+ ScRangeList aScRangeList( rRangesVector[nN] );
+ OUString sRangeStr;
+ aScRangeList.Format( sRangeStr, ScRefFlags::RANGE_ABS_3D, *this, GetAddressConvention() );
+ aRangeStringsRange[nN]=sRangeStr;
+ }
+ ScChartHelper::SetChartRanges( xChartDoc, aRangeStrings );
+}
+
+void ScDocument::GetOldChartParameters( std::u16string_view rName,
+ ScRangeList& rRanges, bool& rColHeaders, bool& rRowHeaders )
+{
+ // used for undo of changing chart source area
+
+ if (!mpDrawLayer)
+ return;
+
+ sal_uInt16 nCount = mpDrawLayer->GetPageCount();
+ for (sal_uInt16 nTab=0; nTab<nCount && nTab < static_cast<SCTAB>(maTabs.size()); nTab++)
+ {
+ SdrPage* pPage = mpDrawLayer->GetPage(nTab);
+ OSL_ENSURE(pPage,"Page ?");
+
+ SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups );
+ SdrObject* pObject = aIter.Next();
+ while (pObject)
+ {
+ if ( pObject->GetObjIdentifier() == SdrObjKind::OLE2 &&
+ static_cast<SdrOle2Obj*>(pObject)->GetPersistName() == rName )
+ {
+ uno::Reference< chart2::XChartDocument > xChartDoc( ScChartHelper::GetChartFromSdrObject( pObject ) );
+ if ( xChartDoc.is() )
+ {
+ chart::ChartDataRowSource eDataRowSource = chart::ChartDataRowSource_COLUMNS;
+ bool bHasCategories = false;
+ bool bFirstCellAsLabel = false;
+ OUString aRangesStr;
+ lcl_GetChartParameters( xChartDoc, aRangesStr, eDataRowSource, bHasCategories, bFirstCellAsLabel );
+
+ rRanges.Parse( aRangesStr, *this, GetAddressConvention());
+ if ( eDataRowSource == chart::ChartDataRowSource_COLUMNS )
+ {
+ rRowHeaders = bHasCategories;
+ rColHeaders = bFirstCellAsLabel;
+ }
+ else
+ {
+ rColHeaders = bHasCategories;
+ rRowHeaders = bFirstCellAsLabel;
+ }
+ }
+ return;
+ }
+ pObject = aIter.Next();
+ }
+ }
+}
+
+void ScDocument::UpdateChartArea( const OUString& rChartName,
+ const ScRangeListRef& rNewList, bool bColHeaders, bool bRowHeaders,
+ bool bAdd )
+{
+ if (!mpDrawLayer)
+ return;
+
+ for (SCTAB nTab=0; nTab< static_cast<SCTAB>(maTabs.size()) && maTabs[nTab]; nTab++)
+ {
+ SdrPage* pPage = mpDrawLayer->GetPage(static_cast<sal_uInt16>(nTab));
+ OSL_ENSURE(pPage,"Page ?");
+
+ SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups );
+ SdrObject* pObject = aIter.Next();
+ while (pObject)
+ {
+ if ( pObject->GetObjIdentifier() == SdrObjKind::OLE2 &&
+ static_cast<SdrOle2Obj*>(pObject)->GetPersistName() == rChartName )
+ {
+ uno::Reference< chart2::XChartDocument > xChartDoc( ScChartHelper::GetChartFromSdrObject( pObject ) );
+ uno::Reference< chart2::data::XDataReceiver > xReceiver( xChartDoc, uno::UNO_QUERY );
+ if ( xChartDoc.is() && xReceiver.is() )
+ {
+ ScRangeListRef aNewRanges;
+ chart::ChartDataRowSource eDataRowSource = chart::ChartDataRowSource_COLUMNS;
+ bool bHasCategories = false;
+ bool bFirstCellAsLabel = false;
+ OUString aRangesStr;
+ lcl_GetChartParameters( xChartDoc, aRangesStr, eDataRowSource, bHasCategories, bFirstCellAsLabel );
+
+ bool bInternalData = xChartDoc->hasInternalDataProvider();
+
+ if ( bAdd && !bInternalData )
+ {
+ // append to old ranges, keep other settings
+
+ aNewRanges = new ScRangeList;
+ aNewRanges->Parse( aRangesStr, *this, GetAddressConvention());
+ aNewRanges->insert( aNewRanges->begin(), rNewList->begin(), rNewList->end() );
+ }
+ else
+ {
+ // directly use new ranges (only eDataRowSource is used from old settings)
+
+ if ( eDataRowSource == chart::ChartDataRowSource_COLUMNS )
+ {
+ bHasCategories = bRowHeaders;
+ bFirstCellAsLabel = bColHeaders;
+ }
+ else
+ {
+ bHasCategories = bColHeaders;
+ bFirstCellAsLabel = bRowHeaders;
+ }
+ aNewRanges = rNewList;
+ }
+
+ if ( bInternalData && mpShell )
+ {
+ // Calc -> DataProvider
+ uno::Reference< chart2::data::XDataProvider > xDataProvider = new ScChart2DataProvider( this );
+ xReceiver->attachDataProvider( xDataProvider );
+ uno::Reference< util::XNumberFormatsSupplier > xNumberFormatsSupplier(
+ mpShell->GetModel(), uno::UNO_QUERY );
+ xReceiver->attachNumberFormatsSupplier( xNumberFormatsSupplier );
+ }
+
+ OUString sRangeStr;
+ aNewRanges->Format( sRangeStr, ScRefFlags::RANGE_ABS_3D, *this, GetAddressConvention() );
+
+ lcl_SetChartParameters( xReceiver, sRangeStr, eDataRowSource, bHasCategories, bFirstCellAsLabel );
+
+ pChartListenerCollection->ChangeListening( rChartName, aNewRanges );
+
+ return; // do not search anymore
+ }
+ }
+ pObject = aIter.Next();
+ }
+ }
+}
+
+void ScDocument::UpdateChart( const OUString& rChartName )
+{
+ if (!mpDrawLayer || bInDtorClear)
+ return;
+ uno::Reference< chart2::XChartDocument > xChartDoc( GetChartByName( rChartName ) );
+ if (xChartDoc && (!mpShell || mpShell->IsEnableSetModified()))
+ {
+ try
+ {
+ uno::Reference< util::XModifiable > xModif( xChartDoc, uno::UNO_QUERY_THROW );
+ if (apTemporaryChartLock)
+ apTemporaryChartLock->AlsoLockThisChart( uno::Reference< frame::XModel >( xModif, uno::UNO_QUERY ) );
+ xModif->setModified( true );
+ }
+ catch ( uno::Exception& )
+ {
+ }
+ }
+
+ // After the update, chart keeps track of its own data source ranges,
+ // the listener doesn't need to listen anymore, except the chart has
+ // an internal data provider.
+ if ( !( xChartDoc.is() && xChartDoc->hasInternalDataProvider() ) && pChartListenerCollection )
+ {
+ pChartListenerCollection->ChangeListening( rChartName, new ScRangeList );
+ }
+}
+
+void ScDocument::RestoreChartListener( const OUString& rName )
+{
+ if (!pChartListenerCollection)
+ return;
+
+ // Read the data ranges from the chart object, and start listening to those ranges again
+ // (called when a chart is saved, because then it might be swapped out and stop listening itself).
+
+ uno::Reference< embed::XEmbeddedObject > xObject = FindOleObjectByName( rName );
+ if ( !xObject.is() )
+ return;
+
+ uno::Reference< util::XCloseable > xComponent = xObject->getComponent();
+ uno::Reference< chart2::XChartDocument > xChartDoc( xComponent, uno::UNO_QUERY );
+ uno::Reference< chart2::data::XDataReceiver > xReceiver( xComponent, uno::UNO_QUERY );
+ if ( !xChartDoc.is() || !xReceiver.is() || xChartDoc->hasInternalDataProvider() )
+ return;
+
+ const uno::Sequence<OUString> aRepresentations( xReceiver->getUsedRangeRepresentations() );
+ ScRangeListRef aRanges = new ScRangeList;
+ for ( const auto& rRepresentation : aRepresentations )
+ {
+ ScRange aRange;
+ ScAddress::Details aDetails(GetAddressConvention(), 0, 0);
+ if ( aRange.ParseAny( rRepresentation, *this, aDetails ) & ScRefFlags::VALID )
+ aRanges->push_back( aRange );
+ }
+
+ pChartListenerCollection->ChangeListening( rName, aRanges );
+}
+
+void ScDocument::UpdateChartRef( UpdateRefMode eUpdateRefMode,
+ SCCOL nCol1, SCROW nRow1, SCTAB nTab1,
+ SCCOL nCol2, SCROW nRow2, SCTAB nTab2,
+ SCCOL nDx, SCROW nDy, SCTAB nDz )
+{
+ if (!mpDrawLayer)
+ return;
+
+ ScChartListenerCollection::ListenersType& rListeners = pChartListenerCollection->getListeners();
+ for (auto const& it : rListeners)
+ {
+ ScChartListener *const pChartListener = it.second.get();
+ ScRangeListRef aRLR( pChartListener->GetRangeList() );
+ ScRangeListRef aNewRLR( new ScRangeList );
+ bool bChanged = false;
+ bool bDataChanged = false;
+ for ( size_t i = 0, nListSize = aRLR->size(); i < nListSize; ++i )
+ {
+ ScRange& rRange = (*aRLR)[i];
+ SCCOL theCol1 = rRange.aStart.Col();
+ SCROW theRow1 = rRange.aStart.Row();
+ SCTAB theTab1 = rRange.aStart.Tab();
+ SCCOL theCol2 = rRange.aEnd.Col();
+ SCROW theRow2 = rRange.aEnd.Row();
+ SCTAB theTab2 = rRange.aEnd.Tab();
+ ScRefUpdateRes eRes = ScRefUpdate::Update(
+ this, eUpdateRefMode,
+ nCol1,nRow1,nTab1, nCol2,nRow2,nTab2,
+ nDx,nDy,nDz,
+ theCol1,theRow1,theTab1,
+ theCol2,theRow2,theTab2 );
+ if ( eRes != UR_NOTHING )
+ {
+ bChanged = true;
+ aNewRLR->push_back( ScRange(
+ theCol1, theRow1, theTab1,
+ theCol2, theRow2, theTab2 ));
+ if ( eUpdateRefMode == URM_INSDEL
+ && !bDataChanged
+ && (eRes == UR_INVALID ||
+ ((rRange.aEnd.Col() - rRange.aStart.Col()
+ != theCol2 - theCol1)
+ || (rRange.aEnd.Row() - rRange.aStart.Row()
+ != theRow2 - theRow1)
+ || (rRange.aEnd.Tab() - rRange.aStart.Tab()
+ != theTab2 - theTab1))) )
+ {
+ bDataChanged = true;
+ }
+ }
+ else
+ aNewRLR->push_back( rRange );
+ }
+ if ( bChanged )
+ {
+ // Force the chart to be loaded now, so it registers itself for UNO events.
+ // UNO broadcasts are done after UpdateChartRef, so the chart will get this
+ // reference change.
+
+ uno::Reference<embed::XEmbeddedObject> xIPObj =
+ FindOleObjectByName(pChartListener->GetName());
+
+ svt::EmbeddedObjectRef::TryRunningState( xIPObj );
+
+ // After the change, chart keeps track of its own data source ranges,
+ // the listener doesn't need to listen anymore, except the chart has
+ // an internal data provider.
+ bool bInternalDataProvider = false;
+ if ( xIPObj.is() )
+ {
+ try
+ {
+ uno::Reference< chart2::XChartDocument > xChartDoc( xIPObj->getComponent(), uno::UNO_QUERY_THROW );
+ bInternalDataProvider = xChartDoc->hasInternalDataProvider();
+ }
+ catch ( uno::Exception& )
+ {
+ }
+ }
+ if ( bInternalDataProvider )
+ {
+ pChartListener->ChangeListening( aNewRLR, bDataChanged );
+ }
+ else
+ {
+ pChartListener->ChangeListening( new ScRangeList, bDataChanged );
+ }
+ }
+ }
+}
+
+void ScDocument::SetChartRangeList( std::u16string_view rChartName,
+ const ScRangeListRef& rNewRangeListRef )
+{
+ // called from ChartListener
+
+ if (!mpDrawLayer)
+ return;
+
+ for (SCTAB nTab=0; nTab< static_cast<SCTAB>(maTabs.size()) && maTabs[nTab]; nTab++)
+ {
+ SdrPage* pPage = mpDrawLayer->GetPage(static_cast<sal_uInt16>(nTab));
+ OSL_ENSURE(pPage,"Page ?");
+
+ SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups );
+ SdrObject* pObject = aIter.Next();
+ while (pObject)
+ {
+ if ( pObject->GetObjIdentifier() == SdrObjKind::OLE2 &&
+ static_cast<SdrOle2Obj*>(pObject)->GetPersistName() == rChartName )
+ {
+ uno::Reference< chart2::XChartDocument > xChartDoc( ScChartHelper::GetChartFromSdrObject( pObject ) );
+ uno::Reference< chart2::data::XDataReceiver > xReceiver( xChartDoc, uno::UNO_QUERY );
+ if ( xChartDoc.is() && xReceiver.is() )
+ {
+ chart::ChartDataRowSource eDataRowSource = chart::ChartDataRowSource_COLUMNS;
+ bool bHasCategories = false;
+ bool bFirstCellAsLabel = false;
+ OUString aRangesStr;
+ lcl_GetChartParameters( xChartDoc, aRangesStr, eDataRowSource, bHasCategories, bFirstCellAsLabel );
+
+ OUString sRangeStr;
+ rNewRangeListRef->Format( sRangeStr, ScRefFlags::RANGE_ABS_3D, *this, GetAddressConvention() );
+
+ lcl_SetChartParameters( xReceiver, sRangeStr, eDataRowSource, bHasCategories, bFirstCellAsLabel );
+
+ // don't modify pChartListenerCollection here, called from there
+ return;
+ }
+ }
+ pObject = aIter.Next();
+ }
+ }
+}
+
+bool ScDocument::HasData( SCCOL nCol, SCROW nRow, SCTAB nTab )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab]
+ && nCol < maTabs[nTab]->GetAllocatedColumnsCount())
+ return maTabs[nTab]->HasData( nCol, nRow );
+ else
+ return false;
+}
+
+uno::Reference< embed::XEmbeddedObject >
+ ScDocument::FindOleObjectByName( std::u16string_view rName )
+{
+ if (!mpDrawLayer)
+ return uno::Reference< embed::XEmbeddedObject >();
+
+ // take the pages here from Draw-Layer, as they might not match with the tables
+ // (e.g. delete Redo of table; Draw-Redo happens before DeleteTab)
+
+ sal_uInt16 nCount = mpDrawLayer->GetPageCount();
+ for (sal_uInt16 nTab=0; nTab<nCount; nTab++)
+ {
+ SdrPage* pPage = mpDrawLayer->GetPage(nTab);
+ OSL_ENSURE(pPage,"Page ?");
+
+ SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups );
+ SdrObject* pObject = aIter.Next();
+ while (pObject)
+ {
+ if ( pObject->GetObjIdentifier() == SdrObjKind::OLE2 )
+ {
+ SdrOle2Obj * pOleObject ( dynamic_cast< SdrOle2Obj * >( pObject ));
+ if( pOleObject &&
+ pOleObject->GetPersistName() == rName )
+ {
+ return pOleObject->GetObjRef();
+ }
+ }
+ pObject = aIter.Next();
+ }
+ }
+
+ return uno::Reference< embed::XEmbeddedObject >();
+}
+
+void ScDocument::UpdateChartListenerCollection()
+{
+ assert(pChartListenerCollection);
+
+ bChartListenerCollectionNeedsUpdate = false;
+ if (!mpDrawLayer)
+ return;
+
+ for (SCTAB nTab=0; nTab< static_cast<SCTAB>(maTabs.size()); nTab++)
+ {
+ if (!maTabs[nTab])
+ continue;
+
+ SdrPage* pPage = mpDrawLayer->GetPage(static_cast<sal_uInt16>(nTab));
+ OSL_ENSURE(pPage,"Page ?");
+
+ if (!pPage)
+ continue;
+
+ SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups );
+ ScChartListenerCollection::StringSetType& rNonOleObjects =
+ pChartListenerCollection->getNonOleObjectNames();
+
+ for (SdrObject* pObject = aIter.Next(); pObject; pObject = aIter.Next())
+ {
+ if ( pObject->GetObjIdentifier() != SdrObjKind::OLE2 )
+ continue;
+
+ OUString aObjName = static_cast<SdrOle2Obj*>(pObject)->GetPersistName();
+ ScChartListener* pListener = pChartListenerCollection->findByName(aObjName);
+
+ if (pListener)
+ pListener->SetUsed(true);
+ else if (rNonOleObjects.count(aObjName) > 0)
+ {
+ // non-chart OLE object -> don't touch
+ }
+ else
+ {
+ uno::Reference< embed::XEmbeddedObject > xIPObj = static_cast<SdrOle2Obj*>(pObject)->GetObjRef();
+ OSL_ENSURE( xIPObj.is(), "No embedded object is given!");
+ uno::Reference< css::chart2::data::XDataReceiver > xReceiver;
+ if( xIPObj.is())
+ xReceiver.set( xIPObj->getComponent(), uno::UNO_QUERY );
+
+ // if the object is a chart2::XDataReceiver, we must attach as XDataProvider
+ if( xReceiver.is() &&
+ !PastingDrawFromOtherDoc())
+ {
+ // NOTE: this currently does not work as we are
+ // unable to set the data. So a chart from the
+ // same document is treated like a chart with
+ // own data for the time being.
+
+ // data provider
+ // number formats supplier
+
+ // data ?
+ // how to set?? Defined in XML-file, which is already loaded!!!
+ // => we have to do this stuff here, BEFORE the chart is actually loaded
+ }
+
+ // put into list of other ole objects, so the object doesn't have to
+ // be swapped in the next time UpdateChartListenerCollection is called
+ //TODO: remove names when objects are no longer there?
+ // (object names aren't used again before reloading the document)
+
+ rNonOleObjects.insert(aObjName);
+ }
+ }
+ }
+ // delete all that are not set SetUsed
+ pChartListenerCollection->FreeUnused();
+}
+
+void ScDocument::AddOLEObjectToCollection(const OUString& rName)
+{
+ assert(pChartListenerCollection);
+ ScChartListenerCollection::StringSetType& rNonOleObjects =
+ pChartListenerCollection->getNonOleObjectNames();
+
+ rNonOleObjects.insert(rName);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/documen6.cxx b/sc/source/core/data/documen6.cxx
new file mode 100644
index 000000000..49d433ffe
--- /dev/null
+++ b/sc/source/core/data/documen6.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 <scitems.hxx>
+
+#include <com/sun/star/i18n/BreakIterator.hpp>
+#include <com/sun/star/i18n/ScriptType.hpp>
+#include <comphelper/processfactory.hxx>
+
+#include <document.hxx>
+#include <cellform.hxx>
+#include <patattr.hxx>
+#include <scrdata.hxx>
+#include <poolhelp.hxx>
+#include <attrib.hxx>
+#include <columnspanset.hxx>
+#include <table.hxx>
+
+using namespace com::sun::star;
+
+// this file is compiled with exceptions enabled
+// put functions here that need exceptions!
+
+const uno::Reference< i18n::XBreakIterator >& ScDocument::GetBreakIterator()
+{
+ if ( !pScriptTypeData )
+ pScriptTypeData.reset( new ScScriptTypeData );
+ if ( !pScriptTypeData->xBreakIter.is() )
+ {
+ pScriptTypeData->xBreakIter = i18n::BreakIterator::create( comphelper::getProcessComponentContext() );
+ }
+ return pScriptTypeData->xBreakIter;
+}
+
+bool ScDocument::HasStringWeakCharacters( const OUString& rString )
+{
+ if (!rString.isEmpty())
+ {
+ uno::Reference<i18n::XBreakIterator> xBreakIter = GetBreakIterator();
+ if ( xBreakIter.is() )
+ {
+ sal_Int32 nLen = rString.getLength();
+
+ sal_Int32 nPos = 0;
+ do
+ {
+ sal_Int16 nType = xBreakIter->getScriptType( rString, nPos );
+ if ( nType == i18n::ScriptType::WEAK )
+ return true; // found
+
+ nPos = xBreakIter->endOfScript( rString, nPos, nType );
+ }
+ while ( nPos >= 0 && nPos < nLen );
+ }
+ }
+
+ return false; // none found
+}
+
+SvtScriptType ScDocument::GetStringScriptType( const OUString& rString )
+{
+ SvtScriptType nRet = SvtScriptType::NONE;
+ if (!rString.isEmpty())
+ {
+ uno::Reference<i18n::XBreakIterator> xBreakIter = GetBreakIterator();
+ if ( xBreakIter.is() )
+ {
+ sal_Int32 nLen = rString.getLength();
+
+ sal_Int32 nPos = 0;
+ do
+ {
+ sal_Int16 nType = xBreakIter->getScriptType( rString, nPos );
+ switch ( nType )
+ {
+ case i18n::ScriptType::LATIN:
+ nRet |= SvtScriptType::LATIN;
+ break;
+ case i18n::ScriptType::ASIAN:
+ nRet |= SvtScriptType::ASIAN;
+ break;
+ case i18n::ScriptType::COMPLEX:
+ nRet |= SvtScriptType::COMPLEX;
+ break;
+ // WEAK is ignored
+ }
+ nPos = xBreakIter->endOfScript( rString, nPos, nType );
+ }
+ while ( nPos >= 0 && nPos < nLen );
+ }
+ }
+ return nRet;
+}
+
+SvtScriptType ScDocument::GetCellScriptType( const ScAddress& rPos, sal_uInt32 nNumberFormat,
+ const ScRefCellValue* pCell )
+{
+ SvtScriptType nStored = GetScriptType(rPos);
+ if ( nStored != SvtScriptType::UNKNOWN ) // stored value valid?
+ return nStored; // use stored value
+
+ const Color* pColor;
+ OUString aStr;
+ if( pCell )
+ aStr = ScCellFormat::GetString(*pCell, nNumberFormat, &pColor, *mxPoolHelper->GetFormTable(), *this);
+ else
+ aStr = ScCellFormat::GetString(*this, rPos, nNumberFormat, &pColor, *mxPoolHelper->GetFormTable());
+
+ SvtScriptType nRet = GetStringScriptType( aStr );
+
+ SetScriptType(rPos, nRet); // store for later calls
+
+ return nRet;
+}
+
+SvtScriptType ScDocument::GetScriptType( SCCOL nCol, SCROW nRow, SCTAB nTab, const ScRefCellValue* pCell )
+{
+ // if script type is set, don't have to get number formats
+
+ ScAddress aPos(nCol, nRow, nTab);
+ SvtScriptType nStored = GetScriptType(aPos);
+ if ( nStored != SvtScriptType::UNKNOWN ) // stored value valid?
+ return nStored; // use stored value
+
+ // include number formats from conditional formatting
+
+ const ScPatternAttr* pPattern = GetPattern( nCol, nRow, nTab );
+ if (!pPattern) return SvtScriptType::NONE;
+ const SfxItemSet* pCondSet = nullptr;
+ if ( !pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData().empty() )
+ pCondSet = GetCondResult( nCol, nRow, nTab );
+
+ sal_uInt32 nFormat = pPattern->GetNumberFormat( mxPoolHelper->GetFormTable(), pCondSet );
+
+ return GetCellScriptType(aPos, nFormat, pCell);
+}
+
+namespace {
+
+class ScriptTypeAggregator : public sc::ColumnSpanSet::Action
+{
+ ScDocument& mrDoc;
+ sc::ColumnBlockPosition maBlockPos;
+ SvtScriptType mnScriptType;
+
+public:
+ explicit ScriptTypeAggregator(ScDocument& rDoc) : mrDoc(rDoc), mnScriptType(SvtScriptType::NONE) {}
+
+ virtual void startColumn(SCTAB nTab, SCCOL nCol) override
+ {
+ mrDoc.InitColumnBlockPosition(maBlockPos, nTab, nCol);
+ }
+
+ virtual void execute(const ScAddress& rPos, SCROW nLength, bool bVal) override
+ {
+ if (!bVal)
+ return;
+
+ mnScriptType |= mrDoc.GetRangeScriptType(maBlockPos, rPos, nLength);
+ };
+
+ SvtScriptType getScriptType() const { return mnScriptType; }
+};
+
+}
+
+SvtScriptType ScDocument::GetRangeScriptType(
+ sc::ColumnBlockPosition& rBlockPos, const ScAddress& rPos, SCROW nLength )
+{
+ if (!TableExists(rPos.Tab()))
+ return SvtScriptType::NONE;
+
+ return maTabs[rPos.Tab()]->GetRangeScriptType(rBlockPos, rPos.Col(), rPos.Row(), rPos.Row()+nLength-1);
+}
+
+SvtScriptType ScDocument::GetRangeScriptType( const ScRangeList& rRanges )
+{
+ sc::ColumnSpanSet aSet;
+ for (size_t i = 0, n = rRanges.size(); i < n; ++i)
+ {
+ const ScRange& rRange = rRanges[i];
+ SCTAB nTab = rRange.aStart.Tab();
+ SCROW nRow1 = rRange.aStart.Row();
+ SCROW nRow2 = rRange.aEnd.Row();
+ for (SCCOL nCol = rRange.aStart.Col(); nCol <= rRange.aEnd.Col(); ++nCol)
+ aSet.set(*this, nTab, nCol, nRow1, nRow2, true);
+ }
+
+ ScriptTypeAggregator aAction(*this);
+ aSet.executeAction(*this, aAction);
+ return aAction.getScriptType();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/documen7.cxx b/sc/source/core/data/documen7.cxx
new file mode 100644
index 000000000..61f6b68f0
--- /dev/null
+++ b/sc/source/core/data/documen7.cxx
@@ -0,0 +1,621 @@
+/* -*- 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/log.hxx>
+#include <osl/diagnose.h>
+
+#include <document.hxx>
+#include <brdcst.hxx>
+#include <bcaslot.hxx>
+#include <formulacell.hxx>
+#include <table.hxx>
+#include <progress.hxx>
+#include <scmod.hxx>
+#include <inputopt.hxx>
+#include <sheetevents.hxx>
+#include <tokenarray.hxx>
+#include <listenercontext.hxx>
+
+void ScDocument::StartListeningArea(
+ const ScRange& rRange, bool bGroupListening, SvtListener* pListener )
+{
+ if (!pBASM)
+ return;
+
+ // Ensure sane ranges for the slots, specifically don't attempt to listen
+ // to more sheets than the document has. The slot machine handles it but
+ // with memory waste. Binary import filters can set out-of-bounds ranges
+ // in formula expressions' references, so all middle layers would have to
+ // check it, rather have this central point here.
+ ScRange aLimitedRange( ScAddress::UNINITIALIZED );
+ bool bEntirelyOut;
+ if (!LimitRangeToAvailableSheets( rRange, aLimitedRange, bEntirelyOut))
+ {
+ pBASM->StartListeningArea(rRange, bGroupListening, pListener);
+ return;
+ }
+
+ // If both sheets are out-of-bounds in the same direction then just bail out.
+ if (bEntirelyOut)
+ return;
+
+ pBASM->StartListeningArea( aLimitedRange, bGroupListening, pListener);
+}
+
+void ScDocument::EndListeningArea( const ScRange& rRange, bool bGroupListening, SvtListener* pListener )
+{
+ if (!pBASM)
+ return;
+
+ // End listening has to limit the range exactly the same as in
+ // StartListeningArea(), otherwise the range would not be found.
+ ScRange aLimitedRange( ScAddress::UNINITIALIZED );
+ bool bEntirelyOut;
+ if (!LimitRangeToAvailableSheets( rRange, aLimitedRange, bEntirelyOut))
+ {
+ pBASM->EndListeningArea(rRange, bGroupListening, pListener);
+ return;
+ }
+
+ // If both sheets are out-of-bounds in the same direction then just bail out.
+ if (bEntirelyOut)
+ return;
+
+ pBASM->EndListeningArea( aLimitedRange, bGroupListening, pListener);
+}
+
+bool ScDocument::LimitRangeToAvailableSheets( const ScRange& rRange, ScRange& o_rRange,
+ bool& o_bEntirelyOutOfBounds ) const
+{
+ const SCTAB nMaxTab = GetTableCount() - 1;
+ if (ValidTab( rRange.aStart.Tab(), nMaxTab) && ValidTab( rRange.aEnd.Tab(), nMaxTab))
+ return false;
+
+ // Originally BCA_LISTEN_ALWAYS uses an implicit tab 0 and should had been
+ // valid already, but in case that would change...
+ if (rRange == BCA_LISTEN_ALWAYS)
+ return false;
+
+ SCTAB nTab1 = rRange.aStart.Tab();
+ SCTAB nTab2 = rRange.aEnd.Tab();
+ SAL_WARN("sc.core","ScDocument::LimitRangeToAvailableSheets - bad sheet range: " << nTab1 << ".." << nTab2 <<
+ ", sheets: 0.." << nMaxTab);
+
+ // Both sheets are out-of-bounds in the same direction.
+ if ((nTab1 < 0 && nTab2 < 0) || (nMaxTab < nTab1 && nMaxTab < nTab2))
+ {
+ o_bEntirelyOutOfBounds = true;
+ return true;
+ }
+
+ // Limit the sheet range to bounds.
+ o_bEntirelyOutOfBounds = false;
+ nTab1 = std::clamp<SCTAB>( nTab1, 0, nMaxTab);
+ nTab2 = std::clamp<SCTAB>( nTab2, 0, nMaxTab);
+ o_rRange = rRange;
+ o_rRange.aStart.SetTab(nTab1);
+ o_rRange.aEnd.SetTab(nTab2);
+ return true;
+}
+
+void ScDocument::Broadcast( const ScHint& rHint )
+{
+ if ( !pBASM )
+ return ; // Clipboard or Undo
+ if ( eHardRecalcState == HardRecalcState::OFF )
+ {
+ ScBulkBroadcast aBulkBroadcast( pBASM.get(), rHint.GetId()); // scoped bulk broadcast
+ bool bIsBroadcasted = BroadcastHintInternal(rHint);
+ if ( pBASM->AreaBroadcast( rHint ) || bIsBroadcasted )
+ TrackFormulas( rHint.GetId() );
+ }
+
+ if ( rHint.GetStartAddress() != BCA_BRDCST_ALWAYS )
+ {
+ SCTAB nTab = rHint.GetStartAddress().Tab();
+ if (nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->SetStreamValid(false);
+ }
+}
+
+bool ScDocument::BroadcastHintInternal( const ScHint& rHint )
+{
+ bool bIsBroadcasted = false;
+ const ScAddress address(rHint.GetStartAddress());
+ SvtBroadcaster* pLastBC = nullptr;
+ // Process all broadcasters for the given row range.
+ for( SCROW nRow = 0; nRow < rHint.GetRowCount(); ++nRow )
+ {
+ ScAddress a(address);
+ a.SetRow(address.Row() + nRow);
+ SvtBroadcaster* pBC = GetBroadcaster(a);
+ if ( pBC && pBC != pLastBC )
+ {
+ pBC->Broadcast( rHint );
+ bIsBroadcasted = true;
+ pLastBC = pBC;
+ }
+ }
+ return bIsBroadcasted;
+}
+
+void ScDocument::BroadcastCells( const ScRange& rRange, SfxHintId nHint, bool bBroadcastSingleBroadcasters )
+{
+ PrepareFormulaCalc();
+
+ if (!pBASM)
+ return; // Clipboard or Undo
+
+ SCTAB nTab1 = rRange.aStart.Tab();
+ SCTAB nTab2 = rRange.aEnd.Tab();
+ SCROW nRow1 = rRange.aStart.Row();
+ SCROW nRow2 = rRange.aEnd.Row();
+ SCCOL nCol1 = rRange.aStart.Col();
+ SCCOL nCol2 = rRange.aEnd.Col();
+
+ if (eHardRecalcState == HardRecalcState::OFF)
+ {
+ ScBulkBroadcast aBulkBroadcast( pBASM.get(), nHint); // scoped bulk broadcast
+ bool bIsBroadcasted = false;
+
+ if (bBroadcastSingleBroadcasters)
+ {
+ for (SCTAB nTab = nTab1; nTab <= nTab2; ++nTab)
+ {
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ continue;
+
+ bIsBroadcasted |= pTab->BroadcastBroadcasters( nCol1, nRow1, nCol2, nRow2, nHint);
+ }
+ }
+
+ if (pBASM->AreaBroadcast(rRange, nHint) || bIsBroadcasted)
+ TrackFormulas(nHint);
+ }
+
+ for (SCTAB nTab = nTab1; nTab <= nTab2; ++nTab)
+ {
+ ScTable* pTab = FetchTable(nTab);
+ if (pTab)
+ pTab->SetStreamValid(false);
+ }
+
+ BroadcastUno(SfxHint(SfxHintId::ScDataChanged));
+}
+
+void ScDocument::AreaBroadcast( const ScHint& rHint )
+{
+ if ( !pBASM )
+ return ; // Clipboard or Undo
+ if (eHardRecalcState == HardRecalcState::OFF)
+ {
+ ScBulkBroadcast aBulkBroadcast( pBASM.get(), rHint.GetId()); // scoped bulk broadcast
+ if ( pBASM->AreaBroadcast( rHint ) )
+ TrackFormulas( rHint.GetId() );
+ }
+}
+
+void ScDocument::DelBroadcastAreasInRange( const ScRange& rRange )
+{
+ if ( pBASM )
+ pBASM->DelBroadcastAreasInRange( rRange );
+}
+
+void ScDocument::StartListeningCell( const ScAddress& rAddress,
+ SvtListener* pListener )
+{
+ OSL_ENSURE(pListener, "StartListeningCell: pListener Null");
+ SCTAB nTab = rAddress.Tab();
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->StartListening( rAddress, pListener );
+}
+
+void ScDocument::EndListeningCell( const ScAddress& rAddress,
+ SvtListener* pListener )
+{
+ OSL_ENSURE(pListener, "EndListeningCell: pListener Null");
+ SCTAB nTab = rAddress.Tab();
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->EndListening( rAddress, pListener );
+}
+
+void ScDocument::StartListeningCell(
+ sc::StartListeningContext& rCxt, const ScAddress& rPos, SvtListener& rListener )
+{
+ ScTable* pTab = FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ pTab->StartListening(rCxt, rPos, rListener);
+}
+
+void ScDocument::EndListeningCell(
+ sc::EndListeningContext& rCxt, const ScAddress& rPos, SvtListener& rListener )
+{
+ ScTable* pTab = FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ pTab->EndListening(rCxt, rPos, rListener);
+}
+
+void ScDocument::EndListeningFormulaCells( std::vector<ScFormulaCell*>& rCells )
+{
+ if (rCells.empty())
+ return;
+
+ sc::EndListeningContext aCxt(*this);
+ for (auto& pCell : rCells)
+ pCell->EndListeningTo(aCxt);
+
+ aCxt.purgeEmptyBroadcasters();
+}
+
+void ScDocument::PutInFormulaTree( ScFormulaCell* pCell )
+{
+ OSL_ENSURE( pCell, "PutInFormulaTree: pCell Null" );
+ RemoveFromFormulaTree( pCell );
+ // append
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ if ( pEOFormulaTree )
+ pEOFormulaTree->SetNext( pCell );
+ else
+ pFormulaTree = pCell; // No end, no beginning...
+ pCell->SetPrevious( pEOFormulaTree );
+ pCell->SetNext( nullptr );
+ pEOFormulaTree = pCell;
+ nFormulaCodeInTree += pCell->GetCode()->GetCodeLen();
+}
+
+void ScDocument::RemoveFromFormulaTree( ScFormulaCell* pCell )
+{
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ OSL_ENSURE( pCell, "RemoveFromFormulaTree: pCell Null" );
+ ScFormulaCell* pPrev = pCell->GetPrevious();
+ assert(pPrev != pCell); // pointing to itself?!?
+ // if the cell is first or somewhere in chain
+ if ( pPrev || pFormulaTree == pCell )
+ {
+ ScFormulaCell* pNext = pCell->GetNext();
+ assert(pNext != pCell); // pointing to itself?!?
+ if ( pPrev )
+ {
+ assert(pFormulaTree != pCell); // if this cell is also head something's wrong
+ pPrev->SetNext( pNext ); // predecessor exists, set successor
+ }
+ else
+ {
+ pFormulaTree = pNext; // this cell was first cell
+ }
+ if ( pNext )
+ {
+ assert(pEOFormulaTree != pCell); // if this cell is also tail something's wrong
+ pNext->SetPrevious( pPrev ); // successor exists, set predecessor
+ }
+ else
+ {
+ pEOFormulaTree = pPrev; // this cell was last cell
+ }
+ pCell->SetPrevious( nullptr );
+ pCell->SetNext( nullptr );
+ sal_uInt16 nRPN = pCell->GetCode()->GetCodeLen();
+ if ( nFormulaCodeInTree >= nRPN )
+ nFormulaCodeInTree -= nRPN;
+ else
+ {
+ OSL_FAIL( "RemoveFromFormulaTree: nFormulaCodeInTree < nRPN" );
+ nFormulaCodeInTree = 0;
+ }
+ }
+ else if ( !pFormulaTree && nFormulaCodeInTree )
+ {
+ OSL_FAIL( "!pFormulaTree && nFormulaCodeInTree != 0" );
+ nFormulaCodeInTree = 0;
+ }
+}
+
+void ScDocument::CalcFormulaTree( bool bOnlyForced, bool bProgressBar, bool bSetAllDirty )
+{
+ OSL_ENSURE( !IsCalculatingFormulaTree(), "CalcFormulaTree recursion" );
+ // never ever recurse into this, might end up lost in infinity
+ if ( IsCalculatingFormulaTree() )
+ return ;
+
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ mpFormulaGroupCxt.reset();
+ bCalculatingFormulaTree = true;
+
+ SetForcedFormulaPending( false );
+ bool bOldIdleEnabled = IsIdleEnabled();
+ EnableIdle(false);
+ bool bOldAutoCalc = GetAutoCalc();
+ //ATTENTION: _not_ SetAutoCalc( true ) because this might call CalcFormulaTree( true )
+ //ATTENTION: if it was disabled before and bHasForcedFormulas is set
+ bAutoCalc = true;
+ if (eHardRecalcState == HardRecalcState::ETERNAL)
+ CalcAll();
+ else
+ {
+ ::std::vector<ScFormulaCell*> vAlwaysDirty;
+ ScFormulaCell* pCell = pFormulaTree;
+ while ( pCell )
+ {
+ if ( pCell->GetDirty() )
+ ; // nothing to do
+ else if ( pCell->GetCode()->IsRecalcModeAlways() )
+ {
+ // pCell and dependents are to be set dirty again, collect
+ // them first and broadcast afterwards to not break the
+ // FormulaTree chain here.
+ vAlwaysDirty.push_back( pCell);
+ }
+ else if ( bSetAllDirty )
+ {
+ // Force calculating all in tree, without broadcasting.
+ pCell->SetDirtyVar();
+ }
+ pCell = pCell->GetNext();
+ }
+ for (const auto& rpCell : vAlwaysDirty)
+ {
+ pCell = rpCell;
+ if (!pCell->GetDirty())
+ pCell->SetDirty();
+ }
+
+ bool bProgress = !bOnlyForced && nFormulaCodeInTree && bProgressBar;
+ if ( bProgress )
+ ScProgress::CreateInterpretProgress( this );
+
+ pCell = pFormulaTree;
+ ScFormulaCell* pLastNoGood = nullptr;
+ while ( pCell )
+ {
+ // Interpret resets bDirty and calls Remove, also the referenced!
+ // the Cell remains when ScRecalcMode::ALWAYS.
+ if ( bOnlyForced )
+ {
+ if ( pCell->GetCode()->IsRecalcModeForced() )
+ pCell->Interpret();
+ }
+ else
+ {
+ pCell->Interpret();
+ }
+ if ( pCell->GetPrevious() || pCell == pFormulaTree )
+ { // (IsInFormulaTree(pCell)) no Remove was called => next
+ pLastNoGood = pCell;
+ pCell = pCell->GetNext();
+ }
+ else
+ {
+ if ( pFormulaTree )
+ {
+ if ( pFormulaTree->GetDirty() && !bOnlyForced )
+ {
+ pCell = pFormulaTree;
+ pLastNoGood = nullptr;
+ }
+ else
+ {
+ // IsInFormulaTree(pLastNoGood)
+ if ( pLastNoGood && (pLastNoGood->GetPrevious() ||
+ pLastNoGood == pFormulaTree) )
+ pCell = pLastNoGood->GetNext();
+ else
+ {
+ pCell = pFormulaTree;
+ while ( pCell && !pCell->GetDirty() )
+ pCell = pCell->GetNext();
+ if ( pCell )
+ pLastNoGood = pCell->GetPrevious();
+ }
+ }
+ }
+ else
+ pCell = nullptr;
+ }
+ }
+ if ( bProgress )
+ ScProgress::DeleteInterpretProgress();
+ }
+ bAutoCalc = bOldAutoCalc;
+ EnableIdle(bOldIdleEnabled);
+ bCalculatingFormulaTree = false;
+
+ mpFormulaGroupCxt.reset();
+}
+
+void ScDocument::ClearFormulaTree()
+{
+ ScFormulaCell* pCell;
+ ScFormulaCell* pTree = pFormulaTree;
+ while ( pTree )
+ {
+ pCell = pTree;
+ pTree = pCell->GetNext();
+ if ( !pCell->GetCode()->IsRecalcModeAlways() )
+ RemoveFromFormulaTree( pCell );
+ }
+}
+
+void ScDocument::AppendToFormulaTrack( ScFormulaCell* pCell )
+{
+ OSL_ENSURE( pCell, "AppendToFormulaTrack: pCell Null" );
+ // The cell can not be in both lists at the same time
+ RemoveFromFormulaTrack( pCell );
+ RemoveFromFormulaTree( pCell );
+ if ( pEOFormulaTrack )
+ pEOFormulaTrack->SetNextTrack( pCell );
+ else
+ pFormulaTrack = pCell; // No end, no beginning...
+ pCell->SetPreviousTrack( pEOFormulaTrack );
+ pCell->SetNextTrack( nullptr );
+ pEOFormulaTrack = pCell;
+ ++nFormulaTrackCount;
+}
+
+void ScDocument::RemoveFromFormulaTrack( ScFormulaCell* pCell )
+{
+ OSL_ENSURE( pCell, "RemoveFromFormulaTrack: pCell Null" );
+ ScFormulaCell* pPrev = pCell->GetPreviousTrack();
+ assert(pPrev != pCell); // pointing to itself?!?
+ // if the cell is first or somewhere in chain
+ if ( !(pPrev || pFormulaTrack == pCell) )
+ return;
+
+ ScFormulaCell* pNext = pCell->GetNextTrack();
+ assert(pNext != pCell); // pointing to itself?!?
+ if ( pPrev )
+ {
+ assert(pFormulaTrack != pCell); // if this cell is also head something's wrong
+ pPrev->SetNextTrack( pNext ); // predecessor exists, set successor
+ }
+ else
+ {
+ pFormulaTrack = pNext; // this cell was first cell
+ }
+ if ( pNext )
+ {
+ assert(pEOFormulaTrack != pCell); // if this cell is also tail something's wrong
+ pNext->SetPreviousTrack( pPrev ); // successor exists, set predecessor
+ }
+ else
+ {
+ pEOFormulaTrack = pPrev; // this cell was last cell
+ }
+ pCell->SetPreviousTrack( nullptr );
+ pCell->SetNextTrack( nullptr );
+ --nFormulaTrackCount;
+}
+
+void ScDocument::FinalTrackFormulas( SfxHintId nHintId )
+{
+ mbTrackFormulasPending = false;
+ mbFinalTrackFormulas = true;
+ {
+ ScBulkBroadcast aBulk( GetBASM(), nHintId);
+ // Collect all pending formula cells in bulk.
+ TrackFormulas( nHintId );
+ }
+ // A final round not in bulk to track all remaining formula cells and their
+ // dependents that were collected during ScBulkBroadcast dtor.
+ TrackFormulas( nHintId );
+ mbFinalTrackFormulas = false;
+}
+
+/*
+ The first is broadcasted,
+ the ones that are created through this are appended to the Track by Notify.
+ The next is broadcasted again, and so on.
+ View initiates Interpret.
+ */
+void ScDocument::TrackFormulas( SfxHintId nHintId )
+{
+ if (!pBASM)
+ return;
+
+ if (pBASM->IsInBulkBroadcast() && !IsFinalTrackFormulas() &&
+ (nHintId == SfxHintId::ScDataChanged || nHintId == SfxHintId::ScHiddenRowsChanged))
+ {
+ SetTrackFormulasPending();
+ return;
+ }
+
+ if ( pFormulaTrack )
+ {
+ // outside the loop, check if any sheet has a "calculate" event script
+ bool bCalcEvent = HasAnySheetEventScript( ScSheetEventId::CALCULATE, true );
+ for( ScFormulaCell* pTrack = pFormulaTrack; pTrack != nullptr; pTrack = pTrack->GetNextTrack())
+ {
+ SCROW rowCount = 1;
+ ScAddress address = pTrack->aPos;
+ // Compress to include all adjacent cells in the same column.
+ for(ScFormulaCell* pNext = pTrack->GetNextTrack(); pNext != nullptr; pNext = pNext->GetNextTrack())
+ {
+ if(pNext->aPos != ScAddress(address.Col(), address.Row() + rowCount, address.Tab()))
+ break;
+ ++rowCount;
+ pTrack = pNext;
+ }
+ ScHint aHint( nHintId, address, rowCount );
+ BroadcastHintInternal( aHint );
+ pBASM->AreaBroadcast( aHint );
+ // for "calculate" event, keep track of which sheets are affected by tracked formulas
+ if ( bCalcEvent )
+ SetCalcNotification( address.Tab() );
+ }
+ bool bHaveForced = false;
+ for( ScFormulaCell* pTrack = pFormulaTrack; pTrack != nullptr;)
+ {
+ ScFormulaCell* pNext = pTrack->GetNextTrack();
+ RemoveFromFormulaTrack( pTrack );
+ PutInFormulaTree( pTrack );
+ if ( pTrack->GetCode()->IsRecalcModeForced() )
+ bHaveForced = true;
+ pTrack = pNext;
+ }
+ if ( bHaveForced )
+ {
+ SetForcedFormulas( true );
+ if ( bAutoCalc && !IsAutoCalcShellDisabled() && !IsInInterpreter()
+ && !IsCalculatingFormulaTree() )
+ CalcFormulaTree( true );
+ else
+ SetForcedFormulaPending( true );
+ }
+ }
+ OSL_ENSURE( nFormulaTrackCount==0, "TrackFormulas: nFormulaTrackCount!=0" );
+}
+
+void ScDocument::StartAllListeners()
+{
+ sc::StartListeningContext aCxt(*this);
+ for ( auto const & i: maTabs )
+ if ( i )
+ i->StartListeners(aCxt, true);
+}
+
+void ScDocument::UpdateBroadcastAreas( UpdateRefMode eUpdateRefMode,
+ const ScRange& rRange, SCCOL nDx, SCROW nDy, SCTAB nDz
+ )
+{
+ bool bExpandRefsOld = IsExpandRefs();
+ if ( eUpdateRefMode == URM_INSDEL && (nDx > 0 || nDy > 0 || nDz > 0) )
+ SetExpandRefs( SC_MOD()->GetInputOptions().GetExpandRefs() );
+ if ( pBASM )
+ pBASM->UpdateBroadcastAreas( eUpdateRefMode, rRange, nDx, nDy, nDz );
+ SetExpandRefs( bExpandRefsOld );
+}
+
+void ScDocument::SetAutoCalc( bool bNewAutoCalc )
+{
+ bool bOld = bAutoCalc;
+ bAutoCalc = bNewAutoCalc;
+ if ( !bOld && bNewAutoCalc && bHasForcedFormulas )
+ {
+ if ( IsAutoCalcShellDisabled() )
+ SetForcedFormulaPending( true );
+ else if ( !IsInInterpreter() )
+ CalcFormulaTree( true );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/documen8.cxx b/sc/source/core/data/documen8.cxx
new file mode 100644
index 000000000..e0abb1d3b
--- /dev/null
+++ b/sc/source/core/data/documen8.cxx
@@ -0,0 +1,1329 @@
+/* -*- 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/fileformat.h>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/servicehelper.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <tools/urlobj.hxx>
+#include <editeng/frmdiritem.hxx>
+#include <editeng/langitem.hxx>
+#include <sfx2/linkmgr.hxx>
+#include <sfx2/bindings.hxx>
+#include <sfx2/objsh.hxx>
+#include <sfx2/printer.hxx>
+#include <sfx2/viewfrm.hxx>
+#include <sfx2/viewsh.hxx>
+#include <svl/flagitem.hxx>
+#include <svl/intitem.hxx>
+#include <svl/numformat.hxx>
+#include <svl/zformat.hxx>
+#include <svl/ctloptions.hxx>
+#include <unotools/transliterationwrapper.hxx>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <vcl/svapp.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/TaskStopwatch.hxx>
+
+#include <inputopt.hxx>
+#include <global.hxx>
+#include <table.hxx>
+#include <column.hxx>
+#include <poolhelp.hxx>
+#include <docpool.hxx>
+#include <stlpool.hxx>
+#include <stlsheet.hxx>
+#include <docoptio.hxx>
+#include <viewopti.hxx>
+#include <scextopt.hxx>
+#include <rechead.hxx>
+#include <ddelink.hxx>
+#include <scmatrix.hxx>
+#include <arealink.hxx>
+#include <patattr.hxx>
+#include <editutil.hxx>
+#include <progress.hxx>
+#include <document.hxx>
+#include <chartlis.hxx>
+#include <chartlock.hxx>
+#include <refupdat.hxx>
+#include <markdata.hxx>
+#include <scmod.hxx>
+#include <externalrefmgr.hxx>
+#include <globstr.hrc>
+#include <strings.hrc>
+#include <sc.hrc>
+#include <charthelper.hxx>
+#include <macromgr.hxx>
+#include <docuno.hxx>
+#include <scresid.hxx>
+#include <columniterator.hxx>
+#include <globalnames.hxx>
+#include <stringutil.hxx>
+#include <documentlinkmgr.hxx>
+#include <tokenarray.hxx>
+#include <recursionhelper.hxx>
+
+#include <memory>
+#include <utility>
+
+using namespace com::sun::star;
+
+namespace {
+
+sal_uInt16 getScaleValue(SfxStyleSheetBase& rStyle, sal_uInt16 nWhich)
+{
+ return static_cast<const SfxUInt16Item&>(rStyle.GetItemSet().Get(nWhich)).GetValue();
+}
+
+}
+
+void ScDocument::ImplCreateOptions()
+{
+ pDocOptions.reset( new ScDocOptions() );
+ pViewOptions.reset( new ScViewOptions() );
+}
+
+void ScDocument::ImplDeleteOptions()
+{
+ pDocOptions.reset();
+ pViewOptions.reset();
+ pExtDocOptions.reset();
+}
+
+SfxPrinter* ScDocument::GetPrinter(bool bCreateIfNotExist)
+{
+ if ( !mpPrinter && bCreateIfNotExist )
+ {
+ auto pSet =
+ std::make_unique<SfxItemSetFixed
+ <SID_PRINTER_NOTFOUND_WARN, SID_PRINTER_NOTFOUND_WARN,
+ SID_PRINTER_CHANGESTODOC, SID_PRINTER_CHANGESTODOC,
+ SID_PRINT_SELECTEDSHEET, SID_PRINT_SELECTEDSHEET,
+ SID_SCPRINTOPTIONS, SID_SCPRINTOPTIONS>>(*mxPoolHelper->GetDocPool());
+
+ SfxPrinterChangeFlags nFlags = SfxPrinterChangeFlags::NONE;
+ if (officecfg::Office::Common::Print::Warning::PaperOrientation::get())
+ nFlags |= SfxPrinterChangeFlags::CHG_ORIENTATION;
+ if (officecfg::Office::Common::Print::Warning::PaperSize::get())
+ nFlags |= SfxPrinterChangeFlags::CHG_SIZE;
+ pSet->Put( SfxFlagItem( SID_PRINTER_CHANGESTODOC, static_cast<int>(nFlags) ) );
+ pSet->Put( SfxBoolItem( SID_PRINTER_NOTFOUND_WARN, officecfg::Office::Common::Print::Warning::NotFound::get() ) );
+
+ mpPrinter = VclPtr<SfxPrinter>::Create( std::move(pSet) );
+ mpPrinter->SetMapMode(MapMode(MapUnit::Map100thMM));
+ UpdateDrawPrinter();
+ mpPrinter->SetDigitLanguage( SC_MOD()->GetOptDigitLanguage() );
+ }
+
+ return mpPrinter;
+}
+
+void ScDocument::SetPrinter( VclPtr<SfxPrinter> const & pNewPrinter )
+{
+ if ( pNewPrinter == mpPrinter.get() )
+ {
+ // #i6706# SetPrinter is called with the same printer again if
+ // the JobSetup has changed. In that case just call UpdateDrawPrinter
+ // (SetRefDevice for drawing layer) because of changed text sizes.
+ UpdateDrawPrinter();
+ }
+ else
+ {
+ ScopedVclPtr<SfxPrinter> xKeepAlive( mpPrinter );
+ mpPrinter = pNewPrinter;
+ UpdateDrawPrinter();
+ mpPrinter->SetDigitLanguage( SC_MOD()->GetOptDigitLanguage() );
+ }
+ InvalidateTextWidth(nullptr, nullptr, false); // in both cases
+}
+
+void ScDocument::SetPrintOptions()
+{
+ if ( !mpPrinter ) GetPrinter(); // this sets mpPrinter
+ OSL_ENSURE( mpPrinter, "Error in printer creation :-/" );
+
+ if ( !mpPrinter )
+ return;
+
+ SfxItemSet aOptSet( mpPrinter->GetOptions() );
+
+ SfxPrinterChangeFlags nFlags = SfxPrinterChangeFlags::NONE;
+ if (officecfg::Office::Common::Print::Warning::PaperOrientation::get())
+ nFlags |= SfxPrinterChangeFlags::CHG_ORIENTATION;
+ if (officecfg::Office::Common::Print::Warning::PaperSize::get())
+ nFlags |= SfxPrinterChangeFlags::CHG_SIZE;
+ aOptSet.Put( SfxFlagItem( SID_PRINTER_CHANGESTODOC, static_cast<int>(nFlags) ) );
+ aOptSet.Put( SfxBoolItem( SID_PRINTER_NOTFOUND_WARN, officecfg::Office::Common::Print::Warning::NotFound::get() ) );
+
+ mpPrinter->SetOptions( aOptSet );
+}
+
+VirtualDevice* ScDocument::GetVirtualDevice_100th_mm()
+{
+ if (!mpVirtualDevice_100th_mm)
+ {
+#ifdef IOS
+ mpVirtualDevice_100th_mm = VclPtr<VirtualDevice>::Create(DeviceFormat::GRAYSCALE);
+#else
+ mpVirtualDevice_100th_mm = VclPtr<VirtualDevice>::Create(DeviceFormat::DEFAULT);
+#endif
+ mpVirtualDevice_100th_mm->SetReferenceDevice(VirtualDevice::RefDevMode::MSO1);
+ MapMode aMapMode( mpVirtualDevice_100th_mm->GetMapMode() );
+ aMapMode.SetMapUnit( MapUnit::Map100thMM );
+ mpVirtualDevice_100th_mm->SetMapMode( aMapMode );
+ }
+ return mpVirtualDevice_100th_mm;
+}
+
+OutputDevice* ScDocument::GetRefDevice()
+{
+ // Create printer like ref device, see Writer...
+ OutputDevice* pRefDevice = nullptr;
+ if ( SC_MOD()->GetInputOptions().GetTextWysiwyg() )
+ pRefDevice = GetPrinter();
+ else
+ pRefDevice = GetVirtualDevice_100th_mm();
+ return pRefDevice;
+}
+
+void ScDocument::ModifyStyleSheet( SfxStyleSheetBase& rStyleSheet,
+ const SfxItemSet& rChanges )
+{
+ SfxItemSet& rSet = rStyleSheet.GetItemSet();
+
+ switch ( rStyleSheet.GetFamily() )
+ {
+ case SfxStyleFamily::Page:
+ {
+ const sal_uInt16 nOldScale = getScaleValue(rStyleSheet, ATTR_PAGE_SCALE);
+ const sal_uInt16 nOldScaleToPages = getScaleValue(rStyleSheet, ATTR_PAGE_SCALETOPAGES);
+ rSet.Put( rChanges );
+ const sal_uInt16 nNewScale = getScaleValue(rStyleSheet, ATTR_PAGE_SCALE);
+ const sal_uInt16 nNewScaleToPages = getScaleValue(rStyleSheet, ATTR_PAGE_SCALETOPAGES);
+
+ if ( (nOldScale != nNewScale) || (nOldScaleToPages != nNewScaleToPages) )
+ InvalidateTextWidth( rStyleSheet.GetName() );
+
+ if( SvtCTLOptions().IsCTLFontEnabled() )
+ {
+ if( rChanges.GetItemState(ATTR_WRITINGDIR ) == SfxItemState::SET )
+ ScChartHelper::DoUpdateAllCharts( *this );
+ }
+ }
+ break;
+
+ case SfxStyleFamily::Para:
+ {
+ bool bNumFormatChanged;
+ if ( ScGlobal::CheckWidthInvalidate( bNumFormatChanged,
+ rSet, rChanges ) )
+ InvalidateTextWidth( nullptr, nullptr, bNumFormatChanged );
+
+ for (SCTAB nTab=0; nTab<=MAXTAB; ++nTab)
+ if (maTabs[nTab])
+ maTabs[nTab]->SetStreamValid( false );
+
+ sal_uLong nOldFormat =
+ rSet.Get( ATTR_VALUE_FORMAT ).GetValue();
+ sal_uLong nNewFormat =
+ rChanges.Get( ATTR_VALUE_FORMAT ).GetValue();
+ LanguageType eNewLang, eOldLang;
+ eNewLang = eOldLang = LANGUAGE_DONTKNOW;
+ if ( nNewFormat != nOldFormat )
+ {
+ SvNumberFormatter* pFormatter = GetFormatTable();
+ eOldLang = pFormatter->GetEntry( nOldFormat )->GetLanguage();
+ eNewLang = pFormatter->GetEntry( nNewFormat )->GetLanguage();
+ }
+
+ // Explanation to Items in rChanges:
+ // Set Item - take over change
+ // Dontcare - Set Default
+ // Default - No change
+ // ("no change" is not possible with PutExtended, thus the loop)
+ for (sal_uInt16 nWhich = ATTR_PATTERN_START; nWhich <= ATTR_PATTERN_END; nWhich++)
+ {
+ const SfxPoolItem* pItem;
+ SfxItemState eState = rChanges.GetItemState( nWhich, false, &pItem );
+ if ( eState == SfxItemState::SET )
+ rSet.Put( *pItem );
+ else if ( eState == SfxItemState::DONTCARE )
+ rSet.ClearItem( nWhich );
+ // when Default nothing
+ }
+
+ if ( eNewLang != eOldLang )
+ rSet.Put(
+ SvxLanguageItem( eNewLang, ATTR_LANGUAGE_FORMAT ) );
+ }
+ break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+}
+
+void ScDocument::CopyStdStylesFrom( const ScDocument& rSrcDoc )
+{
+ // number format exchange list has to be handled here, too
+ NumFmtMergeHandler aNumFmtMergeHdl(*this, rSrcDoc);
+ mxPoolHelper->GetStylePool()->CopyStdStylesFrom( rSrcDoc.mxPoolHelper->GetStylePool() );
+}
+
+void ScDocument::InvalidateTextWidth( std::u16string_view rStyleName )
+{
+ const SCTAB nCount = GetTableCount();
+ for ( SCTAB i=0; i<nCount && maTabs[i]; i++ )
+ if ( maTabs[i]->GetPageStyle() == rStyleName )
+ InvalidateTextWidth( i );
+}
+
+void ScDocument::InvalidateTextWidth( SCTAB nTab )
+{
+ ScAddress aAdrFrom( 0, 0, nTab );
+ ScAddress aAdrTo ( MaxCol(), MaxRow(), nTab );
+ InvalidateTextWidth( &aAdrFrom, &aAdrTo, false );
+}
+
+bool ScDocument::IsPageStyleInUse( std::u16string_view rStrPageStyle, SCTAB* pInTab )
+{
+ bool bInUse = false;
+ const SCTAB nCount = GetTableCount();
+ SCTAB i;
+
+ for ( i = 0; !bInUse && i < nCount && maTabs[i]; i++ )
+ bInUse = ( maTabs[i]->GetPageStyle() == rStrPageStyle );
+
+ if ( pInTab )
+ *pInTab = i-1;
+
+ return bInUse;
+}
+
+bool ScDocument::RemovePageStyleInUse( std::u16string_view rStyle )
+{
+ bool bWasInUse = false;
+ const SCTAB nCount = GetTableCount();
+
+ for ( SCTAB i=0; i<nCount && maTabs[i]; i++ )
+ if ( maTabs[i]->GetPageStyle() == rStyle )
+ {
+ bWasInUse = true;
+ maTabs[i]->SetPageStyle( ScResId(STR_STYLENAME_STANDARD) );
+ }
+
+ return bWasInUse;
+}
+
+bool ScDocument::RenamePageStyleInUse( std::u16string_view rOld, const OUString& rNew )
+{
+ bool bWasInUse = false;
+ const SCTAB nCount = GetTableCount();
+
+ for ( SCTAB i=0; i<nCount && maTabs[i]; i++ )
+ if ( maTabs[i]->GetPageStyle() == rOld )
+ {
+ bWasInUse = true;
+ maTabs[i]->SetPageStyle( rNew );
+ }
+
+ return bWasInUse;
+}
+
+EEHorizontalTextDirection ScDocument::GetEditTextDirection(SCTAB nTab) const
+{
+ EEHorizontalTextDirection eRet = EEHorizontalTextDirection::Default;
+
+ OUString aStyleName = GetPageStyle( nTab );
+ SfxStyleSheetBase* pStyle = mxPoolHelper->GetStylePool()->Find( aStyleName, SfxStyleFamily::Page );
+ if ( pStyle )
+ {
+ SfxItemSet& rStyleSet = pStyle->GetItemSet();
+ SvxFrameDirection eDirection =
+ rStyleSet.Get( ATTR_WRITINGDIR ).GetValue();
+
+ if ( eDirection == SvxFrameDirection::Horizontal_LR_TB )
+ eRet = EEHorizontalTextDirection::L2R;
+ else if ( eDirection == SvxFrameDirection::Horizontal_RL_TB )
+ eRet = EEHorizontalTextDirection::R2L;
+ // else (invalid for EditEngine): keep "default"
+ }
+
+ return eRet;
+}
+
+ScMacroManager* ScDocument::GetMacroManager()
+{
+ if (!mpMacroMgr)
+ mpMacroMgr.reset(new ScMacroManager(*this));
+ return mpMacroMgr.get();
+}
+
+void ScDocument::FillMatrix(
+ ScMatrix& rMat, SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, svl::SharedStringPool* pPool ) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ if (nCol1 > nCol2 || nRow1 > nRow2)
+ return;
+
+ SCSIZE nC, nR;
+ rMat.GetDimensions(nC, nR);
+ if (static_cast<SCROW>(nR) != nRow2 - nRow1 + 1 || static_cast<SCCOL>(nC) != nCol2 - nCol1 + 1)
+ return;
+
+ pTab->FillMatrix(rMat, nCol1, nRow1, nCol2, nRow2, pPool);
+}
+
+void ScDocument::SetFormulaResults( const ScAddress& rTopPos, const double* pResults, size_t nLen )
+{
+ ScTable* pTab = FetchTable(rTopPos.Tab());
+ if (!pTab)
+ return;
+
+ pTab->SetFormulaResults(rTopPos.Col(), rTopPos.Row(), pResults, nLen);
+}
+
+void ScDocument::CalculateInColumnInThread( ScInterpreterContext& rContext, const ScRange& rCalcRange, unsigned nThisThread, unsigned nThreadsTotal)
+{
+ ScTable* pTab = FetchTable(rCalcRange.aStart.Tab());
+ if (!pTab)
+ return;
+
+ assert(IsThreadedGroupCalcInProgress());
+
+ maThreadSpecific.pContext = &rContext;
+ pTab->CalculateInColumnInThread(rContext, rCalcRange.aStart.Col(), rCalcRange.aEnd.Col(), rCalcRange.aStart.Row(), rCalcRange.aEnd.Row(), nThisThread, nThreadsTotal);
+
+ assert(IsThreadedGroupCalcInProgress());
+ maThreadSpecific.pContext = nullptr;
+ // If any of the thread_local data would cause problems if they stay around for too long
+ // (and e.g. outlive the ScDocument), clean them up here, they cannot be cleaned up
+ // later from the main thread.
+ if(maThreadSpecific.xRecursionHelper)
+ maThreadSpecific.xRecursionHelper->Clear();
+}
+
+void ScDocument::HandleStuffAfterParallelCalculation( SCCOL nColStart, SCCOL nColEnd, SCROW nRow, size_t nLen, SCTAB nTab, ScInterpreter* pInterpreter )
+{
+ assert(!IsThreadedGroupCalcInProgress());
+ for( const DelayedSetNumberFormat& data : GetNonThreadedContext().maDelayedSetNumberFormat)
+ SetNumberFormat( ScAddress( data.mCol, data.mRow, nTab ), data.mnNumberFormat );
+ GetNonThreadedContext().maDelayedSetNumberFormat.clear();
+
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ pTab->HandleStuffAfterParallelCalculation(nColStart, nColEnd, nRow, nLen, pInterpreter);
+}
+
+void ScDocument::InvalidateTextWidth( const ScAddress* pAdrFrom, const ScAddress* pAdrTo,
+ bool bNumFormatChanged )
+{
+ bool bBroadcast = (bNumFormatChanged && GetDocOptions().IsCalcAsShown() && !IsImportingXML() && !IsClipboard());
+ if ( pAdrFrom && !pAdrTo )
+ {
+ const SCTAB nTab = pAdrFrom->Tab();
+
+ if (nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->InvalidateTextWidth( pAdrFrom, nullptr, bNumFormatChanged, bBroadcast );
+ }
+ else
+ {
+ const SCTAB nTabStart = pAdrFrom ? pAdrFrom->Tab() : 0;
+ const SCTAB nTabEnd = pAdrTo ? pAdrTo->Tab() : MAXTAB;
+
+ for ( SCTAB nTab=nTabStart; nTab<=nTabEnd && nTab < static_cast<SCTAB>(maTabs.size()); nTab++ )
+ if ( maTabs[nTab] )
+ maTabs[nTab]->InvalidateTextWidth( pAdrFrom, pAdrTo, bNumFormatChanged, bBroadcast );
+ }
+}
+
+#define CALCMAX 1000 // Calculations
+
+namespace {
+
+class IdleCalcTextWidthScope : public TaskStopwatch
+{
+ ScDocument& mrDoc;
+ ScAddress& mrCalcPos;
+ MapMode maOldMapMode;
+ ScStyleSheetPool* mpStylePool;
+ bool mbNeedMore;
+ bool mbProgress;
+
+public:
+ IdleCalcTextWidthScope(ScDocument& rDoc, ScAddress& rCalcPos) :
+ mrDoc(rDoc),
+ mrCalcPos(rCalcPos),
+ mpStylePool(rDoc.GetStyleSheetPool()),
+ mbNeedMore(false),
+ mbProgress(false)
+ {
+ mrDoc.EnableIdle(false);
+ }
+
+ ~IdleCalcTextWidthScope() COVERITY_NOEXCEPT_FALSE
+ {
+ SfxPrinter* pDev = mrDoc.GetPrinter();
+ if (pDev)
+ pDev->SetMapMode(maOldMapMode);
+
+ if (mbProgress)
+ ScProgress::DeleteInterpretProgress();
+
+ mrDoc.EnableIdle(true);
+ }
+
+ SCTAB Tab() const { return mrCalcPos.Tab(); }
+ SCCOL Col() const { return mrCalcPos.Col(); }
+ SCROW Row() const { return mrCalcPos.Row(); }
+
+ void setTab(SCTAB nTab) { mrCalcPos.SetTab(nTab); }
+ void setCol(SCCOL nCol) { mrCalcPos.SetCol(nCol); }
+ void setRow(SCROW nRow) { mrCalcPos.SetRow(nRow); }
+
+ void incTab() { mrCalcPos.IncTab(); }
+ void incCol(SCCOL nInc) { mrCalcPos.IncCol(nInc); }
+
+ void setOldMapMode(const MapMode& rOldMapMode) { maOldMapMode = rOldMapMode; }
+
+ void setNeedMore(bool b) { mbNeedMore = b; }
+ bool getNeedMore() const { return mbNeedMore; }
+
+ void createProgressBar()
+ {
+ ScProgress::CreateInterpretProgress(&mrDoc, false);
+ mbProgress = true;
+ }
+
+ bool hasProgressBar() const { return mbProgress; }
+
+ ScStyleSheetPool* getStylePool() { return mpStylePool; }
+};
+
+}
+
+bool ScDocument::IdleCalcTextWidth() // true = try next again
+{
+ // #i75610# if a printer hasn't been set or created yet, don't create one for this
+ if (!mbIdleEnabled || IsInLinkUpdate() || GetPrinter(false) == nullptr)
+ return false;
+
+ IdleCalcTextWidthScope aScope(*this, aCurTextWidthCalcPos);
+
+ if (!ValidRow(aScope.Row()))
+ {
+ aScope.setRow(0);
+ aScope.incCol(-1);
+ }
+
+ if (aScope.Col() < 0)
+ {
+ aScope.setCol(MaxCol());
+ aScope.incTab();
+ }
+
+ if (!ValidTab(aScope.Tab()) || aScope.Tab() >= static_cast<SCTAB>(maTabs.size()) || !maTabs[aScope.Tab()])
+ aScope.setTab(0);
+
+ ScTable* pTab = maTabs[aScope.Tab()].get();
+ ScStyleSheet* pStyle = static_cast<ScStyleSheet*>(aScope.getStylePool()->Find(pTab->aPageStyle, SfxStyleFamily::Page));
+ OSL_ENSURE( pStyle, "Missing StyleSheet :-/" );
+
+ if (!pStyle || getScaleValue(*pStyle, ATTR_PAGE_SCALETOPAGES) == 0)
+ {
+ // Move to the next sheet as the current one has scale-to-pages set,
+ // and bail out.
+ aScope.incTab();
+ return false;
+ }
+
+ sal_uInt16 nZoom = getScaleValue(*pStyle, ATTR_PAGE_SCALE);
+ Fraction aZoomFract(nZoom, 100);
+
+ aScope.setCol(pTab->ClampToAllocatedColumns(aScope.Col()));
+ // Start at specified cell position (nCol, nRow, nTab).
+ ScColumn* pCol = &pTab->aCol[aScope.Col()];
+ std::optional<ScColumnTextWidthIterator> pColIter(std::in_place, *this, *pCol, aScope.Row(), MaxRow());
+
+ OutputDevice* pDev = nullptr;
+ sal_uInt16 nRestart = 0;
+ sal_uInt16 nCount = 0;
+ while ( (nZoom > 0) && (nCount < CALCMAX) && (nRestart < 2) )
+ {
+ if (pColIter->hasCell())
+ {
+ // More cell in this column.
+ SCROW nRow = pColIter->getPos();
+ aScope.setRow(nRow);
+
+ if (pColIter->getValue() == TEXTWIDTH_DIRTY)
+ {
+ // Calculate text width for this cell.
+ double nPPTX = 0.0;
+ double nPPTY = 0.0;
+ if (!pDev)
+ {
+ pDev = GetPrinter();
+ aScope.setOldMapMode(pDev->GetMapMode());
+ pDev->SetMapMode(MapMode(MapUnit::MapPixel)); // Important for GetNeededSize
+
+ Point aPix1000 = pDev->LogicToPixel(Point(1000,1000), MapMode(MapUnit::MapTwip));
+ nPPTX = aPix1000.X() / 1000.0;
+ nPPTY = aPix1000.Y() / 1000.0;
+ }
+
+ if (!aScope.hasProgressBar() && pCol->IsFormulaDirty(nRow))
+ aScope.createProgressBar();
+
+ sal_uInt16 nNewWidth = static_cast<sal_uInt16>(GetNeededSize(
+ aScope.Col(), aScope.Row(), aScope.Tab(),
+ pDev, nPPTX, nPPTY, aZoomFract,aZoomFract, true, true)); // bTotalSize
+
+ pColIter->setValue(nNewWidth);
+ aScope.setNeedMore(true);
+ }
+ pColIter->next();
+ }
+ else
+ {
+ // No more cell in this column. Move to the left column and start at row 0.
+
+ bool bNewTab = false;
+
+ aScope.setRow(0);
+ aScope.incCol(-1);
+
+ if (aScope.Col() < 0)
+ {
+ // No more column to the left. Move to the right-most column of the next sheet.
+ aScope.setCol(MaxCol());
+ aScope.incTab();
+ bNewTab = true;
+ }
+
+ if (!ValidTab(aScope.Tab()) || aScope.Tab() >= static_cast<SCTAB>(maTabs.size()) || !maTabs[aScope.Tab()] )
+ {
+ // Sheet doesn't exist at specified sheet position. Restart at sheet 0.
+ aScope.setTab(0);
+ nRestart++;
+ bNewTab = true;
+ }
+
+ if ( nRestart < 2 )
+ {
+ if ( bNewTab )
+ {
+ pTab = maTabs[aScope.Tab()].get();
+ aScope.setCol(pTab->ClampToAllocatedColumns(aScope.Col()));
+ pStyle = static_cast<ScStyleSheet*>(aScope.getStylePool()->Find(
+ pTab->aPageStyle, SfxStyleFamily::Page));
+
+ if ( pStyle )
+ {
+ // Check if the scale-to-pages setting is set. If
+ // set, we exit the loop. If not, get the page
+ // scale factor of the new sheet.
+ if (getScaleValue(*pStyle, ATTR_PAGE_SCALETOPAGES) == 0)
+ {
+ nZoom = getScaleValue(*pStyle, ATTR_PAGE_SCALE);
+ aZoomFract = Fraction(nZoom, 100);
+ }
+ else
+ nZoom = 0;
+ }
+ else
+ {
+ OSL_FAIL( "Missing StyleSheet :-/" );
+ }
+ }
+
+ if ( nZoom > 0 )
+ {
+ pCol = &pTab->aCol[aScope.Col()];
+ pColIter.emplace(*this, *pCol, aScope.Row(), MaxRow());
+ }
+ else
+ {
+ aScope.incTab(); // Move to the next sheet as the current one has scale-to-pages set.
+ return false;
+ }
+ }
+ }
+
+ ++nCount;
+
+ if (!aScope.continueIter())
+ break;
+ }
+
+ return aScope.getNeedMore();
+}
+
+void ScDocument::RepaintRange( const ScRange& rRange )
+{
+ if ( bIsVisible && mpShell )
+ {
+ ScModelObj* pModel = comphelper::getFromUnoTunnel<ScModelObj>( mpShell->GetModel() );
+ if ( pModel )
+ pModel->RepaintRange( rRange ); // locked repaints are checked there
+ }
+}
+
+void ScDocument::RepaintRange( const ScRangeList& rRange )
+{
+ if ( bIsVisible && mpShell )
+ {
+ ScModelObj* pModel = comphelper::getFromUnoTunnel<ScModelObj>( mpShell->GetModel() );
+ if ( pModel )
+ pModel->RepaintRange( rRange ); // locked repaints are checked there
+ }
+}
+
+void ScDocument::SaveDdeLinks(SvStream& rStream) const
+{
+ // when 4.0-Export, remove all with mode != DEFAULT
+ bool bExport40 = ( rStream.GetVersion() <= SOFFICE_FILEFORMAT_40 );
+
+ const ::sfx2::SvBaseLinks& rLinks = GetLinkManager()->GetLinks();
+ sal_uInt16 nCount = rLinks.size();
+
+ // Count them first
+
+ sal_uInt16 nDdeCount = 0;
+ sal_uInt16 i;
+ for (i=0; i<nCount; i++)
+ {
+ ::sfx2::SvBaseLink* pBase = rLinks[i].get();
+ if (ScDdeLink* pLink = dynamic_cast<ScDdeLink*>(pBase))
+ if ( !bExport40 || pLink->GetMode() == SC_DDE_DEFAULT )
+ ++nDdeCount;
+ }
+
+ // Header
+
+ ScMultipleWriteHeader aHdr( rStream );
+ rStream.WriteUInt16( nDdeCount );
+
+ // Save links
+
+ for (i=0; i<nCount; i++)
+ {
+ ::sfx2::SvBaseLink* pBase = rLinks[i].get();
+ if (ScDdeLink* pLink = dynamic_cast<ScDdeLink*>(pBase))
+ {
+ if ( !bExport40 || pLink->GetMode() == SC_DDE_DEFAULT )
+ pLink->Store( rStream, aHdr );
+ }
+ }
+}
+
+void ScDocument::LoadDdeLinks(SvStream& rStream)
+{
+ sfx2::LinkManager* pMgr = GetDocLinkManager().getLinkManager(bAutoCalc);
+ if (!pMgr)
+ return;
+
+ ScMultipleReadHeader aHdr( rStream );
+
+ sal_uInt16 nCount(0);
+ rStream.ReadUInt16( nCount );
+
+ const rtl_TextEncoding eCharSet = rStream.GetStreamCharSet();
+ const size_t nMinStringSize = eCharSet == RTL_TEXTENCODING_UNICODE ? sizeof(sal_uInt32) : sizeof(sal_uInt16);
+ const size_t nMinRecordSize = 1 + nMinStringSize*3;
+ const size_t nMaxRecords = rStream.remainingSize() / nMinRecordSize;
+ if (nCount > nMaxRecords)
+ {
+ SAL_WARN("sc", "Parsing error: " << nMaxRecords <<
+ " max possible entries, but " << nCount << " claimed, truncating");
+ nCount = nMaxRecords;
+ }
+
+ for (sal_uInt16 i=0; i<nCount; ++i)
+ {
+ ScDdeLink* pLink = new ScDdeLink( *this, rStream, aHdr );
+ pMgr->InsertDDELink(pLink, pLink->GetAppl(), pLink->GetTopic(), pLink->GetItem());
+ }
+}
+
+void ScDocument::SetInLinkUpdate(bool bSet)
+{
+ // called from TableLink and AreaLink
+
+ OSL_ENSURE( bInLinkUpdate != bSet, "SetInLinkUpdate twice" );
+ bInLinkUpdate = bSet;
+}
+
+bool ScDocument::IsInLinkUpdate() const
+{
+ return bInLinkUpdate || IsInDdeLinkUpdate();
+}
+
+void ScDocument::UpdateExternalRefLinks(weld::Window* pWin)
+{
+ if (!pExternalRefMgr)
+ return;
+
+ sfx2::LinkManager* pMgr = GetDocLinkManager().getLinkManager(bAutoCalc);
+ if (!pMgr)
+ return;
+
+ const ::sfx2::SvBaseLinks& rLinks = pMgr->GetLinks();
+ sal_uInt16 nCount = rLinks.size();
+
+ bool bAny = false;
+
+ // Collect all the external ref links first.
+ std::vector<ScExternalRefLink*> aRefLinks;
+ for (sal_uInt16 i = 0; i < nCount; ++i)
+ {
+ ::sfx2::SvBaseLink* pBase = rLinks[i].get();
+ ScExternalRefLink* pRefLink = dynamic_cast<ScExternalRefLink*>(pBase);
+ if (pRefLink)
+ aRefLinks.push_back(pRefLink);
+ }
+
+ weld::WaitObject aWaitSwitch(pWin);
+
+ pExternalRefMgr->enableDocTimer(false);
+ ScProgress aProgress(GetDocumentShell(), ScResId(SCSTR_UPDATE_EXTDOCS), aRefLinks.size(), true);
+ for (size_t i = 0, n = aRefLinks.size(); i < n; ++i)
+ {
+ aProgress.SetState(i+1);
+
+ ScExternalRefLink* pRefLink = aRefLinks[i];
+ if (pRefLink->Update())
+ {
+ bAny = true;
+ continue;
+ }
+
+ // Update failed. Notify the user.
+
+ OUString aFile;
+ sfx2::LinkManager::GetDisplayNames(pRefLink, nullptr, &aFile);
+ // Decode encoded URL for display friendliness.
+ INetURLObject aUrl(aFile,INetURLObject::EncodeMechanism::WasEncoded);
+ aFile = aUrl.GetMainURL(INetURLObject::DecodeMechanism::Unambiguous);
+
+ OUString sMessage = ScResId(SCSTR_EXTDOC_NOT_LOADED) +
+ "\n\n" +
+ aFile;
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pWin,
+ VclMessageType::Warning, VclButtonsType::Ok,
+ sMessage));
+ xBox->run();
+ }
+
+ pExternalRefMgr->enableDocTimer(true);
+
+ if (!bAny)
+ return;
+
+ TrackFormulas();
+ mpShell->Broadcast( SfxHint(SfxHintId::ScDataChanged) );
+
+ // #i101960# set document modified, as in TrackTimeHdl for DDE links
+ if (!mpShell->IsModified())
+ {
+ mpShell->SetModified();
+ SfxBindings* pBindings = GetViewBindings();
+ if (pBindings)
+ {
+ pBindings->Invalidate( SID_SAVEDOC );
+ pBindings->Invalidate( SID_DOC_MODIFIED );
+ }
+ }
+}
+
+void ScDocument::CopyDdeLinks( ScDocument& rDestDoc ) const
+{
+ if (bIsClip) // Create from Stream
+ {
+ if (pClipData)
+ {
+ pClipData->Seek(0);
+ rDestDoc.LoadDdeLinks(*pClipData);
+ }
+
+ return;
+ }
+
+ const sfx2::LinkManager* pMgr = GetDocLinkManager().getExistingLinkManager();
+ if (!pMgr)
+ return;
+
+ sfx2::LinkManager* pDestMgr = rDestDoc.GetDocLinkManager().getLinkManager(rDestDoc.bAutoCalc);
+ if (!pDestMgr)
+ return;
+
+ const sfx2::SvBaseLinks& rLinks = pMgr->GetLinks();
+ for (const auto & rLink : rLinks)
+ {
+ const sfx2::SvBaseLink* pBase = rLink.get();
+ if (const ScDdeLink* p = dynamic_cast<const ScDdeLink*>(pBase))
+ {
+ ScDdeLink* pNew = new ScDdeLink(rDestDoc, *p);
+ pDestMgr->InsertDDELink(
+ pNew, pNew->GetAppl(), pNew->GetTopic(), pNew->GetItem());
+ }
+ }
+}
+
+namespace {
+
+/** Tries to find the specified DDE link.
+ @param pnDdePos (out-param) if not 0, the index of the DDE link is returned here
+ (does not include other links from link manager).
+ @return The DDE link, if it exists, otherwise 0. */
+ScDdeLink* lclGetDdeLink(
+ const sfx2::LinkManager* pLinkManager,
+ std::u16string_view rAppl, std::u16string_view rTopic, std::u16string_view rItem, sal_uInt8 nMode,
+ size_t* pnDdePos = nullptr )
+{
+ if( pLinkManager )
+ {
+ const ::sfx2::SvBaseLinks& rLinks = pLinkManager->GetLinks();
+ size_t nCount = rLinks.size();
+ if( pnDdePos ) *pnDdePos = 0;
+ for( size_t nIndex = 0; nIndex < nCount; ++nIndex )
+ {
+ ::sfx2::SvBaseLink* pLink = rLinks[ nIndex ].get();
+ if( ScDdeLink* pDdeLink = dynamic_cast<ScDdeLink*>( pLink ) )
+ {
+ if( (pDdeLink->GetAppl() == rAppl) &&
+ (pDdeLink->GetTopic() == rTopic) &&
+ (pDdeLink->GetItem() == rItem) &&
+ ((nMode == SC_DDE_IGNOREMODE) || (nMode == pDdeLink->GetMode())) )
+ return pDdeLink;
+ if( pnDdePos ) ++*pnDdePos;
+ }
+ }
+ }
+ return nullptr;
+}
+
+/** Returns a pointer to the specified DDE link.
+ @param nDdePos Index of the DDE link (does not include other links from link manager).
+ @return The DDE link, if it exists, otherwise 0. */
+ScDdeLink* lclGetDdeLink( const sfx2::LinkManager* pLinkManager, size_t nDdePos )
+{
+ if( pLinkManager )
+ {
+ const ::sfx2::SvBaseLinks& rLinks = pLinkManager->GetLinks();
+ size_t nCount = rLinks.size();
+ size_t nDdeIndex = 0; // counts only the DDE links
+ for( size_t nIndex = 0; nIndex < nCount; ++nIndex )
+ {
+ ::sfx2::SvBaseLink* pLink = rLinks[ nIndex ].get();
+ if( ScDdeLink* pDdeLink = dynamic_cast<ScDdeLink*>( pLink ) )
+ {
+ if( nDdeIndex == nDdePos )
+ return pDdeLink;
+ ++nDdeIndex;
+ }
+ }
+ }
+ return nullptr;
+}
+
+} // namespace
+
+bool ScDocument::FindDdeLink( std::u16string_view rAppl, std::u16string_view rTopic, std::u16string_view rItem,
+ sal_uInt8 nMode, size_t& rnDdePos )
+{
+ return lclGetDdeLink( GetLinkManager(), rAppl, rTopic, rItem, nMode, &rnDdePos ) != nullptr;
+}
+
+bool ScDocument::GetDdeLinkData( size_t nDdePos, OUString& rAppl, OUString& rTopic, OUString& rItem ) const
+{
+ if( const ScDdeLink* pDdeLink = lclGetDdeLink( GetLinkManager(), nDdePos ) )
+ {
+ rAppl = pDdeLink->GetAppl();
+ rTopic = pDdeLink->GetTopic();
+ rItem = pDdeLink->GetItem();
+ return true;
+ }
+ return false;
+}
+
+bool ScDocument::GetDdeLinkMode( size_t nDdePos, sal_uInt8& rnMode ) const
+{
+ if( const ScDdeLink* pDdeLink = lclGetDdeLink( GetLinkManager(), nDdePos ) )
+ {
+ rnMode = pDdeLink->GetMode();
+ return true;
+ }
+ return false;
+}
+
+const ScMatrix* ScDocument::GetDdeLinkResultMatrix( size_t nDdePos ) const
+{
+ const ScDdeLink* pDdeLink = lclGetDdeLink( GetLinkManager(), nDdePos );
+ return pDdeLink ? pDdeLink->GetResult() : nullptr;
+}
+
+bool ScDocument::CreateDdeLink( const OUString& rAppl, const OUString& rTopic, const OUString& rItem, sal_uInt8 nMode, const ScMatrixRef& pResults )
+{
+ /* Create a DDE link without updating it (i.e. for Excel import), to prevent
+ unwanted connections. First try to find existing link. Set result array
+ on existing and new links. */
+ //TODO: store DDE links additionally at document (for efficiency)?
+ OSL_ENSURE( nMode != SC_DDE_IGNOREMODE, "ScDocument::CreateDdeLink - SC_DDE_IGNOREMODE not allowed here" );
+
+ sfx2::LinkManager* pMgr = GetDocLinkManager().getLinkManager(bAutoCalc);
+ if (!pMgr)
+ return false;
+
+ if (nMode != SC_DDE_IGNOREMODE)
+ {
+ ScDdeLink* pDdeLink = lclGetDdeLink(pMgr, rAppl, rTopic, rItem, nMode);
+ if( !pDdeLink )
+ {
+ // create a new DDE link, but without TryUpdate
+ pDdeLink = new ScDdeLink( *this, rAppl, rTopic, rItem, nMode );
+ pMgr->InsertDDELink(pDdeLink, rAppl, rTopic, rItem);
+ }
+
+ // insert link results
+ if( pResults )
+ pDdeLink->SetResult( pResults );
+
+ return true;
+ }
+ return false;
+}
+
+bool ScDocument::SetDdeLinkResultMatrix( size_t nDdePos, const ScMatrixRef& pResults )
+{
+ if( ScDdeLink* pDdeLink = lclGetDdeLink( GetLinkManager(), nDdePos ) )
+ {
+ pDdeLink->SetResult( pResults );
+ return true;
+ }
+ return false;
+}
+
+bool ScDocument::HasAreaLinks() const
+{
+ const sfx2::LinkManager* pMgr = GetDocLinkManager().getExistingLinkManager();
+ if (!pMgr)
+ return false;
+
+ const ::sfx2::SvBaseLinks& rLinks = pMgr->GetLinks();
+ sal_uInt16 nCount = rLinks.size();
+ for (sal_uInt16 i=0; i<nCount; i++)
+ if (nullptr != dynamic_cast<const ScAreaLink* >(rLinks[i].get()))
+ return true;
+
+ return false;
+}
+
+void ScDocument::UpdateAreaLinks()
+{
+ sfx2::LinkManager* pMgr = GetDocLinkManager().getLinkManager(false);
+ if (!pMgr)
+ return;
+
+ const ::sfx2::SvBaseLinks& rLinks = pMgr->GetLinks();
+ for (const auto & rLink : rLinks)
+ {
+ ::sfx2::SvBaseLink* pBase = rLink.get();
+ if (dynamic_cast<const ScAreaLink*>( pBase) != nullptr)
+ pBase->Update();
+ }
+}
+
+void ScDocument::DeleteAreaLinksOnTab( SCTAB nTab )
+{
+ sfx2::LinkManager* pMgr = GetDocLinkManager().getLinkManager(false);
+ if (!pMgr)
+ return;
+
+ const ::sfx2::SvBaseLinks& rLinks = pMgr->GetLinks();
+ sfx2::SvBaseLinks::size_type nPos = 0;
+ while ( nPos < rLinks.size() )
+ {
+ const ::sfx2::SvBaseLink* pBase = rLinks[nPos].get();
+ const ScAreaLink* pLink = dynamic_cast<const ScAreaLink*>(pBase);
+ if (pLink && pLink->GetDestArea().aStart.Tab() == nTab)
+ pMgr->Remove(nPos);
+ else
+ ++nPos;
+ }
+}
+
+void ScDocument::UpdateRefAreaLinks( UpdateRefMode eUpdateRefMode,
+ const ScRange& rRange, SCCOL nDx, SCROW nDy, SCTAB nDz )
+{
+ sfx2::LinkManager* pMgr = GetDocLinkManager().getLinkManager(false);
+ if (!pMgr)
+ return;
+
+ bool bAnyUpdate = false;
+
+ const ::sfx2::SvBaseLinks& rLinks = pMgr->GetLinks();
+ sal_uInt16 nCount = rLinks.size();
+ for (sal_uInt16 i=0; i<nCount; i++)
+ {
+ ::sfx2::SvBaseLink* pBase = rLinks[i].get();
+ if (ScAreaLink* pLink = dynamic_cast<ScAreaLink*>(pBase))
+ {
+ ScRange aOutRange = pLink->GetDestArea();
+
+ SCCOL nCol1 = aOutRange.aStart.Col();
+ SCROW nRow1 = aOutRange.aStart.Row();
+ SCTAB nTab1 = aOutRange.aStart.Tab();
+ SCCOL nCol2 = aOutRange.aEnd.Col();
+ SCROW nRow2 = aOutRange.aEnd.Row();
+ SCTAB nTab2 = aOutRange.aEnd.Tab();
+
+ ScRefUpdateRes eRes =
+ ScRefUpdate::Update( this, 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 )
+ {
+ pLink->SetDestArea( ScRange( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ) );
+ bAnyUpdate = true;
+ }
+ }
+ }
+
+ if ( !bAnyUpdate )
+ return;
+
+ // #i52120# Look for duplicates (after updating all positions).
+ // If several links start at the same cell, the one with the lower index is removed
+ // (file format specifies only one link definition for a cell).
+
+ sal_uInt16 nFirstIndex = 0;
+ while ( nFirstIndex < nCount )
+ {
+ bool bFound = false;
+ ::sfx2::SvBaseLink* pFirst = rLinks[nFirstIndex].get();
+ if (ScAreaLink* pFirstLink = dynamic_cast<ScAreaLink*>(pFirst))
+ {
+ ScAddress aFirstPos = pFirstLink->GetDestArea().aStart;
+ for ( sal_uInt16 nSecondIndex = nFirstIndex + 1; nSecondIndex < nCount && !bFound; ++nSecondIndex )
+ {
+ ::sfx2::SvBaseLink* pSecond = rLinks[nSecondIndex].get();
+ ScAreaLink* pSecondLink = dynamic_cast<ScAreaLink*>(pSecond);
+ if (pSecondLink && pSecondLink->GetDestArea().aStart == aFirstPos)
+ {
+ // remove the first link, exit the inner loop, don't increment nFirstIndex
+ pMgr->Remove(pFirst);
+ nCount = rLinks.size();
+ bFound = true;
+ }
+ }
+ }
+ if (!bFound)
+ ++nFirstIndex;
+ }
+}
+
+void ScDocument::CheckLinkFormulaNeedingCheck( const ScTokenArray& rCode )
+{
+ if (HasLinkFormulaNeedingCheck())
+ return;
+
+ // Prefer RPN over tokenized formula if available.
+ if (rCode.GetCodeLen())
+ {
+ if (rCode.HasOpCodeRPN(ocDde) || rCode.HasOpCodeRPN(ocWebservice))
+ SetLinkFormulaNeedingCheck(true);
+ }
+ else if (rCode.GetLen())
+ {
+ if (rCode.HasOpCode(ocDde) || rCode.HasOpCode(ocWebservice))
+ SetLinkFormulaNeedingCheck(true);
+ }
+ else
+ {
+ // Possible with named expression without expression like Excel
+ // internal print ranges, obscure user define names, ... formula error
+ // cells without formula ...
+ SAL_WARN("sc.core","ScDocument::CheckLinkFormulaNeedingCheck - called with empty ScTokenArray");
+ }
+}
+
+// TimerDelays etc.
+void ScDocument::KeyInput()
+{
+ if ( pChartListenerCollection->hasListeners() )
+ pChartListenerCollection->StartTimer();
+ if (apTemporaryChartLock)
+ apTemporaryChartLock->StartOrContinueLocking();
+}
+
+SfxBindings* ScDocument::GetViewBindings()
+{
+ // used to invalidate slots after changes to this document
+
+ if ( !mpShell )
+ return nullptr; // no ObjShell -> no view
+
+ // first check current view
+ SfxViewFrame* pViewFrame = SfxViewFrame::Current();
+ if ( pViewFrame && pViewFrame->GetObjectShell() != mpShell ) // wrong document?
+ pViewFrame = nullptr;
+
+ // otherwise use first view for this doc
+ if ( !pViewFrame )
+ pViewFrame = SfxViewFrame::GetFirst( mpShell );
+
+ if (pViewFrame)
+ return &pViewFrame->GetBindings();
+ else
+ return nullptr;
+}
+
+void ScDocument::TransliterateText( const ScMarkData& rMultiMark, TransliterationFlags nType )
+{
+ OSL_ENSURE( rMultiMark.IsMultiMarked(), "TransliterateText: no selection" );
+
+ utl::TransliterationWrapper aTransliterationWrapper( comphelper::getProcessComponentContext(), nType );
+ bool bConsiderLanguage = aTransliterationWrapper.needLanguageForTheMode();
+ LanguageType nLanguage = LANGUAGE_SYSTEM;
+
+ std::unique_ptr<ScEditEngineDefaulter> pEngine; // not using mpEditEngine member because of defaults
+
+ SCTAB nCount = GetTableCount();
+ for (const SCTAB& nTab : rMultiMark)
+ {
+ if (nTab >= nCount)
+ break;
+
+ if ( maTabs[nTab] )
+ {
+ SCCOL nCol = 0;
+ SCROW nRow = 0;
+
+ bool bFound = rMultiMark.IsCellMarked( nCol, nRow );
+ if (!bFound)
+ bFound = GetNextMarkedCell( nCol, nRow, nTab, rMultiMark );
+
+ while (bFound)
+ {
+ ScRefCellValue aCell(*this, ScAddress(nCol, nRow, nTab));
+
+ // fdo#32786 TITLE_CASE/SENTENCE_CASE need the extra handling in EditEngine (loop over words/sentences).
+ // Still use TransliterationWrapper directly for text cells with other transliteration types,
+ // for performance reasons.
+ if (aCell.meType == CELLTYPE_EDIT ||
+ (aCell.meType == CELLTYPE_STRING &&
+ ( nType == TransliterationFlags::SENTENCE_CASE || nType == TransliterationFlags::TITLE_CASE)))
+ {
+ if (!pEngine)
+ pEngine.reset(new ScFieldEditEngine(this, GetEnginePool(), GetEditPool()));
+
+ // defaults from cell attributes must be set so right language is used
+ const ScPatternAttr* pPattern = GetPattern( nCol, nRow, nTab );
+ SfxItemSet aDefaults( pEngine->GetEmptyItemSet() );
+ if ( ScStyleSheet* pPreviewStyle = GetPreviewCellStyle( nCol, nRow, nTab ) )
+ {
+ ScPatternAttr aPreviewPattern( *pPattern );
+ aPreviewPattern.SetStyleSheet(pPreviewStyle);
+ aPreviewPattern.FillEditItemSet( &aDefaults );
+ }
+ else
+ {
+ SfxItemSet* pFontSet = GetPreviewFont( nCol, nRow, nTab );
+ pPattern->FillEditItemSet( &aDefaults, pFontSet );
+ }
+ pEngine->SetDefaults( std::move(aDefaults) );
+ if (aCell.meType == CELLTYPE_STRING)
+ pEngine->SetTextCurrentDefaults(aCell.mpString->getString());
+ else if (aCell.mpEditText)
+ pEngine->SetTextCurrentDefaults(*aCell.mpEditText);
+
+ pEngine->ClearModifyFlag();
+
+ sal_Int32 nLastPar = pEngine->GetParagraphCount();
+ if (nLastPar)
+ --nLastPar;
+ sal_Int32 nTxtLen = pEngine->GetTextLen(nLastPar);
+ ESelection aSelAll( 0, 0, nLastPar, nTxtLen );
+
+ pEngine->TransliterateText( aSelAll, nType );
+
+ if ( pEngine->IsModified() )
+ {
+ ScEditAttrTester aTester( pEngine.get() );
+ if ( aTester.NeedsObject() )
+ {
+ // remove defaults (paragraph attributes) before creating text object
+ pEngine->SetDefaults( std::make_unique<SfxItemSet>( pEngine->GetEmptyItemSet() ) );
+
+ // The cell will take ownership of the text object instance.
+ SetEditText(ScAddress(nCol,nRow,nTab), pEngine->CreateTextObject());
+ }
+ else
+ {
+ ScSetStringParam aParam;
+ aParam.setTextInput();
+ SetString(ScAddress(nCol,nRow,nTab), pEngine->GetText(), &aParam);
+ }
+ }
+ }
+
+ else if (aCell.meType == CELLTYPE_STRING)
+ {
+ OUString aOldStr = aCell.mpString->getString();
+ sal_Int32 nOldLen = aOldStr.getLength();
+
+ if ( bConsiderLanguage )
+ {
+ SvtScriptType nScript = GetStringScriptType( aOldStr ); //TODO: cell script type?
+ sal_uInt16 nWhich = ( nScript == SvtScriptType::ASIAN ) ? ATTR_CJK_FONT_LANGUAGE :
+ ( ( nScript == SvtScriptType::COMPLEX ) ? ATTR_CTL_FONT_LANGUAGE :
+ ATTR_FONT_LANGUAGE );
+ nLanguage = static_cast<const SvxLanguageItem*>(GetAttr( nCol, nRow, nTab, nWhich ))->GetValue();
+ }
+
+ uno::Sequence<sal_Int32> aOffsets;
+ OUString aNewStr = aTransliterationWrapper.transliterate( aOldStr, nLanguage, 0, nOldLen, &aOffsets );
+
+ if ( aNewStr != aOldStr )
+ {
+ ScSetStringParam aParam;
+ aParam.setTextInput();
+ SetString(ScAddress(nCol,nRow,nTab), aNewStr, &aParam);
+ }
+ }
+ bFound = GetNextMarkedCell( nCol, nRow, nTab, rMultiMark );
+ }
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/documen9.cxx b/sc/source/core/data/documen9.cxx
new file mode 100644
index 000000000..28f0e2333
--- /dev/null
+++ b/sc/source/core/data/documen9.cxx
@@ -0,0 +1,681 @@
+/* -*- 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 <editeng/eeitem.hxx>
+
+#include <editeng/autokernitem.hxx>
+#include <editeng/fontitem.hxx>
+#include <editeng/langitem.hxx>
+#include <o3tl/unit_conversion.hxx>
+#include <osl/thread.h>
+#include <osl/diagnose.h>
+#include <svl/asiancfg.hxx>
+#include <svx/svditer.hxx>
+#include <svx/svdograf.hxx>
+#include <svx/svdoole2.hxx>
+#include <svx/svdpage.hxx>
+#include <svx/svdundo.hxx>
+#include <svx/xtable.hxx>
+#include <sfx2/objsh.hxx>
+#include <sfx2/printer.hxx>
+
+#include <document.hxx>
+#include <docoptio.hxx>
+#include <table.hxx>
+#include <drwlayer.hxx>
+#include <markdata.hxx>
+#include <patattr.hxx>
+#include <rechead.hxx>
+#include <poolhelp.hxx>
+#include <docpool.hxx>
+#include <editutil.hxx>
+#include <charthelper.hxx>
+#include <conditio.hxx>
+#include <documentlinkmgr.hxx>
+
+using namespace ::com::sun::star;
+
+SfxBroadcaster* ScDocument::GetDrawBroadcaster()
+{
+ return mpDrawLayer.get();
+}
+
+void ScDocument::BeginDrawUndo()
+{
+ if (mpDrawLayer)
+ mpDrawLayer->BeginCalcUndo(false);
+}
+
+void ScDocument::TransferDrawPage(const ScDocument& rSrcDoc, SCTAB nSrcPos, SCTAB nDestPos)
+{
+ if (mpDrawLayer && rSrcDoc.mpDrawLayer)
+ {
+ SdrPage* pOldPage = rSrcDoc.mpDrawLayer->GetPage(static_cast<sal_uInt16>(nSrcPos));
+ SdrPage* pNewPage = mpDrawLayer->GetPage(static_cast<sal_uInt16>(nDestPos));
+
+ if (pOldPage && pNewPage)
+ {
+ SdrObjListIter aIter( pOldPage, SdrIterMode::Flat );
+ SdrObject* pOldObject = aIter.Next();
+ while (pOldObject)
+ {
+ // Clone to target SdrModel
+ SdrObject* pNewObject(pOldObject->CloneSdrObject(*mpDrawLayer));
+ pNewObject->NbcMove(Size(0,0));
+ pNewPage->InsertObject( pNewObject );
+
+ if (mpDrawLayer->IsRecording())
+ mpDrawLayer->AddCalcUndo( std::make_unique<SdrUndoInsertObj>( *pNewObject ) );
+
+ pOldObject = aIter.Next();
+ }
+ }
+ }
+
+ // make sure the data references of charts are adapted
+ // (this must be after InsertObject!)
+ ScChartHelper::AdjustRangesOfChartsOnDestinationPage( rSrcDoc, *this, nSrcPos, nDestPos );
+ ScChartHelper::UpdateChartsOnDestinationPage(*this, nDestPos);
+}
+
+void ScDocument::InitDrawLayer( SfxObjectShell* pDocShell )
+{
+ if (pDocShell && !mpShell)
+ {
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ mpShell = pDocShell;
+ }
+
+ if (mpDrawLayer)
+ return;
+
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ OUString aName;
+ if ( mpShell && !mpShell->IsLoading() ) // don't call GetTitle while loading
+ aName = mpShell->GetTitle();
+ mpDrawLayer.reset(new ScDrawLayer( this, aName ));
+
+ sfx2::LinkManager* pMgr = GetDocLinkManager().getLinkManager(bAutoCalc);
+ if (pMgr)
+ mpDrawLayer->SetLinkManager(pMgr);
+
+ // set DrawingLayer's SfxItemPool at Calc's SfxItemPool as
+ // secondary pool to support DrawingLayer FillStyle ranges (and similar)
+ // in SfxItemSets using the Calc SfxItemPool. This is e.g. needed when
+ // the PageStyle using SvxBrushItem is visualized and will be potentially
+ // used more intense in the future
+ if (mxPoolHelper.is() && !IsClipOrUndo()) //Using IsClipOrUndo as a proxy for SharePooledResources called
+ {
+ ScDocumentPool* pLocalPool = mxPoolHelper->GetDocPool();
+
+ if (pLocalPool)
+ {
+ OSL_ENSURE(!pLocalPool->GetSecondaryPool(), "OOps, already a secondary pool set where the DrawingLayer ItemPool is to be placed (!)");
+ pLocalPool->SetSecondaryPool(&mpDrawLayer->GetItemPool());
+ }
+ }
+
+ // Drawing pages are accessed by table number, so they must also be present
+ // for preceding table numbers, even if the tables aren't allocated
+ // (important for clipboard documents).
+
+ SCTAB nDrawPages = 0;
+ SCTAB nTab;
+ for (nTab=0; nTab < static_cast<SCTAB>(maTabs.size()); nTab++)
+ if (maTabs[nTab])
+ nDrawPages = nTab + 1; // needed number of pages
+
+ for (nTab=0; nTab<nDrawPages && nTab < static_cast<SCTAB>(maTabs.size()); nTab++)
+ {
+ mpDrawLayer->ScAddPage( nTab ); // always add page, with or without the table
+ if (maTabs[nTab])
+ {
+ OUString aTabName = maTabs[nTab]->GetName();
+ mpDrawLayer->ScRenamePage( nTab, aTabName );
+
+ maTabs[nTab]->SetDrawPageSize(false,false); // set the right size immediately
+ }
+ }
+
+ mpDrawLayer->SetDefaultTabulator( GetDocOptions().GetTabDistance() );
+
+ UpdateDrawPrinter();
+
+ // set draw defaults directly
+ SfxItemPool& rDrawPool = mpDrawLayer->GetItemPool();
+ rDrawPool.SetPoolDefaultItem( SvxAutoKernItem( true, EE_CHAR_PAIRKERNING ) );
+
+ UpdateDrawLanguages();
+ if (bImportingXML)
+ mpDrawLayer->EnableAdjust(false);
+
+ mpDrawLayer->SetForbiddenCharsTable( xForbiddenCharacters );
+ mpDrawLayer->SetCharCompressType( GetAsianCompression() );
+ mpDrawLayer->SetKernAsianPunctuation( GetAsianKerning() );
+}
+
+void ScDocument::UpdateDrawLanguages()
+{
+ if (mpDrawLayer)
+ {
+ SfxItemPool& rDrawPool = mpDrawLayer->GetItemPool();
+ rDrawPool.SetPoolDefaultItem( SvxLanguageItem( eLanguage, EE_CHAR_LANGUAGE ) );
+ rDrawPool.SetPoolDefaultItem( SvxLanguageItem( eCjkLanguage, EE_CHAR_LANGUAGE_CJK ) );
+ rDrawPool.SetPoolDefaultItem( SvxLanguageItem( eCtlLanguage, EE_CHAR_LANGUAGE_CTL ) );
+ }
+}
+
+void ScDocument::UpdateDrawPrinter()
+{
+ if (mpDrawLayer)
+ {
+ // use the printer even if IsValid is false
+ // Application::GetDefaultDevice causes trouble with changing MapModes
+ mpDrawLayer->SetRefDevice(GetRefDevice());
+ }
+}
+
+void ScDocument::SetDrawPageSize(SCTAB nTab)
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return;
+
+ maTabs[nTab]->SetDrawPageSize();
+}
+
+bool ScDocument::IsChart( const SdrObject* pObject )
+{
+ // IsChart() implementation moved to svx drawinglayer
+ if(pObject && SdrObjKind::OLE2 == pObject->GetObjIdentifier())
+ {
+ return static_cast<const SdrOle2Obj*>(pObject)->IsChart();
+ }
+
+ return false;
+}
+
+IMPL_LINK( ScDocument, GetUserDefinedColor, sal_uInt16, nColorIndex, Color* )
+{
+ rtl::Reference<XColorList> xColorList;
+ if (mpDrawLayer)
+ xColorList = mpDrawLayer->GetColorList();
+ else
+ {
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ if (!pColorList.is())
+ pColorList = XColorList::CreateStdColorList();
+ xColorList = pColorList;
+ }
+ return const_cast<Color*>(&(xColorList->GetColor(nColorIndex)->GetColor()));
+}
+
+void ScDocument::DeleteDrawLayer()
+{
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+
+ // remove DrawingLayer's SfxItemPool from Calc's SfxItemPool where
+ // it is registered as secondary pool
+ if (mxPoolHelper.is() && !IsClipOrUndo()) //Using IsClipOrUndo as a proxy for SharePooledResources called
+ {
+ ScDocumentPool* pLocalPool = mxPoolHelper->GetDocPool();
+
+ if(pLocalPool && pLocalPool->GetSecondaryPool())
+ {
+ pLocalPool->SetSecondaryPool(nullptr);
+ }
+ }
+ mpDrawLayer.reset();
+}
+
+bool ScDocument::DrawGetPrintArea( ScRange& rRange, bool bSetHor, bool bSetVer ) const
+{
+ return mpDrawLayer->GetPrintArea( rRange, bSetHor, bSetVer );
+}
+
+void ScDocument::DeleteObjectsInArea( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ const ScMarkData& rMark, bool bAnchored)
+{
+ if (!mpDrawLayer)
+ return;
+
+ SCTAB nTabCount = GetTableCount();
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nTabCount)
+ break;
+ if (maTabs[rTab])
+ mpDrawLayer->DeleteObjectsInArea( rTab, nCol1, nRow1, nCol2, nRow2, bAnchored);
+ }
+}
+
+void ScDocument::DeleteObjectsInSelection( const ScMarkData& rMark )
+{
+ if (!mpDrawLayer)
+ return;
+
+ mpDrawLayer->DeleteObjectsInSelection( rMark );
+}
+
+bool ScDocument::HasOLEObjectsInArea( const ScRange& rRange, const ScMarkData* pTabMark )
+{
+ // pTabMark is used only for selected tables. If pTabMark is 0, all tables of rRange are used.
+
+ if (!mpDrawLayer)
+ return false;
+
+ SCTAB nStartTab = 0;
+ SCTAB nEndTab = static_cast<SCTAB>(maTabs.size());
+ if ( !pTabMark )
+ {
+ nStartTab = rRange.aStart.Tab();
+ nEndTab = rRange.aEnd.Tab();
+ }
+
+ for (SCTAB nTab = nStartTab; nTab <= nEndTab; nTab++)
+ {
+ if ( !pTabMark || pTabMark->GetTableSelect(nTab) )
+ {
+ tools::Rectangle aMMRect = GetMMRect( rRange.aStart.Col(), rRange.aStart.Row(),
+ rRange.aEnd.Col(), rRange.aEnd.Row(), nTab );
+
+ SdrPage* pPage = mpDrawLayer->GetPage(static_cast<sal_uInt16>(nTab));
+ OSL_ENSURE(pPage,"Page ?");
+ if (pPage)
+ {
+ SdrObjListIter aIter( pPage, SdrIterMode::Flat );
+ SdrObject* pObject = aIter.Next();
+ while (pObject)
+ {
+ if ( pObject->GetObjIdentifier() == SdrObjKind::OLE2 &&
+ aMMRect.Contains( pObject->GetCurrentBoundRect() ) )
+ return true;
+
+ pObject = aIter.Next();
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+void ScDocument::StartAnimations( SCTAB nTab )
+{
+ if (!mpDrawLayer)
+ return;
+ SdrPage* pPage = mpDrawLayer->GetPage(static_cast<sal_uInt16>(nTab));
+ OSL_ENSURE(pPage,"Page ?");
+ if (!pPage)
+ return;
+
+ SdrObjListIter aIter( pPage, SdrIterMode::Flat );
+ SdrObject* pObject = aIter.Next();
+ while (pObject)
+ {
+ if (SdrGrafObj* pGrafObj = dynamic_cast<SdrGrafObj*>(pObject))
+ {
+ if ( pGrafObj->IsAnimated() )
+ {
+ pGrafObj->StartAnimation();
+ }
+ }
+ pObject = aIter.Next();
+ }
+}
+
+bool ScDocument::HasBackgroundDraw( SCTAB nTab, const tools::Rectangle& rMMRect ) const
+{
+ // Are there objects in the background layer who are (partly) affected by rMMRect
+ // (for Drawing optimization, no deletion in front of the background
+ if (!mpDrawLayer)
+ return false;
+ SdrPage* pPage = mpDrawLayer->GetPage(static_cast<sal_uInt16>(nTab));
+ OSL_ENSURE(pPage,"Page ?");
+ if (!pPage)
+ return false;
+
+ bool bFound = false;
+
+ SdrObjListIter aIter( pPage, SdrIterMode::Flat );
+ SdrObject* pObject = aIter.Next();
+ while (pObject && !bFound)
+ {
+ if ( pObject->GetLayer() == SC_LAYER_BACK && pObject->GetCurrentBoundRect().Overlaps( rMMRect ) )
+ bFound = true;
+ pObject = aIter.Next();
+ }
+
+ return bFound;
+}
+
+bool ScDocument::HasAnyDraw( SCTAB nTab, const tools::Rectangle& rMMRect ) const
+{
+ // Are there any objects at all who are (partly) affected by rMMRect?
+ // (To detect blank pages when printing)
+ if (!mpDrawLayer)
+ return false;
+ SdrPage* pPage = mpDrawLayer->GetPage(static_cast<sal_uInt16>(nTab));
+ OSL_ENSURE(pPage,"Page ?");
+ if (!pPage)
+ return false;
+
+ bool bFound = false;
+
+ SdrObjListIter aIter( pPage, SdrIterMode::Flat );
+ SdrObject* pObject = aIter.Next();
+ while (pObject && !bFound)
+ {
+ if ( pObject->GetCurrentBoundRect().Overlaps( rMMRect ) )
+ bFound = true;
+ pObject = aIter.Next();
+ }
+
+ return bFound;
+}
+
+void ScDocument::EnsureGraphicNames()
+{
+ if (mpDrawLayer)
+ mpDrawLayer->EnsureGraphicNames();
+}
+
+SdrObject* ScDocument::GetObjectAtPoint( SCTAB nTab, const Point& rPos )
+{
+ // for Drag&Drop on draw object
+ SdrObject* pFound = nullptr;
+ if (mpDrawLayer && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ {
+ SdrPage* pPage = mpDrawLayer->GetPage(static_cast<sal_uInt16>(nTab));
+ OSL_ENSURE(pPage,"Page ?");
+ if (pPage)
+ {
+ SdrObjListIter aIter( pPage, SdrIterMode::Flat );
+ SdrObject* pObject = aIter.Next();
+ while (pObject)
+ {
+ if ( pObject->GetCurrentBoundRect().Contains(rPos) )
+ {
+ // Intern is of no interest
+ // Only object form background layer, when no object form another layer is found
+ SdrLayerID nLayer = pObject->GetLayer();
+ if ( (nLayer != SC_LAYER_INTERN) && (nLayer != SC_LAYER_HIDDEN) )
+ {
+ if ( nLayer != SC_LAYER_BACK ||
+ !pFound || pFound->GetLayer() == SC_LAYER_BACK )
+ {
+ pFound = pObject;
+ }
+ }
+ }
+ // Continue search -> take last (on top) found object
+ pObject = aIter.Next();
+ }
+ }
+ }
+ return pFound;
+}
+
+bool ScDocument::IsPrintEmpty( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow,
+ SCTAB nTab, bool bLeftIsEmpty,
+ ScRange* pLastRange, tools::Rectangle* pLastMM ) const
+{
+ if (!IsBlockEmpty( nStartCol, nStartRow, nEndCol, nEndRow, nTab ))
+ return false;
+
+ if (HasAttrib(ScRange(nStartCol, nStartRow, nTab, nEndCol, nEndRow, nTab), HasAttrFlags::Lines))
+ // We want to print sheets with borders even if there is no cell content.
+ return false;
+
+ tools::Rectangle aMMRect;
+ if ( pLastRange && pLastMM && nTab == pLastRange->aStart.Tab() &&
+ nStartRow == pLastRange->aStart.Row() && nEndRow == pLastRange->aEnd.Row() )
+ {
+ // keep vertical part of aMMRect, only update horizontal position
+ aMMRect = *pLastMM;
+
+ tools::Long nLeft = 0;
+ SCCOL i;
+ for (i=0; i<nStartCol; i++)
+ nLeft += GetColWidth(i,nTab);
+ tools::Long nRight = nLeft;
+ for (i=nStartCol; i<=nEndCol; i++)
+ nRight += GetColWidth(i,nTab);
+
+ aMMRect.SetLeft(o3tl::convert(nLeft, o3tl::Length::twip, o3tl::Length::mm100));
+ aMMRect.SetRight(o3tl::convert(nRight, o3tl::Length::twip, o3tl::Length::mm100));
+ }
+ else
+ aMMRect = GetMMRect( nStartCol, nStartRow, nEndCol, nEndRow, nTab );
+
+ if ( pLastRange && pLastMM )
+ {
+ *pLastRange = ScRange( nStartCol, nStartRow, nTab, nEndCol, nEndRow, nTab );
+ *pLastMM = aMMRect;
+ }
+
+ if ( HasAnyDraw( nTab, aMMRect ))
+ return false;
+
+ if ( nStartCol > 0 && !bLeftIsEmpty )
+ {
+ // similar to in ScPrintFunc::AdjustPrintArea
+ // ExtendPrintArea starting only from the start column of the print area
+
+ SCCOL nExtendCol = nStartCol - 1;
+ SCROW nTmpRow = nEndRow;
+
+ // ExtendMerge() is non-const, but called without refresh. GetPrinter()
+ // might create and assign a printer.
+ ScDocument* pThis = const_cast<ScDocument*>(this);
+
+ pThis->ExtendMerge( 0,nStartRow, nExtendCol,nTmpRow, nTab ); // no Refresh, incl. Attrs
+
+ OutputDevice* pDev = pThis->GetPrinter();
+ pDev->SetMapMode(MapMode(MapUnit::MapPixel)); // Important for GetNeededSize
+ ExtendPrintArea( pDev, nTab, 0, nStartRow, nExtendCol, nEndRow );
+ if ( nExtendCol >= nStartCol )
+ return false;
+ }
+
+ return true;
+}
+
+void ScDocument::Clear( bool bFromDestructor )
+{
+ for (auto& rxTab : maTabs)
+ if (rxTab)
+ rxTab->GetCondFormList()->clear();
+
+ maTabs.clear();
+ pSelectionAttr.reset();
+
+ if (mpDrawLayer)
+ {
+ mpDrawLayer->ClearModel( bFromDestructor );
+ }
+}
+
+bool ScDocument::HasDetectiveObjects(SCTAB nTab) const
+{
+ // looks for detective objects, annotations don't count
+ // (used to adjust scale so detective objects hit their cells better)
+
+ bool bFound = false;
+
+ if (mpDrawLayer)
+ {
+ SdrPage* pPage = mpDrawLayer->GetPage(static_cast<sal_uInt16>(nTab));
+ OSL_ENSURE(pPage,"Page ?");
+ if (pPage)
+ {
+ SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups );
+ SdrObject* pObject = aIter.Next();
+ while (pObject && !bFound)
+ {
+ // anything on the internal layer except captions (annotations)
+ if ( (pObject->GetLayer() == SC_LAYER_INTERN) && !ScDrawLayer::IsNoteCaption( pObject ) )
+ bFound = true;
+
+ pObject = aIter.Next();
+ }
+ }
+ }
+
+ return bFound;
+}
+
+void ScDocument::UpdateFontCharSet()
+{
+ // In old versions (until 4.0 without SP), when switching between systems,
+ // the Font attribute was not adjusted.
+ // This has to be redone for Documents until SP2:
+ // Everything that is not SYMBOL is set to system CharSet.
+ // CharSet should be correct for new documents (version SC_FONTCHARSET)
+
+ bool bUpdateOld = ( nSrcVer < SC_FONTCHARSET );
+
+ rtl_TextEncoding eSysSet = osl_getThreadTextEncoding();
+ if ( !(eSrcSet != eSysSet || bUpdateOld) )
+ return;
+
+ ScDocumentPool* pPool = mxPoolHelper->GetDocPool();
+ for (const SfxPoolItem* pItem : pPool->GetItemSurrogates(ATTR_FONT))
+ {
+ auto pFontItem = const_cast<SvxFontItem*>(dynamic_cast<const SvxFontItem*>(pItem));
+ if ( pFontItem && ( pFontItem->GetCharSet() == eSrcSet ||
+ ( bUpdateOld && pFontItem->GetCharSet() != RTL_TEXTENCODING_SYMBOL ) ) )
+ pFontItem->SetCharSet(eSysSet);
+ }
+
+ if ( mpDrawLayer )
+ {
+ SfxItemPool& rDrawPool = mpDrawLayer->GetItemPool();
+ for (const SfxPoolItem* pItem : rDrawPool.GetItemSurrogates(EE_CHAR_FONTINFO))
+ {
+ SvxFontItem* pFontItem = const_cast<SvxFontItem*>(dynamic_cast<const SvxFontItem*>(pItem));
+ if ( pFontItem && ( pFontItem->GetCharSet() == eSrcSet ||
+ ( bUpdateOld && pFontItem->GetCharSet() != RTL_TEXTENCODING_SYMBOL ) ) )
+ pFontItem->SetCharSet( eSysSet );
+ }
+ }
+}
+
+void ScDocument::SetLoadingMedium( bool bVal )
+{
+ bLoadingMedium = bVal;
+ for (auto& rxTab : maTabs)
+ {
+ if (!rxTab)
+ return;
+
+ rxTab->SetLoadingMedium(bVal);
+ }
+}
+
+void ScDocument::SetImportingXML( bool bVal )
+{
+ bImportingXML = bVal;
+ if (mpDrawLayer)
+ mpDrawLayer->EnableAdjust(!bImportingXML);
+
+ if ( !bVal )
+ {
+ // #i57869# after loading, do the real RTL mirroring for the sheets that have the LoadingRTL flag set
+
+ for ( SCTAB nTab=0; nTab< static_cast<SCTAB>(maTabs.size()) && maTabs[nTab]; nTab++ )
+ if ( maTabs[nTab]->IsLoadingRTL() )
+ {
+ // SetLayoutRTL => SetDrawPageSize => ScDrawLayer::SetPageSize, includes RTL-mirroring;
+ // bImportingXML must be cleared first
+ maTabs[nTab]->SetLoadingRTL( false );
+ SetLayoutRTL( nTab, true, ScObjectHandling::MoveRTLMode );
+ }
+ }
+
+ SetLoadingMedium(bVal);
+}
+
+const std::shared_ptr<SvxForbiddenCharactersTable>& ScDocument::GetForbiddenCharacters() const
+{
+ return xForbiddenCharacters;
+}
+
+void ScDocument::SetForbiddenCharacters(const std::shared_ptr<SvxForbiddenCharactersTable>& rNew)
+{
+ xForbiddenCharacters = rNew;
+ if ( mpEditEngine )
+ EditEngine::SetForbiddenCharsTable( xForbiddenCharacters );
+ if ( mpDrawLayer )
+ mpDrawLayer->SetForbiddenCharsTable( xForbiddenCharacters );
+}
+
+bool ScDocument::IsValidAsianCompression() const
+{
+ return nAsianCompression != CharCompressType::Invalid;
+}
+
+CharCompressType ScDocument::GetAsianCompression() const
+{
+ if ( nAsianCompression == CharCompressType::Invalid )
+ return CharCompressType::NONE;
+ else
+ return nAsianCompression;
+}
+
+void ScDocument::SetAsianCompression(CharCompressType nNew)
+{
+ nAsianCompression = nNew;
+ if ( mpEditEngine )
+ mpEditEngine->SetAsianCompressionMode( nAsianCompression );
+ if ( mpDrawLayer )
+ mpDrawLayer->SetCharCompressType( nAsianCompression );
+}
+
+bool ScDocument::IsValidAsianKerning() const
+{
+ return ( nAsianKerning != SC_ASIANKERNING_INVALID );
+}
+
+bool ScDocument::GetAsianKerning() const
+{
+ if ( nAsianKerning == SC_ASIANKERNING_INVALID )
+ return false;
+ else
+ return static_cast<bool>(nAsianKerning);
+}
+
+void ScDocument::SetAsianKerning(bool bNew)
+{
+ nAsianKerning = static_cast<sal_uInt8>(bNew);
+ if ( mpEditEngine )
+ mpEditEngine->SetKernAsianPunctuation( static_cast<bool>( nAsianKerning ) );
+ if ( mpDrawLayer )
+ mpDrawLayer->SetKernAsianPunctuation( static_cast<bool>( nAsianKerning ) );
+}
+
+void ScDocument::ApplyAsianEditSettings( ScEditEngineDefaulter& rEngine )
+{
+ EditEngine::SetForbiddenCharsTable( xForbiddenCharacters );
+ rEngine.SetAsianCompressionMode( GetAsianCompression() );
+ rEngine.SetKernAsianPunctuation( GetAsianKerning() );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/document.cxx b/sc/source/core/data/document.cxx
new file mode 100644
index 000000000..9753a16e8
--- /dev/null
+++ b/sc/source/core/data/document.cxx
@@ -0,0 +1,7089 @@
+/* -*- 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 <editeng/boxitem.hxx>
+#include <editeng/editobj.hxx>
+#include <o3tl/safeint.hxx>
+#include <svx/sdrundomanager.hxx>
+#include <svx/svditer.hxx>
+#include <sfx2/objsh.hxx>
+#include <sfx2/docfile.hxx>
+#include <svl/numformat.hxx>
+#include <svl/poolcach.hxx>
+#include <svl/zforlist.hxx>
+#include <unotools/charclass.hxx>
+#include <unotools/transliterationwrapper.hxx>
+#include <tools/urlobj.hxx>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <com/sun/star/text/WritingMode2.hpp>
+#include <com/sun/star/script/vba/XVBACompatibility.hpp>
+#include <com/sun/star/sheet/TablePageBreakData.hpp>
+#include <com/sun/star/lang/NotInitializedException.hpp>
+
+#include <document.hxx>
+#include <docuno.hxx>
+#include <table.hxx>
+#include <column.hxx>
+#include <attrib.hxx>
+#include <attarray.hxx>
+#include <patattr.hxx>
+#include <rangenam.hxx>
+#include <poolhelp.hxx>
+#include <docpool.hxx>
+#include <stlpool.hxx>
+#include <stlsheet.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <dbdata.hxx>
+#include <chartlis.hxx>
+#include <rangelst.hxx>
+#include <markdata.hxx>
+#include <drwlayer.hxx>
+#include <validat.hxx>
+#include <prnsave.hxx>
+#include <chgtrack.hxx>
+#include <hints.hxx>
+#include <detdata.hxx>
+#include <dpobject.hxx>
+#include <detfunc.hxx>
+#include <scmod.hxx>
+#include <dociter.hxx>
+#include <progress.hxx>
+#include <autonamecache.hxx>
+#include <bcaslot.hxx>
+#include <postit.hxx>
+#include <clipparam.hxx>
+#include <defaultsoptions.hxx>
+#include <editutil.hxx>
+#include <stringutil.hxx>
+#include <formulaiter.hxx>
+#include <formulacell.hxx>
+#include <clipcontext.hxx>
+#include <listenercontext.hxx>
+#include <scopetools.hxx>
+#include <refupdatecontext.hxx>
+#include <formulagroup.hxx>
+#include <tokenstringcontext.hxx>
+#include <compressedarray.hxx>
+#include <recursionhelper.hxx>
+#include <SparklineGroup.hxx>
+#include <SparklineList.hxx>
+
+#include <formula/vectortoken.hxx>
+
+#include <limits>
+#include <memory>
+#include <utility>
+
+#include <comphelper/lok.hxx>
+#include <comphelper/servicehelper.hxx>
+
+#include <vcl/uitest/logger.hxx>
+#include <vcl/uitest/eventdescription.hxx>
+
+#include <mtvelements.hxx>
+#include <sfx2/lokhelper.hxx>
+
+using ::editeng::SvxBorderLine;
+using namespace ::com::sun::star;
+
+namespace WritingMode2 = ::com::sun::star::text::WritingMode2;
+using ::com::sun::star::uno::Sequence;
+using ::com::sun::star::sheet::TablePageBreakData;
+using ::std::set;
+
+namespace {
+
+std::pair<SCTAB,SCTAB> getMarkedTableRange(const std::vector<ScTableUniquePtr>& rTables, const ScMarkData& rMark)
+{
+ SCTAB nTabStart = MAXTAB;
+ SCTAB nTabEnd = 0;
+ SCTAB nMax = static_cast<SCTAB>(rTables.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+
+ if (!rTables[rTab])
+ continue;
+
+ if (rTab < nTabStart)
+ nTabStart = rTab;
+ nTabEnd = rTab;
+ }
+
+ return std::pair<SCTAB,SCTAB>(nTabStart,nTabEnd);
+}
+
+void collectUIInformation(std::map<OUString, OUString>&& aParameters, const OUString& rAction)
+{
+ EventDescription aDescription;
+ aDescription.aID = "grid_window";
+ aDescription.aAction = rAction;
+ aDescription.aParameters = std::move(aParameters);
+ aDescription.aParent = "MainWindow";
+ aDescription.aKeyWord = "ScGridWinUIObject";
+
+ UITestLogger::getInstance().logEvent(aDescription);
+}
+
+struct ScDefaultAttr
+{
+ const ScPatternAttr* pAttr;
+ SCROW nFirst;
+ SCSIZE nCount;
+ explicit ScDefaultAttr(const ScPatternAttr* pPatAttr) : pAttr(pPatAttr), nFirst(0), nCount(0) {}
+};
+
+struct ScLessDefaultAttr
+{
+ bool operator() (const ScDefaultAttr& rValue1, const ScDefaultAttr& rValue2) const
+ {
+ return rValue1.pAttr < rValue2.pAttr;
+ }
+};
+
+}
+
+typedef std::set<ScDefaultAttr, ScLessDefaultAttr> ScDefaultAttrSet;
+
+void ScDocument::MakeTable( SCTAB nTab,bool _bNeedsNameCheck )
+{
+ if ( !(ValidTab(nTab) && ( nTab >= static_cast<SCTAB>(maTabs.size()) ||!maTabs[nTab])) )
+ return;
+
+ // Get Custom prefix
+ const ScDefaultsOptions& rOpt = SC_MOD()->GetDefaultsOptions();
+ OUString aString = rOpt.GetInitTabPrefix() + OUString::number(nTab+1);
+ if ( _bNeedsNameCheck )
+ CreateValidTabName( aString ); // no doubles
+ if (nTab < static_cast<SCTAB>(maTabs.size()))
+ {
+ maTabs[nTab].reset( new ScTable(*this, nTab, aString) );
+ }
+ else
+ {
+ while(nTab > static_cast<SCTAB>(maTabs.size()))
+ maTabs.push_back(nullptr);
+ maTabs.emplace_back( new ScTable(*this, nTab, aString) );
+ }
+ maTabs[nTab]->SetLoadingMedium(bLoadingMedium);
+}
+
+bool ScDocument::HasTable( SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ if (maTabs[nTab])
+ return true;
+
+ return false;
+}
+
+bool ScDocument::GetHashCode( SCTAB nTab, sal_Int64& rHashCode ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ {
+ if (maTabs[nTab])
+ {
+ rHashCode = maTabs[nTab]->GetHashCode();
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ScDocument::GetName( SCTAB nTab, OUString& rName ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ {
+ if (maTabs[nTab])
+ {
+ rName = maTabs[nTab]->GetName();
+ return true;
+ }
+ }
+ rName.clear();
+ return false;
+}
+
+OUString ScDocument::GetCopyTabName( SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabNames.size()))
+ return maTabNames[nTab];
+ return OUString();
+}
+
+bool ScDocument::SetCodeName( SCTAB nTab, const OUString& rName )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ {
+ if (maTabs[nTab])
+ {
+ maTabs[nTab]->SetCodeName( rName );
+ return true;
+ }
+ }
+ SAL_WARN("sc", "can't set code name " << rName );
+ return false;
+}
+
+bool ScDocument::GetCodeName( SCTAB nTab, OUString& rName ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ if (maTabs[nTab])
+ {
+ rName = maTabs[nTab]->GetCodeName();
+ return true;
+ }
+ rName.clear();
+ return false;
+}
+
+bool ScDocument::GetTable( const OUString& rName, SCTAB& rTab ) const
+{
+ static OUString aCacheName, aCacheUpperName;
+
+ assert(!IsThreadedGroupCalcInProgress());
+ if (aCacheName != rName)
+ {
+ aCacheName = rName;
+ // surprisingly slow ...
+ aCacheUpperName = ScGlobal::getCharClass().uppercase(rName);
+ }
+ const OUString aUpperName = aCacheUpperName;
+
+ for (SCTAB i=0; i< static_cast<SCTAB>(maTabs.size()); i++)
+ if (maTabs[i])
+ {
+ if (aUpperName == maTabs[i]->GetUpperName())
+ {
+ rTab = i;
+ return true;
+ }
+ }
+ rTab = 0;
+ return false;
+}
+
+std::vector<OUString> ScDocument::GetAllTableNames() const
+{
+ std::vector<OUString> aNames;
+ aNames.reserve(maTabs.size());
+ for (const auto& a : maTabs)
+ {
+ // Positions need to be preserved for ScCompiler and address convention
+ // context, so still push an empty string for NULL tabs.
+ OUString aName;
+ if (a)
+ {
+ const ScTable& rTab = *a;
+ aName = rTab.GetName();
+ }
+ aNames.push_back(aName);
+ }
+
+ return aNames;
+}
+
+ScDBData* ScDocument::GetAnonymousDBData(SCTAB nTab)
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetAnonymousDBData();
+ return nullptr;
+}
+
+SCTAB ScDocument::GetTableCount() const
+{
+ return static_cast<SCTAB>(maTabs.size());
+}
+
+void ScDocument::SetAnonymousDBData(SCTAB nTab, std::unique_ptr<ScDBData> pDBData)
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->SetAnonymousDBData(std::move(pDBData));
+}
+
+void ScDocument::SetAnonymousDBData( std::unique_ptr<ScDBData> pDBData )
+{
+ mpAnonymousDBData = std::move(pDBData);
+}
+
+ScDBData* ScDocument::GetAnonymousDBData()
+{
+ return mpAnonymousDBData.get();
+}
+
+bool ScDocument::ValidTabName( const OUString& rName )
+{
+ if (rName.isEmpty())
+ return false;
+ sal_Int32 nLen = rName.getLength();
+
+#if 1
+ // Restrict sheet names to what Excel accepts.
+ /* TODO: We may want to remove this restriction for full ODFF compliance.
+ * Merely loading and calculating ODF documents using these characters in
+ * sheet names is not affected by this, but all sheet name editing and
+ * copying functionality is, maybe falling back to "Sheet4" or similar. */
+ for (sal_Int32 i = 0; i < nLen; ++i)
+ {
+ const sal_Unicode c = rName[i];
+ switch (c)
+ {
+ case ':':
+ case '\\':
+ case '/':
+ case '?':
+ case '*':
+ case '[':
+ case ']':
+ // these characters are not allowed to match XL's convention.
+ return false;
+ case '\'':
+ if (i == 0 || i == nLen - 1)
+ // single quote is not allowed at the first or last
+ // character position.
+ return false;
+ break;
+ }
+ }
+#endif
+
+ return true;
+}
+
+bool ScDocument::ValidNewTabName( const OUString& rName ) const
+{
+ bool bValid = ValidTabName(rName);
+ if (!bValid)
+ return false;
+ OUString aUpperName = ScGlobal::getCharClass().uppercase(rName);
+ for (const auto& a : maTabs)
+ {
+ if (!a)
+ continue;
+ const OUString& rOldName = a->GetUpperName();
+ bValid = rOldName != aUpperName;
+ if (!bValid)
+ break;
+ }
+ return bValid;
+}
+
+void ScDocument::CreateValidTabName(OUString& rName) const
+{
+ if ( !ValidTabName(rName) )
+ {
+ // Find new one
+
+ // Get Custom prefix
+ const ScDefaultsOptions& rOpt = SC_MOD()->GetDefaultsOptions();
+ const OUString& aStrTable = rOpt.GetInitTabPrefix();
+
+ bool bOk = false;
+
+ // First test if the prefix is valid, if so only avoid doubles
+ bool bPrefix = ValidTabName( aStrTable );
+ OSL_ENSURE(bPrefix, "Invalid Table Name");
+ SCTAB nDummy;
+
+ for ( SCTAB i = static_cast<SCTAB>(maTabs.size())+1; !bOk ; i++ )
+ {
+ rName = aStrTable + OUString::number(static_cast<sal_Int32>(i));
+ if (bPrefix)
+ bOk = ValidNewTabName( rName );
+ else
+ bOk = !GetTable( rName, nDummy );
+ }
+ }
+ else
+ {
+ // testing the supplied Name
+
+ if ( !ValidNewTabName(rName) )
+ {
+ SCTAB i = 1;
+ OUStringBuffer aName;
+ do
+ {
+ i++;
+ aName = rName;
+ aName.append('_');
+ aName.append(static_cast<sal_Int32>(i));
+ }
+ while (!ValidNewTabName(aName.toString()) && (i < MAXTAB+1));
+ rName = aName.makeStringAndClear();
+ }
+ }
+}
+
+void ScDocument::CreateValidTabNames(std::vector<OUString>& aNames, SCTAB nCount) const
+{
+ aNames.clear();//ensure that the vector is empty
+
+ // Get Custom prefix
+ const ScDefaultsOptions& rOpt = SC_MOD()->GetDefaultsOptions();
+ const OUString& aStrTable = rOpt.GetInitTabPrefix();
+
+ OUStringBuffer rName;
+
+ // First test if the prefix is valid, if so only avoid doubles
+ bool bPrefix = ValidTabName( aStrTable );
+ OSL_ENSURE(bPrefix, "Invalid Table Name");
+ SCTAB nDummy;
+ SCTAB i = static_cast<SCTAB>(maTabs.size())+1;
+
+ for (SCTAB j = 0; j < nCount; ++j)
+ {
+ bool bOk = false;
+ while(!bOk)
+ {
+ rName = aStrTable;
+ rName.append(static_cast<sal_Int32>(i));
+ if (bPrefix)
+ bOk = ValidNewTabName( rName.toString() );
+ else
+ bOk = !GetTable( rName.toString(), nDummy );
+ i++;
+ }
+ aNames.push_back(rName.makeStringAndClear());
+ }
+}
+
+void ScDocument::AppendTabOnLoad(const OUString& rName)
+{
+ SCTAB nTabCount = static_cast<SCTAB>(maTabs.size());
+ if (!ValidTab(nTabCount))
+ // max table count reached. No more tables.
+ return;
+
+ OUString aName = rName;
+ CreateValidTabName(aName);
+ maTabs.emplace_back( new ScTable(*this, nTabCount, aName) );
+}
+
+void ScDocument::SetTabNameOnLoad(SCTAB nTab, const OUString& rName)
+{
+ if (!ValidTab(nTab) || static_cast<SCTAB>(maTabs.size()) <= nTab)
+ return;
+
+ if (!ValidTabName(rName))
+ return;
+
+ maTabs[nTab]->SetName(rName);
+}
+
+void ScDocument::InvalidateStreamOnSave()
+{
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->SetStreamValid(false);
+ }
+}
+
+bool ScDocument::InsertTab(
+ SCTAB nPos, const OUString& rName, bool bExternalDocument, bool bUndoDeleteTab )
+{
+ SCTAB nTabCount = static_cast<SCTAB>(maTabs.size());
+ bool bValid = ValidTab(nTabCount);
+ if ( !bExternalDocument ) // else test rName == "'Doc'!Tab" first
+ bValid = (bValid && ValidNewTabName(rName));
+ if (bValid)
+ {
+ if (nPos == SC_TAB_APPEND || nPos >= nTabCount)
+ {
+ nPos = maTabs.size();
+ maTabs.emplace_back( new ScTable(*this, nTabCount, rName) );
+ if ( bExternalDocument )
+ maTabs[nTabCount]->SetVisible( false );
+ }
+ else
+ {
+ if (ValidTab(nPos) && (nPos < nTabCount))
+ {
+ sc::RefUpdateInsertTabContext aCxt( *this, nPos, 1);
+
+ ScRange aRange( 0,0,nPos, MaxCol(),MaxRow(),MAXTAB );
+ xColNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,1 );
+ xRowNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,1 );
+ if (pRangeName)
+ pRangeName->UpdateInsertTab(aCxt);
+ pDBCollection->UpdateReference(
+ URM_INSDEL, 0,0,nPos, MaxCol(),MaxRow(),MAXTAB, 0,0,1 );
+ if (pDPCollection)
+ pDPCollection->UpdateReference( URM_INSDEL, aRange, 0,0,1 );
+ if (pDetOpList)
+ pDetOpList->UpdateReference( this, URM_INSDEL, aRange, 0,0,1 );
+ UpdateChartRef( URM_INSDEL, 0,0,nPos, MaxCol(),MaxRow(),MAXTAB, 0,0,1 );
+ UpdateRefAreaLinks( URM_INSDEL, aRange, 0,0,1 );
+ if ( pUnoBroadcaster )
+ pUnoBroadcaster->Broadcast( ScUpdateRefHint( URM_INSDEL, aRange, 0,0,1 ) );
+
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->UpdateInsertTab(aCxt);
+ }
+ maTabs.emplace(maTabs.begin() + nPos, new ScTable(*this, nPos, rName));
+
+ // UpdateBroadcastAreas must be called between UpdateInsertTab,
+ // which ends listening, and StartAllListeners, to not modify
+ // areas that are to be inserted by starting listeners.
+ UpdateBroadcastAreas( URM_INSDEL, aRange, 0,0,1);
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->UpdateCompile();
+ }
+
+ StartAllListeners();
+
+ if (pValidationList)
+ {
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ pValidationList->UpdateInsertTab(aCxt);
+ }
+
+ bValid = true;
+ }
+ else
+ bValid = false;
+ }
+ }
+
+ if (bValid)
+ {
+ sc::SetFormulaDirtyContext aCxt;
+ aCxt.mbClearTabDeletedFlag = bUndoDeleteTab;
+ aCxt.mnTabDeletedStart = nPos;
+ aCxt.mnTabDeletedEnd = nPos;
+ SetAllFormulasDirty(aCxt);
+
+ if (comphelper::LibreOfficeKit::isActive() && GetDrawLayer())
+ {
+ ScModelObj* pModel = comphelper::getFromUnoTunnel<ScModelObj>(this->GetDocumentShell()->GetModel());
+ SfxLokHelper::notifyDocumentSizeChangedAllViews(pModel);
+ }
+ }
+
+ return bValid;
+}
+
+bool ScDocument::InsertTabs( SCTAB nPos, const std::vector<OUString>& rNames,
+ bool bNamesValid )
+{
+ SCTAB nNewSheets = static_cast<SCTAB>(rNames.size());
+ SCTAB nTabCount = static_cast<SCTAB>(maTabs.size());
+ bool bValid = bNamesValid || ValidTab(nTabCount+nNewSheets);
+
+ if (bValid)
+ {
+ if (nPos == SC_TAB_APPEND || nPos >= nTabCount)
+ {
+ for ( SCTAB i = 0; i < nNewSheets; ++i )
+ {
+ maTabs.emplace_back( new ScTable(*this, nTabCount + i, rNames.at(i)) );
+ }
+ }
+ else
+ {
+ if (ValidTab(nPos) && (nPos < nTabCount))
+ {
+ sc::RefUpdateInsertTabContext aCxt( *this, nPos, nNewSheets);
+ ScRange aRange( 0,0,nPos, MaxCol(),MaxRow(),MAXTAB );
+ xColNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,nNewSheets );
+ xRowNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,nNewSheets );
+ if (pRangeName)
+ pRangeName->UpdateInsertTab(aCxt);
+ pDBCollection->UpdateReference(
+ URM_INSDEL, 0,0,nPos, MaxCol(),MaxRow(),MAXTAB, 0,0,nNewSheets );
+ if (pDPCollection)
+ pDPCollection->UpdateReference( URM_INSDEL, aRange, 0,0,nNewSheets );
+ if (pDetOpList)
+ pDetOpList->UpdateReference( this, URM_INSDEL, aRange, 0,0,nNewSheets );
+ UpdateChartRef( URM_INSDEL, 0,0,nPos, MaxCol(),MaxRow(),MAXTAB, 0,0,nNewSheets );
+ UpdateRefAreaLinks( URM_INSDEL, aRange, 0,0, nNewSheets );
+ if ( pUnoBroadcaster )
+ pUnoBroadcaster->Broadcast( ScUpdateRefHint( URM_INSDEL, aRange, 0,0,nNewSheets ) );
+
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->UpdateInsertTab(aCxt);
+ }
+ for (SCTAB i = 0; i < nNewSheets; ++i)
+ {
+ maTabs.emplace(maTabs.begin() + nPos + i, new ScTable(*this, nPos + i, rNames.at(i)) );
+ }
+
+ // UpdateBroadcastAreas must be called between UpdateInsertTab,
+ // which ends listening, and StartAllListeners, to not modify
+ // areas that are to be inserted by starting listeners.
+ UpdateBroadcastAreas( URM_INSDEL, aRange, 0,0,nNewSheets);
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->UpdateCompile();
+ }
+
+ StartAllListeners();
+
+ if (pValidationList)
+ {
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ pValidationList->UpdateInsertTab(aCxt);
+ }
+
+ bValid = true;
+ }
+ else
+ bValid = false;
+ }
+ }
+
+ if (bValid)
+ {
+ sc::SetFormulaDirtyContext aCxt;
+ SetAllFormulasDirty(aCxt);
+ }
+
+ return bValid;
+}
+
+bool ScDocument::DeleteTab( SCTAB nTab )
+{
+ bool bValid = false;
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ {
+ if (maTabs[nTab])
+ {
+ SCTAB nTabCount = static_cast<SCTAB>(maTabs.size());
+ if (nTabCount > 1)
+ {
+ sc::AutoCalcSwitch aACSwitch(*this, false);
+ sc::RefUpdateDeleteTabContext aCxt( *this, nTab, 1);
+ sc::DelayDeletingBroadcasters delayDeletingBroadcasters(*this);
+
+ ScRange aRange( 0, 0, nTab, MaxCol(), MaxRow(), nTab );
+ DelBroadcastAreasInRange( aRange );
+
+ // #i8180# remove database ranges etc. that are on the deleted tab
+ // (restored in undo with ScRefUndoData)
+
+ xColNameRanges->DeleteOnTab( nTab );
+ xRowNameRanges->DeleteOnTab( nTab );
+ pDBCollection->DeleteOnTab( nTab );
+ if (pDPCollection)
+ pDPCollection->DeleteOnTab( nTab );
+ if (pDetOpList)
+ pDetOpList->DeleteOnTab( nTab );
+ DeleteAreaLinksOnTab( nTab );
+
+ // normal reference update
+
+ aRange.aEnd.SetTab( static_cast<SCTAB>(maTabs.size())-1 );
+ xColNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,-1 );
+ xRowNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,-1 );
+ if (pRangeName)
+ pRangeName->UpdateDeleteTab(aCxt);
+ pDBCollection->UpdateReference(
+ URM_INSDEL, 0,0,nTab, MaxCol(),MaxRow(),MAXTAB, 0,0,-1 );
+ if (pDPCollection)
+ pDPCollection->UpdateReference( URM_INSDEL, aRange, 0,0,-1 );
+ if (pDetOpList)
+ pDetOpList->UpdateReference( this, URM_INSDEL, aRange, 0,0,-1 );
+ UpdateChartRef( URM_INSDEL, 0,0,nTab, MaxCol(),MaxRow(),MAXTAB, 0,0,-1 );
+ UpdateRefAreaLinks( URM_INSDEL, aRange, 0,0,-1 );
+ if (pValidationList)
+ {
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ pValidationList->UpdateDeleteTab(aCxt);
+ }
+ if ( pUnoBroadcaster )
+ pUnoBroadcaster->Broadcast( ScUpdateRefHint( URM_INSDEL, aRange, 0,0,-1 ) );
+
+ for (auto & pTab : maTabs)
+ if (pTab)
+ pTab->UpdateDeleteTab(aCxt);
+
+ // tdf#149502 make sure ScTable destructor called after the erase is finished, when
+ // maTabs[x].nTab==x is true again, as it should be always true.
+ // In the end of maTabs.erase, maTabs indexes change, but nTab updated before erase.
+ // ~ScTable expect that maTabs[x].nTab==x so it shouldn't be called during erase.
+ ScTableUniquePtr pErasedTab = std::move(maTabs[nTab]);
+ maTabs.erase(maTabs.begin() + nTab);
+ delete pErasedTab.release();
+
+ // UpdateBroadcastAreas must be called between UpdateDeleteTab,
+ // which ends listening, and StartAllListeners, to not modify
+ // areas that are to be inserted by starting listeners.
+ UpdateBroadcastAreas( URM_INSDEL, aRange, 0,0,-1);
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->UpdateCompile();
+ }
+ // Excel-Filter deletes some Tables while loading, Listeners will
+ // only be triggered after the loading is done.
+ if ( !bInsertingFromOtherDoc )
+ {
+ StartAllListeners();
+
+ sc::SetFormulaDirtyContext aFormulaDirtyCxt;
+ SetAllFormulasDirty(aFormulaDirtyCxt);
+ }
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ ScModelObj* pModel = comphelper::getFromUnoTunnel<ScModelObj>(this->GetDocumentShell()->GetModel());
+ SfxLokHelper::notifyDocumentSizeChangedAllViews(pModel);
+ }
+
+ bValid = true;
+ }
+ }
+ }
+ return bValid;
+}
+
+bool ScDocument::DeleteTabs( SCTAB nTab, SCTAB nSheets )
+{
+ bool bValid = false;
+ if (ValidTab(nTab) && (nTab + nSheets) <= static_cast<SCTAB>(maTabs.size()))
+ {
+ if (maTabs[nTab])
+ {
+ SCTAB nTabCount = static_cast<SCTAB>(maTabs.size());
+ if (nTabCount > nSheets)
+ {
+ sc::AutoCalcSwitch aACSwitch(*this, false);
+ sc::RefUpdateDeleteTabContext aCxt( *this, nTab, nSheets);
+ sc::DelayDeletingBroadcasters delayDeletingBroadcasters(*this);
+
+ for (SCTAB aTab = 0; aTab < nSheets; ++aTab)
+ {
+ ScRange aRange( 0, 0, nTab, MaxCol(), MaxRow(), nTab + aTab );
+ DelBroadcastAreasInRange( aRange );
+
+ // #i8180# remove database ranges etc. that are on the deleted tab
+ // (restored in undo with ScRefUndoData)
+
+ xColNameRanges->DeleteOnTab( nTab + aTab );
+ xRowNameRanges->DeleteOnTab( nTab + aTab );
+ pDBCollection->DeleteOnTab( nTab + aTab );
+ if (pDPCollection)
+ pDPCollection->DeleteOnTab( nTab + aTab );
+ if (pDetOpList)
+ pDetOpList->DeleteOnTab( nTab + aTab );
+ DeleteAreaLinksOnTab( nTab + aTab );
+ }
+
+ if (pRangeName)
+ pRangeName->UpdateDeleteTab(aCxt);
+
+ // normal reference update
+
+ ScRange aRange( 0, 0, nTab, MaxCol(), MaxRow(), nTabCount - 1 );
+ xColNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,-1*nSheets );
+ xRowNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,-1*nSheets );
+ pDBCollection->UpdateReference(
+ URM_INSDEL, 0,0,nTab, MaxCol(),MaxRow(),MAXTAB, 0,0,-1*nSheets );
+ if (pDPCollection)
+ pDPCollection->UpdateReference( URM_INSDEL, aRange, 0,0,-1*nSheets );
+ if (pDetOpList)
+ pDetOpList->UpdateReference( this, URM_INSDEL, aRange, 0,0,-1*nSheets );
+ UpdateChartRef( URM_INSDEL, 0,0,nTab, MaxCol(),MaxRow(),MAXTAB, 0,0,-1*nSheets );
+ UpdateRefAreaLinks( URM_INSDEL, aRange, 0,0,-1*nSheets );
+ if (pValidationList)
+ {
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ pValidationList->UpdateDeleteTab(aCxt);
+ }
+ if ( pUnoBroadcaster )
+ pUnoBroadcaster->Broadcast( ScUpdateRefHint( URM_INSDEL, aRange, 0,0,-1*nSheets ) );
+
+ for (auto & pTab : maTabs)
+ if (pTab)
+ pTab->UpdateDeleteTab(aCxt);
+
+ maTabs.erase(maTabs.begin() + nTab, maTabs.begin() + nTab + nSheets);
+ // UpdateBroadcastAreas must be called between UpdateDeleteTab,
+ // which ends listening, and StartAllListeners, to not modify
+ // areas that are to be inserted by starting listeners.
+ UpdateBroadcastAreas( URM_INSDEL, aRange, 0,0,-1*nSheets);
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->UpdateCompile();
+ }
+ // Excel-Filter deletes some Tables while loading, Listeners will
+ // only be triggered after the loading is done.
+ if ( !bInsertingFromOtherDoc )
+ {
+ StartAllListeners();
+
+ sc::SetFormulaDirtyContext aFormulaDirtyCxt;
+ SetAllFormulasDirty(aFormulaDirtyCxt);
+ }
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ ScModelObj* pModel = comphelper::getFromUnoTunnel<ScModelObj>(this->GetDocumentShell()->GetModel());
+ SfxLokHelper::notifyDocumentSizeChangedAllViews(pModel);
+ }
+
+ bValid = true;
+ }
+ }
+ }
+ return bValid;
+}
+
+bool ScDocument::RenameTab( SCTAB nTab, const OUString& rName, bool bExternalDocument )
+{
+ bool bValid = false;
+ SCTAB i;
+ if (ValidTab(nTab))
+ {
+ if (maTabs[nTab])
+ {
+ if ( bExternalDocument )
+ bValid = true; // composed name
+ else
+ bValid = ValidTabName(rName);
+ for (i=0; (i< static_cast<SCTAB>(maTabs.size())) && bValid; i++)
+ if (maTabs[i] && (i != nTab))
+ {
+ OUString aOldName = maTabs[i]->GetName();
+ bValid = !ScGlobal::GetTransliteration().isEqual( rName, aOldName );
+ }
+ if (bValid)
+ {
+ // #i75258# update charts before renaming, so they can get their live data objects.
+ // Once the charts are live, the sheet can be renamed without problems.
+ if ( pChartListenerCollection )
+ pChartListenerCollection->UpdateChartsContainingTab( nTab );
+ maTabs[nTab]->SetName(rName);
+
+ // If formulas refer to the renamed sheet, the TokenArray remains valid,
+ // but the XML stream must be re-generated.
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->SetStreamValid( false );
+ }
+
+ if (comphelper::LibreOfficeKit::isActive() && GetDrawLayer())
+ {
+ ScModelObj* pModel = comphelper::getFromUnoTunnel<ScModelObj>(this->GetDocumentShell()->GetModel());
+ SfxLokHelper::notifyDocumentSizeChangedAllViews(pModel);
+ }
+ }
+ }
+ }
+
+ collectUIInformation({{"NewName", rName}}, "Rename_Sheet");
+
+ return bValid;
+}
+
+void ScDocument::SetVisible( SCTAB nTab, bool bVisible )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB> (maTabs.size()))
+ if (maTabs[nTab])
+ maTabs[nTab]->SetVisible(bVisible);
+}
+
+bool ScDocument::IsVisible( SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB> (maTabs.size()))
+ if (maTabs[nTab])
+ return maTabs[nTab]->IsVisible();
+
+ return false;
+}
+
+bool ScDocument::IsStreamValid( SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB> (maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->IsStreamValid();
+
+ return false;
+}
+
+void ScDocument::SetStreamValid( SCTAB nTab, bool bSet, bool bIgnoreLock )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB> (maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->SetStreamValid( bSet, bIgnoreLock );
+}
+
+void ScDocument::LockStreamValid( bool bLock )
+{
+ mbStreamValidLocked = bLock;
+}
+
+bool ScDocument::IsPendingRowHeights( SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB> (maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->IsPendingRowHeights();
+
+ return false;
+}
+
+void ScDocument::SetPendingRowHeights( SCTAB nTab, bool bSet )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB> (maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->SetPendingRowHeights( bSet );
+}
+
+void ScDocument::SetLayoutRTL( SCTAB nTab, bool bRTL, ScObjectHandling eObjectHandling)
+{
+ if ( !(ValidTab(nTab) && nTab < static_cast<SCTAB> (maTabs.size()) && maTabs[nTab]) )
+ return;
+
+ if ( bImportingXML )
+ {
+ // #i57869# only set the LoadingRTL flag, the real setting (including mirroring)
+ // is applied in SetImportingXML(false). This is so the shapes can be loaded in
+ // normal LTR mode.
+
+ maTabs[nTab]->SetLoadingRTL( bRTL );
+ return;
+ }
+
+ maTabs[nTab]->SetLayoutRTL( bRTL ); // only sets the flag
+ maTabs[nTab]->SetDrawPageSize(true, true, eObjectHandling);
+
+ // objects are already repositioned via SetDrawPageSize, only writing mode is missing
+ if (!mpDrawLayer)
+ return;
+
+ SdrPage* pPage = mpDrawLayer->GetPage(static_cast<sal_uInt16>(nTab));
+ OSL_ENSURE(pPage,"Page ?");
+ if (!pPage)
+ return;
+
+ SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups );
+ SdrObject* pObject = aIter.Next();
+ while (pObject)
+ {
+ pObject->SetContextWritingMode( bRTL ? WritingMode2::RL_TB : WritingMode2::LR_TB );
+ pObject = aIter.Next();
+ }
+}
+
+bool ScDocument::IsLayoutRTL( SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB> (maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->IsLayoutRTL();
+
+ return false;
+}
+
+bool ScDocument::IsNegativePage( SCTAB nTab ) const
+{
+ // Negative page area is always used for RTL layout.
+ // The separate method is used to find all RTL handling of drawing objects.
+ return IsLayoutRTL( nTab );
+}
+
+/* ----------------------------------------------------------------------------
+ used search area:
+
+ GetCellArea - Only Data
+ GetTableArea - Data / Attributes
+ GetPrintArea - intended for character objects,
+ sweeps attributes all the way to bottom / right
+---------------------------------------------------------------------------- */
+
+bool ScDocument::GetCellArea( SCTAB nTab, SCCOL& rEndCol, SCROW& rEndRow ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB> (maTabs.size()))
+ if (maTabs[nTab])
+ return maTabs[nTab]->GetCellArea( rEndCol, rEndRow );
+
+ rEndCol = 0;
+ rEndRow = 0;
+ return false;
+}
+
+bool ScDocument::GetTableArea( SCTAB nTab, SCCOL& rEndCol, SCROW& rEndRow, bool bCalcHiddens) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB> (maTabs.size()))
+ if (maTabs[nTab])
+ return maTabs[nTab]->GetTableArea( rEndCol, rEndRow, bCalcHiddens);
+
+ rEndCol = 0;
+ rEndRow = 0;
+ return false;
+}
+
+bool ScDocument::ShrinkToDataArea(SCTAB nTab, SCCOL& rStartCol, SCROW& rStartRow, SCCOL& rEndCol, SCROW& rEndRow) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB> (maTabs.size()) || !maTabs[nTab])
+ return false;
+
+ SCCOL nCol1, nCol2;
+ SCROW nRow1, nRow2;
+ maTabs[nTab]->GetFirstDataPos(nCol1, nRow1);
+ maTabs[nTab]->GetLastDataPos(nCol2, nRow2);
+
+ if (nCol1 > nCol2 || nRow1 > nRow2)
+ // invalid range.
+ return false;
+
+ // Make sure the area only shrinks, and doesn't grow.
+ if (rStartCol < nCol1)
+ rStartCol = nCol1;
+ if (nCol2 < rEndCol)
+ rEndCol = nCol2;
+ if (rStartRow < nRow1)
+ rStartRow = nRow1;
+ if (nRow2 < rEndRow)
+ rEndRow = nRow2;
+
+ if (rStartCol > rEndCol || rStartRow > rEndRow)
+ // invalid range.
+ return false;
+
+ return true; // success!
+}
+
+bool ScDocument::ShrinkToUsedDataArea( bool& o_bShrunk, SCTAB nTab, SCCOL& rStartCol,
+ SCROW& rStartRow, SCCOL& rEndCol, SCROW& rEndRow, bool bColumnsOnly,
+ bool bStickyTopRow, bool bStickyLeftCol, ScDataAreaExtras* pDataAreaExtras ) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB> (maTabs.size()) || !maTabs[nTab])
+ {
+ o_bShrunk = false;
+ return false;
+ }
+ return maTabs[nTab]->ShrinkToUsedDataArea(
+ o_bShrunk, rStartCol, rStartRow, rEndCol, rEndRow, bColumnsOnly, bStickyTopRow,
+ bStickyLeftCol, pDataAreaExtras);
+}
+
+SCROW ScDocument::GetLastDataRow( SCTAB nTab, SCCOL nCol1, SCCOL nCol2, SCROW nLastRow ) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return -1;
+
+ return pTab->GetLastDataRow(nCol1, nCol2, nLastRow);
+}
+
+// connected area
+
+void ScDocument::GetDataArea( SCTAB nTab, SCCOL& rStartCol, SCROW& rStartRow,
+ SCCOL& rEndCol, SCROW& rEndRow, bool bIncludeOld, bool bOnlyDown ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB> (maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->GetDataArea( rStartCol, rStartRow, rEndCol, rEndRow, bIncludeOld, bOnlyDown );
+}
+
+bool ScDocument::GetDataAreaSubrange(ScRange& rRange) const
+{
+ SCTAB nTab = rRange.aStart.Tab();
+ if (nTab != rRange.aEnd.Tab())
+ return true;
+
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB> (maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetDataAreaSubrange(rRange);
+
+ return true;
+}
+
+void ScDocument::LimitChartArea( SCTAB nTab, SCCOL& rStartCol, SCROW& rStartRow,
+ SCCOL& rEndCol, SCROW& rEndRow )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB> (maTabs.size()))
+ if (maTabs[nTab])
+ maTabs[nTab]->LimitChartArea( rStartCol, rStartRow, rEndCol, rEndRow );
+}
+
+void ScDocument::LimitChartIfAll( ScRangeListRef& rRangeList )
+{
+ ScRangeListRef aNew = new ScRangeList;
+ if (rRangeList.is())
+ {
+ for ( size_t i = 0, nCount = rRangeList->size(); i < nCount; i++ )
+ {
+ ScRange aRange( (*rRangeList)[i] );
+ if ( ( aRange.aStart.Col() == 0 && aRange.aEnd.Col() == MaxCol() ) ||
+ ( aRange.aStart.Row() == 0 && aRange.aEnd.Row() == MaxRow() ) )
+ {
+ SCCOL nStartCol = aRange.aStart.Col();
+ SCROW nStartRow = aRange.aStart.Row();
+ SCCOL nEndCol = aRange.aEnd.Col();
+ SCROW nEndRow = aRange.aEnd.Row();
+ SCTAB nTab = aRange.aStart.Tab();
+ if ( nTab < static_cast<SCTAB> (maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->LimitChartArea(nStartCol, nStartRow, nEndCol, nEndRow);
+ aRange.aStart.SetCol( nStartCol );
+ aRange.aStart.SetRow( nStartRow );
+ aRange.aEnd.SetCol( nEndCol );
+ aRange.aEnd.SetRow( nEndRow );
+ }
+ aNew->push_back(aRange);
+ }
+ }
+ else
+ {
+ OSL_FAIL("LimitChartIfAll: Ref==0");
+ }
+ rRangeList = aNew;
+}
+
+static void lcl_GetFirstTabRange( SCTAB& rTabRangeStart, SCTAB& rTabRangeEnd, const ScMarkData* pTabMark, SCTAB aMaxTab )
+{
+ // without ScMarkData, leave start/end unchanged
+ if ( !pTabMark )
+ return;
+
+ for (SCTAB nTab=0; nTab< aMaxTab; ++nTab)
+ if (pTabMark->GetTableSelect(nTab))
+ {
+ // find first range of consecutive selected sheets
+ rTabRangeStart = pTabMark->GetFirstSelected();
+ while ( nTab+1 < aMaxTab && pTabMark->GetTableSelect(nTab+1) )
+ ++nTab;
+ rTabRangeEnd = nTab;
+ return;
+ }
+}
+
+static bool lcl_GetNextTabRange( SCTAB& rTabRangeStart, SCTAB& rTabRangeEnd, const ScMarkData* pTabMark, SCTAB aMaxTab )
+{
+ if ( pTabMark )
+ {
+ // find next range of consecutive selected sheets after rTabRangeEnd
+ for (SCTAB nTab=rTabRangeEnd+1; nTab< aMaxTab; ++nTab)
+ if (pTabMark->GetTableSelect(nTab))
+ {
+ rTabRangeStart = nTab;
+ while ( nTab+1 < aMaxTab && pTabMark->GetTableSelect(nTab+1) )
+ ++nTab;
+ rTabRangeEnd = nTab;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ScDocument::CanInsertRow( const ScRange& rRange ) const
+{
+ SCCOL nStartCol = rRange.aStart.Col();
+ SCROW nStartRow = rRange.aStart.Row();
+ SCTAB nStartTab = rRange.aStart.Tab();
+ SCCOL nEndCol = rRange.aEnd.Col();
+ SCROW nEndRow = rRange.aEnd.Row();
+ SCTAB nEndTab = rRange.aEnd.Tab();
+ PutInOrder( nStartCol, nEndCol );
+ PutInOrder( nStartRow, nEndRow );
+ PutInOrder( nStartTab, nEndTab );
+ SCSIZE nSize = static_cast<SCSIZE>(nEndRow - nStartRow + 1);
+
+ bool bTest = true;
+ for (SCTAB i=nStartTab; i<=nEndTab && bTest && i < static_cast<SCTAB>(maTabs.size()); i++)
+ if (maTabs[i])
+ bTest &= maTabs[i]->TestInsertRow( nStartCol, nEndCol, nStartRow, nSize );
+
+ return bTest;
+}
+
+namespace {
+
+struct SetDirtyIfPostponedHandler
+{
+ void operator() (const ScTableUniquePtr & p)
+ {
+ if (p)
+ p->SetDirtyIfPostponed();
+ }
+};
+
+struct BroadcastRecalcOnRefMoveHandler
+{
+ void operator() (const ScTableUniquePtr & p)
+ {
+ if (p)
+ p->BroadcastRecalcOnRefMove();
+ }
+};
+
+struct BroadcastRecalcOnRefMoveGuard
+{
+ explicit BroadcastRecalcOnRefMoveGuard( ScDocument* pDoc ) :
+ aSwitch( *pDoc, false),
+ aBulk( pDoc->GetBASM(), SfxHintId::ScDataChanged)
+ {
+ }
+
+private:
+ sc::AutoCalcSwitch aSwitch; // first for ctor/dtor order, destroy second
+ ScBulkBroadcast aBulk; // second for ctor/dtor order, destroy first
+};
+
+}
+
+bool ScDocument::InsertRow( SCCOL nStartCol, SCTAB nStartTab,
+ SCCOL nEndCol, SCTAB nEndTab,
+ SCROW nStartRow, SCSIZE nSize, ScDocument* pRefUndoDoc,
+ const ScMarkData* pTabMark )
+{
+ SCTAB i;
+
+ PutInOrder( nStartCol, nEndCol );
+ PutInOrder( nStartTab, nEndTab );
+ if ( pTabMark )
+ {
+ nStartTab = 0;
+ nEndTab = static_cast<SCTAB>(maTabs.size()) -1;
+ }
+
+ bool bTest = true;
+ bool bRet = false;
+ bool bOldAutoCalc = GetAutoCalc();
+ SetAutoCalc( false ); // avoid multiple calculations
+ bool oldDelayedDeleteBroadcasters = IsDelayedDeletingBroadcasters();
+ EnableDelayDeletingBroadcasters( true );
+ for ( i = nStartTab; i <= nEndTab && bTest && i < static_cast<SCTAB>(maTabs.size()); i++)
+ if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
+ bTest &= maTabs[i]->TestInsertRow(nStartCol, nEndCol, nStartRow, nSize);
+ if (bTest)
+ {
+ // UpdateBroadcastAreas have to be called before UpdateReference, so that entries
+ // aren't shifted that would be rebuild at UpdateReference
+
+ // handle chunks of consecutive selected sheets together
+ SCTAB nTabRangeStart = nStartTab;
+ SCTAB nTabRangeEnd = nEndTab;
+ lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast<SCTAB>(maTabs.size()) );
+ ScRange aShiftedRange(nStartCol, nStartRow, nTabRangeStart, nEndCol, MaxRow(), nTabRangeEnd);
+ sc::EndListeningContext aEndListenCxt(*this);
+
+ std::vector<ScAddress> aGroupPos;
+ do
+ {
+ aShiftedRange.aStart.SetTab(nTabRangeStart);
+ aShiftedRange.aEnd.SetTab(nTabRangeEnd);
+
+ // Collect all formula groups that will get split by the shifting,
+ // and end all their listening. Record the position of the top
+ // cell of the topmost group, and the position of the bottom cell
+ // of the bottommost group.
+ EndListeningIntersectedGroups(aEndListenCxt, aShiftedRange, &aGroupPos);
+
+ UpdateBroadcastAreas(URM_INSDEL, aShiftedRange, 0, static_cast<SCROW>(nSize), 0);
+ }
+ while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast<SCTAB>(maTabs.size()) ) );
+
+ lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast<SCTAB>(maTabs.size()) );
+
+ sc::RefUpdateContext aCxt(*this);
+ aCxt.meMode = URM_INSDEL;
+ aCxt.maRange = aShiftedRange;
+ aCxt.mnRowDelta = nSize;
+ do
+ {
+ aCxt.maRange.aStart.SetTab(nTabRangeStart);
+ aCxt.maRange.aEnd.SetTab(nTabRangeEnd);
+ UpdateReference(aCxt, pRefUndoDoc, false); // without drawing objects
+ }
+ while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast<SCTAB>(maTabs.size()) ) );
+
+ // UpdateReference should have set "needs listening" flags to those
+ // whose references have been modified. We also need to set this flag
+ // to those that were in the groups that got split by shifting.
+ SetNeedsListeningGroups(aGroupPos);
+
+ for (i=nStartTab; i<=nEndTab && i < static_cast<SCTAB>(maTabs.size()); i++)
+ if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
+ maTabs[i]->InsertRow( nStartCol, nEndCol, nStartRow, nSize );
+
+ // UpdateRef for drawing layer must be after inserting,
+ // when the new row heights are known.
+ for (i=nStartTab; i<=nEndTab && i < static_cast<SCTAB>(maTabs.size()); i++)
+ if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
+ maTabs[i]->UpdateDrawRef( URM_INSDEL,
+ nStartCol, nStartRow, nStartTab, nEndCol, MaxRow(), nEndTab,
+ 0, static_cast<SCROW>(nSize), 0 );
+
+ if ( pChangeTrack && pChangeTrack->IsInDeleteUndo() )
+ { // A new Listening is needed when references to deleted ranges are restored,
+ // previous Listeners were removed in FormulaCell UpdateReference.
+ StartAllListeners();
+ }
+ else
+ { // Listeners have been removed in UpdateReference
+ StartNeededListeners();
+
+ // At least all cells using range names pointing relative to the
+ // moved range must be recalculated, and all cells marked postponed
+ // dirty.
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->SetDirtyIfPostponed();
+ }
+
+ {
+ BroadcastRecalcOnRefMoveGuard g(this);
+ std::for_each(maTabs.begin(), maTabs.end(), BroadcastRecalcOnRefMoveHandler());
+ }
+ }
+ bRet = true;
+ }
+ EnableDelayDeletingBroadcasters( oldDelayedDeleteBroadcasters );
+ SetAutoCalc( bOldAutoCalc );
+ if ( bRet && pChartListenerCollection )
+ pChartListenerCollection->UpdateDirtyCharts();
+ return bRet;
+}
+
+bool ScDocument::InsertRow( const ScRange& rRange )
+{
+ return InsertRow( rRange.aStart.Col(), rRange.aStart.Tab(),
+ rRange.aEnd.Col(), rRange.aEnd.Tab(),
+ rRange.aStart.Row(), static_cast<SCSIZE>(rRange.aEnd.Row()-rRange.aStart.Row()+1) );
+}
+
+void ScDocument::DeleteRow( SCCOL nStartCol, SCTAB nStartTab,
+ SCCOL nEndCol, SCTAB nEndTab,
+ SCROW nStartRow, SCSIZE nSize,
+ ScDocument* pRefUndoDoc, bool* pUndoOutline,
+ const ScMarkData* pTabMark )
+{
+ SCTAB i;
+
+ PutInOrder( nStartCol, nEndCol );
+ PutInOrder( nStartTab, nEndTab );
+ if ( pTabMark )
+ {
+ nStartTab = 0;
+ nEndTab = static_cast<SCTAB>(maTabs.size())-1;
+ }
+
+ sc::AutoCalcSwitch aACSwitch(*this, false); // avoid multiple calculations
+
+ // handle chunks of consecutive selected sheets together
+ SCTAB nTabRangeStart = nStartTab;
+ SCTAB nTabRangeEnd = nEndTab;
+ lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast<SCTAB>(maTabs.size()) );
+ do
+ {
+ if ( ValidRow(nStartRow+nSize) )
+ {
+ DelBroadcastAreasInRange( ScRange(
+ ScAddress( nStartCol, nStartRow, nTabRangeStart ),
+ ScAddress( nEndCol, nStartRow+nSize-1, nTabRangeEnd ) ) );
+ UpdateBroadcastAreas( URM_INSDEL, ScRange(
+ ScAddress( nStartCol, nStartRow+nSize, nTabRangeStart ),
+ ScAddress( nEndCol, MaxRow(), nTabRangeEnd )), 0, -static_cast<SCROW>(nSize), 0 );
+ }
+ else
+ DelBroadcastAreasInRange( ScRange(
+ ScAddress( nStartCol, nStartRow, nTabRangeStart ),
+ ScAddress( nEndCol, MaxRow(), nTabRangeEnd ) ) );
+ }
+ while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast<SCTAB>(maTabs.size()) ) );
+
+ sc::RefUpdateContext aCxt(*this);
+ const bool bLastRowIncluded = (static_cast<SCROW>(nStartRow + nSize) == GetMaxRowCount() && ValidRow(nStartRow));
+ if ( ValidRow(nStartRow+nSize) || bLastRowIncluded )
+ {
+ lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast<SCTAB>(maTabs.size()) );
+ aCxt.meMode = URM_INSDEL;
+ aCxt.mnRowDelta = -static_cast<SCROW>(nSize);
+ if (bLastRowIncluded)
+ {
+ // Last row is included, shift a virtually non-existent row in.
+ aCxt.maRange = ScRange( nStartCol, GetMaxRowCount(), nTabRangeStart, nEndCol, GetMaxRowCount(), nTabRangeEnd);
+ }
+ else
+ {
+ aCxt.maRange = ScRange( nStartCol, nStartRow+nSize, nTabRangeStart, nEndCol, MaxRow(), nTabRangeEnd);
+ }
+ do
+ {
+ UpdateReference(aCxt, pRefUndoDoc, true, false);
+ }
+ while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast<SCTAB>(maTabs.size()) ) );
+ }
+
+ if (pUndoOutline)
+ *pUndoOutline = false;
+
+ // Keep track of the positions of all formula groups that have been joined
+ // during row deletion.
+ std::vector<ScAddress> aGroupPos;
+
+ for ( i = nStartTab; i <= nEndTab && i < static_cast<SCTAB>(maTabs.size()); i++)
+ if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
+ maTabs[i]->DeleteRow(aCxt.maRegroupCols, nStartCol, nEndCol, nStartRow, nSize, pUndoOutline, &aGroupPos);
+
+ // Newly joined groups have some of their members still listening. We
+ // need to make sure none of them are listening.
+ EndListeningGroups(aGroupPos);
+
+ // Mark all joined groups for group listening.
+ SetNeedsListeningGroups(aGroupPos);
+
+ if ( ValidRow(nStartRow+nSize) || bLastRowIncluded )
+ {
+ // Listeners have been removed in UpdateReference
+ StartNeededListeners();
+
+ // At least all cells using range names pointing relative to the moved
+ // range must be recalculated, and all cells marked postponed dirty.
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->SetDirtyIfPostponed();
+ }
+
+ {
+ BroadcastRecalcOnRefMoveGuard g(this);
+ std::for_each(maTabs.begin(), maTabs.end(), BroadcastRecalcOnRefMoveHandler());
+ }
+ }
+
+ if (pChartListenerCollection)
+ pChartListenerCollection->UpdateDirtyCharts();
+}
+
+void ScDocument::DeleteRow( const ScRange& rRange )
+{
+ DeleteRow( rRange.aStart.Col(), rRange.aStart.Tab(),
+ rRange.aEnd.Col(), rRange.aEnd.Tab(),
+ rRange.aStart.Row(), static_cast<SCSIZE>(rRange.aEnd.Row()-rRange.aStart.Row()+1) );
+}
+
+bool ScDocument::CanInsertCol( const ScRange& rRange ) const
+{
+ SCCOL nStartCol = rRange.aStart.Col();
+ SCROW nStartRow = rRange.aStart.Row();
+ SCTAB nStartTab = rRange.aStart.Tab();
+ SCCOL nEndCol = rRange.aEnd.Col();
+ SCROW nEndRow = rRange.aEnd.Row();
+ SCTAB nEndTab = rRange.aEnd.Tab();
+ PutInOrder( nStartCol, nEndCol );
+ PutInOrder( nStartRow, nEndRow );
+ PutInOrder( nStartTab, nEndTab );
+ SCSIZE nSize = static_cast<SCSIZE>(nEndCol - nStartCol + 1);
+
+ bool bTest = true;
+ for (SCTAB i=nStartTab; i<=nEndTab && bTest && i < static_cast<SCTAB>(maTabs.size()); i++)
+ if (maTabs[i])
+ bTest &= maTabs[i]->TestInsertCol( nStartRow, nEndRow, nSize );
+
+ return bTest;
+}
+
+bool ScDocument::InsertCol( SCROW nStartRow, SCTAB nStartTab,
+ SCROW nEndRow, SCTAB nEndTab,
+ SCCOL nStartCol, SCSIZE nSize, ScDocument* pRefUndoDoc,
+ const ScMarkData* pTabMark )
+{
+ SCTAB i;
+
+ PutInOrder( nStartRow, nEndRow );
+ PutInOrder( nStartTab, nEndTab );
+ if ( pTabMark )
+ {
+ nStartTab = 0;
+ nEndTab = static_cast<SCTAB>(maTabs.size())-1;
+ }
+
+ bool bTest = true;
+ bool bRet = false;
+ bool bOldAutoCalc = GetAutoCalc();
+ SetAutoCalc( false ); // avoid multiple calculations
+ bool oldDelayedDeleteBroadcasters = IsDelayedDeletingBroadcasters();
+ EnableDelayDeletingBroadcasters( true );
+ for ( i = nStartTab; i <= nEndTab && bTest && i < static_cast<SCTAB>(maTabs.size()); i++)
+ if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
+ bTest &= maTabs[i]->TestInsertCol( nStartRow, nEndRow, nSize );
+ if (bTest)
+ {
+ // handle chunks of consecutive selected sheets together
+ SCTAB nTabRangeStart = nStartTab;
+ SCTAB nTabRangeEnd = nEndTab;
+ lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast<SCTAB>(maTabs.size()) );
+ do
+ {
+ UpdateBroadcastAreas( URM_INSDEL, ScRange(
+ ScAddress( nStartCol, nStartRow, nTabRangeStart ),
+ ScAddress( MaxCol(), nEndRow, nTabRangeEnd )), static_cast<SCCOL>(nSize), 0, 0 );
+ }
+ while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast<SCTAB>(maTabs.size()) ) );
+
+ lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast<SCTAB>(maTabs.size()) );
+
+ sc::RefUpdateContext aCxt(*this);
+ aCxt.meMode = URM_INSDEL;
+ aCxt.maRange = ScRange(nStartCol, nStartRow, nTabRangeStart, MaxCol(), nEndRow, nTabRangeEnd);
+ aCxt.mnColDelta = nSize;
+ do
+ {
+ UpdateReference(aCxt, pRefUndoDoc, true, false);
+ }
+ while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast<SCTAB>(maTabs.size()) ) );
+
+ for (i=nStartTab; i<=nEndTab && i < static_cast<SCTAB>(maTabs.size()); i++)
+ if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
+ maTabs[i]->InsertCol(aCxt.maRegroupCols, nStartCol, nStartRow, nEndRow, nSize);
+
+ if ( pChangeTrack && pChangeTrack->IsInDeleteUndo() )
+ { // A new Listening is needed when references to deleted ranges are restored,
+ // previous Listeners were removed in FormulaCell UpdateReference.
+ StartAllListeners();
+ }
+ else
+ {
+ // Listeners have been removed in UpdateReference
+ StartNeededListeners();
+ // At least all cells using range names pointing relative to the
+ // moved range must be recalculated, and all cells marked postponed
+ // dirty.
+ {
+ ScBulkBroadcast aBulkBroadcast(GetBASM(), SfxHintId::ScDataChanged);
+ std::for_each(maTabs.begin(), maTabs.end(), SetDirtyIfPostponedHandler());
+ }
+ // Cells containing functions such as CELL, COLUMN or ROW may have
+ // changed their values on relocation. Broadcast them.
+ {
+ BroadcastRecalcOnRefMoveGuard g(this);
+ std::for_each(maTabs.begin(), maTabs.end(), BroadcastRecalcOnRefMoveHandler());
+ }
+ }
+ bRet = true;
+ }
+ EnableDelayDeletingBroadcasters( oldDelayedDeleteBroadcasters );
+ SetAutoCalc( bOldAutoCalc );
+ if ( bRet && pChartListenerCollection )
+ pChartListenerCollection->UpdateDirtyCharts();
+ return bRet;
+}
+
+bool ScDocument::InsertCol( const ScRange& rRange )
+{
+ return InsertCol( rRange.aStart.Row(), rRange.aStart.Tab(),
+ rRange.aEnd.Row(), rRange.aEnd.Tab(),
+ rRange.aStart.Col(), static_cast<SCSIZE>(rRange.aEnd.Col()-rRange.aStart.Col()+1) );
+}
+
+void ScDocument::DeleteCol(SCROW nStartRow, SCTAB nStartTab, SCROW nEndRow, SCTAB nEndTab,
+ SCCOL nStartCol, SCSIZE nSize, ScDocument* pRefUndoDoc,
+ bool* pUndoOutline, const ScMarkData* pTabMark )
+{
+ SCTAB i;
+
+ PutInOrder( nStartRow, nEndRow );
+ PutInOrder( nStartTab, nEndTab );
+ if ( pTabMark )
+ {
+ nStartTab = 0;
+ nEndTab = static_cast<SCTAB>(maTabs.size())-1;
+ }
+
+ sc::AutoCalcSwitch aACSwitch(*this, false); // avoid multiple calculations
+ ScBulkBroadcast aBulkBroadcast(GetBASM(), SfxHintId::ScDataChanged);
+
+ // handle chunks of consecutive selected sheets together
+ SCTAB nTabRangeStart = nStartTab;
+ SCTAB nTabRangeEnd = nEndTab;
+ lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast<SCTAB>(maTabs.size()) );
+ do
+ {
+ if ( ValidCol(sal::static_int_cast<SCCOL>(nStartCol+nSize)) )
+ {
+ DelBroadcastAreasInRange( ScRange(
+ ScAddress( nStartCol, nStartRow, nTabRangeStart ),
+ ScAddress( sal::static_int_cast<SCCOL>(nStartCol+nSize-1), nEndRow, nTabRangeEnd ) ) );
+ UpdateBroadcastAreas( URM_INSDEL, ScRange(
+ ScAddress( sal::static_int_cast<SCCOL>(nStartCol+nSize), nStartRow, nTabRangeStart ),
+ ScAddress( MaxCol(), nEndRow, nTabRangeEnd )), -static_cast<SCCOL>(nSize), 0, 0 );
+ }
+ else
+ DelBroadcastAreasInRange( ScRange(
+ ScAddress( nStartCol, nStartRow, nTabRangeStart ),
+ ScAddress( MaxCol(), nEndRow, nTabRangeEnd ) ) );
+ }
+ while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast<SCTAB>(maTabs.size()) ) );
+
+ sc::RefUpdateContext aCxt(*this);
+ const bool bLastColIncluded = (static_cast<SCCOL>(nStartCol + nSize) == GetMaxColCount() && ValidCol(nStartCol));
+ if ( ValidCol(sal::static_int_cast<SCCOL>(nStartCol+nSize)) || bLastColIncluded )
+ {
+ lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast<SCTAB>(maTabs.size()) );
+ aCxt.meMode = URM_INSDEL;
+ aCxt.mnColDelta = -static_cast<SCCOL>(nSize);
+ if (bLastColIncluded)
+ {
+ // Last column is included, shift a virtually non-existent column in.
+ aCxt.maRange = ScRange( GetMaxColCount(), nStartRow, nTabRangeStart, GetMaxColCount(), nEndRow, nTabRangeEnd);
+ }
+ else
+ {
+ aCxt.maRange = ScRange( sal::static_int_cast<SCCOL>(nStartCol+nSize), nStartRow, nTabRangeStart,
+ MaxCol(), nEndRow, nTabRangeEnd);
+ }
+ do
+ {
+ UpdateReference(aCxt, pRefUndoDoc, true, false);
+ }
+ while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast<SCTAB>(maTabs.size()) ) );
+ }
+
+ if (pUndoOutline)
+ *pUndoOutline = false;
+
+ for (i = nStartTab; i <= nEndTab && i < static_cast<SCTAB>(maTabs.size()); ++i)
+ {
+ if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
+ maTabs[i]->DeleteCol(aCxt.maRegroupCols, nStartCol, nStartRow, nEndRow, nSize, pUndoOutline);
+ }
+
+ if ( ValidCol(sal::static_int_cast<SCCOL>(nStartCol+nSize)) || bLastColIncluded )
+ {
+ // Listeners have been removed in UpdateReference
+ StartNeededListeners();
+
+ // At least all cells using range names pointing relative to the moved
+ // range must be recalculated, and all cells marked postponed dirty.
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->SetDirtyIfPostponed();
+ }
+
+ {
+ BroadcastRecalcOnRefMoveGuard g(this);
+ std::for_each(maTabs.begin(), maTabs.end(), BroadcastRecalcOnRefMoveHandler());
+ }
+ }
+
+ if (pChartListenerCollection)
+ pChartListenerCollection->UpdateDirtyCharts();
+}
+
+void ScDocument::DeleteCol( const ScRange& rRange )
+{
+ DeleteCol( rRange.aStart.Row(), rRange.aStart.Tab(),
+ rRange.aEnd.Row(), rRange.aEnd.Tab(),
+ rRange.aStart.Col(), static_cast<SCSIZE>(rRange.aEnd.Col()-rRange.aStart.Col()+1) );
+}
+
+// for Area-Links: Insert/delete cells, when the range is changed.
+// (without Paint)
+
+static void lcl_GetInsDelRanges( const ScRange& rOld, const ScRange& rNew,
+ ScRange& rColRange, bool& rInsCol, bool& rDelCol,
+ ScRange& rRowRange, bool& rInsRow, bool& rDelRow )
+{
+ OSL_ENSURE( rOld.aStart == rNew.aStart, "FitBlock: Beginning is different" );
+
+ rInsCol = rDelCol = rInsRow = rDelRow = false;
+
+ SCCOL nStartX = rOld.aStart.Col();
+ SCROW nStartY = rOld.aStart.Row();
+ SCCOL nOldEndX = rOld.aEnd.Col();
+ SCROW nOldEndY = rOld.aEnd.Row();
+ SCCOL nNewEndX = rNew.aEnd.Col();
+ SCROW nNewEndY = rNew.aEnd.Row();
+ SCTAB nTab = rOld.aStart.Tab();
+
+ // if more rows, columns are inserted/deleted at the old height.
+ bool bGrowY = ( nNewEndY > nOldEndY );
+ SCROW nColEndY = bGrowY ? nOldEndY : nNewEndY;
+ SCCOL nRowEndX = bGrowY ? nNewEndX : nOldEndX;
+
+ // Columns
+
+ if ( nNewEndX > nOldEndX ) // Insert columns
+ {
+ rColRange = ScRange( nOldEndX+1, nStartY, nTab, nNewEndX, nColEndY, nTab );
+ rInsCol = true;
+ }
+ else if ( nNewEndX < nOldEndX ) // Delete columns
+ {
+ rColRange = ScRange( nNewEndX+1, nStartY, nTab, nOldEndX, nColEndY, nTab );
+ rDelCol = true;
+ }
+
+ // Rows
+
+ if ( nNewEndY > nOldEndY ) // Insert rows
+ {
+ rRowRange = ScRange( nStartX, nOldEndY+1, nTab, nRowEndX, nNewEndY, nTab );
+ rInsRow = true;
+ }
+ else if ( nNewEndY < nOldEndY ) // Delete rows
+ {
+ rRowRange = ScRange( nStartX, nNewEndY+1, nTab, nRowEndX, nOldEndY, nTab );
+ rDelRow = true;
+ }
+}
+
+bool ScDocument::HasPartOfMerged( const ScRange& rRange )
+{
+ bool bPart = false;
+ SCTAB nTab = rRange.aStart.Tab();
+
+ SCCOL nStartX = rRange.aStart.Col();
+ SCROW nStartY = rRange.aStart.Row();
+ SCCOL nEndX = rRange.aEnd.Col();
+ SCROW nEndY = rRange.aEnd.Row();
+
+ if (HasAttrib( nStartX, nStartY, nTab, nEndX, nEndY, nTab,
+ HasAttrFlags::Merged | HasAttrFlags::Overlapped ))
+ {
+ ExtendMerge( nStartX, nStartY, nEndX, nEndY, nTab );
+ ExtendOverlapped( nStartX, nStartY, nEndX, nEndY, nTab );
+
+ bPart = ( nStartX != rRange.aStart.Col() || nEndX != rRange.aEnd.Col() ||
+ nStartY != rRange.aStart.Row() || nEndY != rRange.aEnd.Row() );
+ }
+ return bPart;
+}
+
+formula::FormulaTokenRef ScDocument::ResolveStaticReference( const ScAddress& rPos )
+{
+ SCTAB nTab = rPos.Tab();
+ if (!TableExists(nTab))
+ return formula::FormulaTokenRef();
+
+ return maTabs[nTab]->ResolveStaticReference(rPos.Col(), rPos.Row());
+}
+
+formula::FormulaTokenRef ScDocument::ResolveStaticReference( const ScRange& rRange )
+{
+ SCTAB nTab = rRange.aStart.Tab();
+ if (nTab != rRange.aEnd.Tab() || !TableExists(nTab))
+ return formula::FormulaTokenRef();
+
+ return maTabs[nTab]->ResolveStaticReference(
+ rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row());
+}
+
+formula::VectorRefArray ScDocument::FetchVectorRefArray( const ScAddress& rPos, SCROW nLength )
+{
+ SCTAB nTab = rPos.Tab();
+ if (!TableExists(nTab))
+ return formula::VectorRefArray();
+
+ return maTabs[nTab]->FetchVectorRefArray(rPos.Col(), rPos.Row(), rPos.Row()+nLength-1);
+}
+
+#ifdef DBG_UTIL
+void ScDocument::AssertNoInterpretNeeded( const ScAddress& rPos, SCROW nLength )
+{
+ SCTAB nTab = rPos.Tab();
+ assert(TableExists(nTab));
+ return maTabs[nTab]->AssertNoInterpretNeeded(rPos.Col(), rPos.Row(), rPos.Row()+nLength-1);
+}
+#endif
+
+void ScDocument::UnlockAdjustHeight()
+{
+ assert(nAdjustHeightLock > 0);
+ if(nAdjustHeightLock > 0)
+ --nAdjustHeightLock;
+}
+
+bool ScDocument::HandleRefArrayForParallelism( const ScAddress& rPos, SCROW nLength, const ScFormulaCellGroupRef& mxGroup )
+{
+ SCTAB nTab = rPos.Tab();
+ if (!TableExists(nTab))
+ return false;
+
+ return maTabs[nTab]->HandleRefArrayForParallelism(rPos.Col(), rPos.Row(), rPos.Row()+nLength-1, mxGroup);
+}
+
+bool ScDocument::CanFitBlock( const ScRange& rOld, const ScRange& rNew )
+{
+ if ( rOld == rNew )
+ return true;
+
+ bool bOk = true;
+ bool bInsCol,bDelCol,bInsRow,bDelRow;
+ ScRange aColRange,aRowRange;
+ lcl_GetInsDelRanges( rOld, rNew, aColRange,bInsCol,bDelCol, aRowRange,bInsRow,bDelRow );
+
+ if ( bInsCol && !CanInsertCol( aColRange ) ) // Cells at the edge ?
+ bOk = false;
+ if ( bInsRow && !CanInsertRow( aRowRange ) ) // Cells at the edge ?
+ bOk = false;
+
+ if ( bInsCol || bDelCol )
+ {
+ aColRange.aEnd.SetCol(MaxCol());
+ if ( HasPartOfMerged(aColRange) )
+ bOk = false;
+ }
+ if ( bInsRow || bDelRow )
+ {
+ aRowRange.aEnd.SetRow(MaxRow());
+ if ( HasPartOfMerged(aRowRange) )
+ bOk = false;
+ }
+
+ return bOk;
+}
+
+void ScDocument::FitBlock( const ScRange& rOld, const ScRange& rNew, bool bClear )
+{
+ if (bClear)
+ DeleteAreaTab( rOld, InsertDeleteFlags::ALL );
+
+ bool bInsCol,bDelCol,bInsRow,bDelRow;
+ ScRange aColRange,aRowRange;
+ lcl_GetInsDelRanges( rOld, rNew, aColRange,bInsCol,bDelCol, aRowRange,bInsRow,bDelRow );
+
+ if ( bInsCol )
+ InsertCol( aColRange ); // First insert columns
+ if ( bInsRow )
+ InsertRow( aRowRange );
+
+ if ( bDelRow )
+ DeleteRow( aRowRange ); // First delete rows
+ if ( bDelCol )
+ DeleteCol( aColRange );
+
+ // Expand references to inserted rows
+
+ if ( bInsCol || bInsRow )
+ {
+ ScRange aGrowSource = rOld;
+ aGrowSource.aEnd.SetCol(std::min( rOld.aEnd.Col(), rNew.aEnd.Col() ));
+ aGrowSource.aEnd.SetRow(std::min( rOld.aEnd.Row(), rNew.aEnd.Row() ));
+ SCCOL nGrowX = bInsCol ? ( rNew.aEnd.Col() - rOld.aEnd.Col() ) : 0;
+ SCROW nGrowY = bInsRow ? ( rNew.aEnd.Row() - rOld.aEnd.Row() ) : 0;
+ UpdateGrow( aGrowSource, nGrowX, nGrowY );
+ }
+}
+
+void ScDocument::DeleteArea(
+ SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, const ScMarkData& rMark,
+ InsertDeleteFlags nDelFlag, bool bBroadcast, sc::ColumnSpanSet* pBroadcastSpans )
+{
+ sc::AutoCalcSwitch aACSwitch(*this, false);
+
+ PutInOrder( nCol1, nCol2 );
+ PutInOrder( nRow1, nRow2 );
+
+ std::vector<ScAddress> aGroupPos;
+ // Destroy and reconstruct listeners only if content is affected.
+ bool bDelContent = ((nDelFlag & ~InsertDeleteFlags::CONTENTS) != nDelFlag);
+ if (bDelContent)
+ {
+ // Record the positions of top and/or bottom formula groups that intersect
+ // the area borders.
+ sc::EndListeningContext aCxt(*this);
+ ScRange aRange(nCol1, nRow1, 0, nCol2, nRow2, 0);
+ for (SCTAB i = 0; i < static_cast<SCTAB>(maTabs.size()); i++)
+ {
+ if (rMark.GetTableSelect(i))
+ {
+ aRange.aStart.SetTab(i);
+ aRange.aEnd.SetTab(i);
+
+ EndListeningIntersectedGroups(aCxt, aRange, &aGroupPos);
+ }
+ }
+ aCxt.purgeEmptyBroadcasters();
+ }
+
+ for (SCTAB i = 0; i < static_cast<SCTAB>(maTabs.size()); i++)
+ if (maTabs[i])
+ if ( rMark.GetTableSelect(i) || bIsUndo )
+ maTabs[i]->DeleteArea(nCol1, nRow1, nCol2, nRow2, nDelFlag, bBroadcast, pBroadcastSpans);
+
+ if (!bDelContent)
+ return;
+
+ // Re-start listeners on those top bottom groups that have been split.
+ SetNeedsListeningGroups(aGroupPos);
+ StartNeededListeners();
+
+ // If formula groups were split their listeners were destroyed and may
+ // need to be notified now that they're restored, ScTable::DeleteArea()
+ // couldn't do that.
+ if (aGroupPos.empty())
+ return;
+
+ ScRange aRange(nCol1, nRow1, 0, nCol2, nRow2, 0);
+ for (SCTAB i = 0; i < static_cast<SCTAB>(maTabs.size()); i++)
+ {
+ if (rMark.GetTableSelect(i))
+ {
+ aRange.aStart.SetTab(i);
+ aRange.aEnd.SetTab(i);
+ SetDirty( aRange, true);
+ }
+ }
+}
+
+void ScDocument::DeleteAreaTab(SCCOL nCol1, SCROW nRow1,
+ SCCOL nCol2, SCROW nRow2,
+ SCTAB nTab, InsertDeleteFlags nDelFlag)
+{
+ PutInOrder( nCol1, nCol2 );
+ PutInOrder( nRow1, nRow2 );
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ {
+ bool bOldAutoCalc = GetAutoCalc();
+ SetAutoCalc( false ); // avoid multiple calculations
+ maTabs[nTab]->DeleteArea(nCol1, nRow1, nCol2, nRow2, nDelFlag);
+ SetAutoCalc( bOldAutoCalc );
+ }
+}
+
+void ScDocument::DeleteAreaTab( const ScRange& rRange, InsertDeleteFlags nDelFlag )
+{
+ for ( SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); nTab++ )
+ DeleteAreaTab( rRange.aStart.Col(), rRange.aStart.Row(),
+ rRange.aEnd.Col(), rRange.aEnd.Row(),
+ nTab, nDelFlag );
+}
+
+void ScDocument::InitUndoSelected(const ScDocument& rSrcDoc, const ScMarkData& rTabSelection,
+ bool bColInfo, bool bRowInfo )
+{
+ if (bIsUndo)
+ {
+ Clear();
+
+ SharePooledResources(&rSrcDoc);
+
+ for (SCTAB nTab = 0; nTab <= rTabSelection.GetLastSelected(); nTab++)
+ if ( rTabSelection.GetTableSelect( nTab ) )
+ {
+ ScTableUniquePtr pTable(new ScTable(*this, nTab, OUString(), bColInfo, bRowInfo));
+ if (nTab < static_cast<SCTAB>(maTabs.size()))
+ maTabs[nTab] = std::move(pTable);
+ else
+ maTabs.push_back(std::move(pTable));
+ }
+ else
+ {
+ if (nTab < static_cast<SCTAB>(maTabs.size()))
+ maTabs[nTab]=nullptr;
+ else
+ maTabs.push_back(nullptr);
+ }
+ }
+ else
+ {
+ OSL_FAIL("InitUndo");
+ }
+}
+
+void ScDocument::InitUndo( const ScDocument& rSrcDoc, SCTAB nTab1, SCTAB nTab2,
+ bool bColInfo, bool bRowInfo )
+{
+ if (!bIsUndo)
+ {
+ OSL_FAIL("InitUndo");
+ return;
+ }
+
+ Clear();
+
+ // Undo document shares its pooled resources with the source document.
+ SharePooledResources(&rSrcDoc);
+
+ if (rSrcDoc.mpShell->GetMedium())
+ maFileURL = rSrcDoc.mpShell->GetMedium()->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::ToIUri);
+
+ if ( nTab2 >= static_cast<SCTAB>(maTabs.size()))
+ maTabs.resize(nTab2 + 1);
+ for (SCTAB nTab = nTab1; nTab <= nTab2; nTab++)
+ {
+ maTabs[nTab].reset(new ScTable(*this, nTab, OUString(), bColInfo, bRowInfo));
+ }
+}
+
+void ScDocument::AddUndoTab( SCTAB nTab1, SCTAB nTab2, bool bColInfo, bool bRowInfo )
+{
+ if (!bIsUndo)
+ {
+ OSL_FAIL("AddUndoTab");
+ return;
+ }
+
+ if (nTab2 >= static_cast<SCTAB>(maTabs.size()))
+ {
+ maTabs.resize(nTab2+1);
+ }
+
+ for (SCTAB nTab = nTab1; nTab <= nTab2; nTab++)
+ if (!maTabs[nTab])
+ {
+ maTabs[nTab].reset( new ScTable(*this, nTab, OUString(), bColInfo, bRowInfo) );
+ }
+}
+
+void ScDocument::SetCutMode( bool bVal )
+{
+ if (bIsClip)
+ GetClipParam().mbCutMode = bVal;
+ else
+ {
+ OSL_FAIL("SetCutMode without bIsClip");
+ }
+}
+
+bool ScDocument::IsCutMode()
+{
+ if (bIsClip)
+ return GetClipParam().mbCutMode;
+ else
+ {
+ OSL_FAIL("IsCutMode without bIsClip");
+ return false;
+ }
+}
+
+void ScDocument::CopyToDocument(SCCOL nCol1, SCROW nRow1, SCTAB nTab1,
+ SCCOL nCol2, SCROW nRow2, SCTAB nTab2,
+ InsertDeleteFlags nFlags, bool bOnlyMarked, ScDocument& rDestDoc,
+ const ScMarkData* pMarks, bool bColRowFlags )
+{
+ if (ValidTab(nTab1) && ValidTab(nTab2))
+ {
+ ScRange aThisRange(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
+ CopyToDocument(aThisRange, nFlags, bOnlyMarked, rDestDoc, pMarks, bColRowFlags);
+ }
+}
+
+void ScDocument::UndoToDocument(SCCOL nCol1, SCROW nRow1, SCTAB nTab1,
+ SCCOL nCol2, SCROW nRow2, SCTAB nTab2,
+ InsertDeleteFlags nFlags, bool bOnlyMarked, ScDocument& rDestDoc)
+{
+ PutInOrder( nCol1, nCol2 );
+ PutInOrder( nRow1, nRow2 );
+ PutInOrder( nTab1, nTab2 );
+ if (!(ValidTab(nTab1) && ValidTab(nTab2)))
+ return;
+
+ sc::AutoCalcSwitch aACSwitch(rDestDoc, false); // avoid multiple calculations
+
+ if (nTab1 > 0)
+ CopyToDocument(0, 0, 0, MaxCol(), MaxRow(), nTab1-1, InsertDeleteFlags::FORMULA, false, rDestDoc);
+
+ sc::CopyToDocContext aCxt(rDestDoc);
+ assert( nTab2 < static_cast<SCTAB>(maTabs.size()) && nTab2 < static_cast<SCTAB>(rDestDoc.maTabs.size()));
+ for (SCTAB i = nTab1; i <= nTab2; i++)
+ {
+ if (maTabs[i] && rDestDoc.maTabs[i])
+ maTabs[i]->UndoToTable(aCxt, nCol1, nRow1, nCol2, nRow2, nFlags,
+ bOnlyMarked, rDestDoc.maTabs[i].get());
+ }
+
+ if (nTab2 < MAXTAB)
+ CopyToDocument(0, 0, nTab2+1, MaxCol(), MaxRow(), MAXTAB, InsertDeleteFlags::FORMULA, false, rDestDoc);
+}
+
+void ScDocument::CopyToDocument(const ScRange& rRange,
+ InsertDeleteFlags nFlags, bool bOnlyMarked, ScDocument& rDestDoc,
+ const ScMarkData* pMarks, bool bColRowFlags)
+{
+ ScRange aNewRange = rRange;
+ aNewRange.PutInOrder();
+
+ if (rDestDoc.aDocName.isEmpty())
+ rDestDoc.aDocName = aDocName;
+
+ sc::AutoCalcSwitch aACSwitch(rDestDoc, false); // avoid multiple calculations
+ ScBulkBroadcast aBulkBroadcast(rDestDoc.GetBASM(), SfxHintId::ScDataChanged);
+ sc::DelayDeletingBroadcasters delayDeletingBroadcasters(*this);
+
+ sc::CopyToDocContext aCxt(rDestDoc);
+ aCxt.setStartListening(false);
+
+ SCTAB nMinSizeBothTabs = static_cast<SCTAB>(std::min(maTabs.size(), rDestDoc.maTabs.size()));
+ for (SCTAB i = aNewRange.aStart.Tab(); i <= aNewRange.aEnd.Tab() && i < nMinSizeBothTabs; i++)
+ {
+ ScTable* pTab = FetchTable(i);
+ ScTable* pDestTab = rDestDoc.FetchTable(i);
+ if (!pTab || !pDestTab)
+ continue;
+
+ pTab->CopyToTable(
+ aCxt, aNewRange.aStart.Col(), aNewRange.aStart.Row(), aNewRange.aEnd.Col(), aNewRange.aEnd.Row(),
+ nFlags, bOnlyMarked, pDestTab, pMarks, false, bColRowFlags,
+ /*bGlobalNamesToLocal*/false, /*bCopyCaptions*/true);
+ }
+
+ rDestDoc.StartAllListeners(aNewRange);
+}
+
+void ScDocument::UndoToDocument(const ScRange& rRange,
+ InsertDeleteFlags nFlags, bool bOnlyMarked, ScDocument& rDestDoc)
+{
+ sc::AutoCalcSwitch aAutoCalcSwitch(*this, false);
+
+ ScRange aNewRange = rRange;
+ aNewRange.PutInOrder();
+ SCTAB nTab1 = aNewRange.aStart.Tab();
+ SCTAB nTab2 = aNewRange.aEnd.Tab();
+
+ sc::CopyToDocContext aCxt(rDestDoc);
+ if (nTab1 > 0)
+ CopyToDocument(0, 0, 0, MaxCol(), MaxRow(), nTab1-1, InsertDeleteFlags::FORMULA, false, rDestDoc);
+
+ SCTAB nMinSizeBothTabs = static_cast<SCTAB>(std::min(maTabs.size(), rDestDoc.maTabs.size()));
+ for (SCTAB i = nTab1; i <= nTab2 && i < nMinSizeBothTabs; i++)
+ {
+ if (maTabs[i] && rDestDoc.maTabs[i])
+ maTabs[i]->UndoToTable(aCxt, aNewRange.aStart.Col(), aNewRange.aStart.Row(),
+ aNewRange.aEnd.Col(), aNewRange.aEnd.Row(),
+ nFlags, bOnlyMarked, rDestDoc.maTabs[i].get());
+ }
+
+ if (nTab2 < static_cast<SCTAB>(maTabs.size()))
+ CopyToDocument(0, 0 , nTab2+1, MaxCol(), MaxRow(), maTabs.size(), InsertDeleteFlags::FORMULA, false, rDestDoc);
+}
+
+void ScDocument::CopyToClip(const ScClipParam& rClipParam,
+ ScDocument* pClipDoc, const ScMarkData* pMarks,
+ bool bKeepScenarioFlags, bool bIncludeObjects )
+{
+ OSL_ENSURE( pMarks, "CopyToClip: ScMarkData fails" );
+
+ if (bIsClip)
+ return;
+
+ if (!pClipDoc)
+ {
+ SAL_WARN("sc", "CopyToClip: no ClipDoc");
+ pClipDoc = ScModule::GetClipDoc();
+ }
+
+ if (mpShell->GetMedium())
+ {
+ pClipDoc->maFileURL = mpShell->GetMedium()->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::ToIUri);
+ // for unsaved files use the title name and adjust during save of file
+ if (pClipDoc->maFileURL.isEmpty())
+ pClipDoc->maFileURL = mpShell->GetName();
+ }
+ else
+ {
+ pClipDoc->maFileURL = mpShell->GetName();
+ }
+
+ //init maTabNames
+ for (const auto& rxTab : maTabs)
+ {
+ if( rxTab )
+ {
+ OUString aTabName = rxTab->GetName();
+ pClipDoc->maTabNames.push_back(aTabName);
+ }
+ else
+ pClipDoc->maTabNames.emplace_back();
+ }
+
+ pClipDoc->aDocName = aDocName;
+ pClipDoc->SetClipParam(rClipParam);
+ ScRange aClipRange = rClipParam.getWholeRange();
+ SCTAB nEndTab = static_cast<SCTAB>(maTabs.size());
+
+ pClipDoc->ResetClip(this, pMarks);
+
+ sc::CopyToClipContext aCxt(*pClipDoc, bKeepScenarioFlags);
+ CopyRangeNamesToClip(pClipDoc, aClipRange, pMarks);
+
+ for (SCTAB i = 0; i < nEndTab; ++i)
+ {
+ if (!maTabs[i] || i >= static_cast<SCTAB>(pClipDoc->maTabs.size()) || !pClipDoc->maTabs[i])
+ continue;
+
+ if ( pMarks && !pMarks->GetTableSelect(i) )
+ continue;
+
+ maTabs[i]->CopyToClip(aCxt, rClipParam.maRanges, pClipDoc->maTabs[i].get());
+
+ if (mpDrawLayer && bIncludeObjects)
+ {
+ // also copy drawing objects
+ tools::Rectangle aObjRect = GetMMRect(
+ aClipRange.aStart.Col(), aClipRange.aStart.Row(), aClipRange.aEnd.Col(), aClipRange.aEnd.Row(), i);
+ mpDrawLayer->CopyToClip(pClipDoc, i, aObjRect);
+ }
+ }
+
+ // Make sure to mark overlapped cells.
+ pClipDoc->ExtendMerge(aClipRange, true);
+}
+
+void ScDocument::CopyStaticToDocument(const ScRange& rSrcRange, SCTAB nDestTab, ScDocument& rDestDoc)
+{
+ ScTable* pSrcTab = rSrcRange.aStart.Tab() < static_cast<SCTAB>(maTabs.size()) ? maTabs[rSrcRange.aStart.Tab()].get() : nullptr;
+ ScTable* pDestTab = nDestTab < static_cast<SCTAB>(rDestDoc.maTabs.size()) ? rDestDoc.maTabs[nDestTab].get() : nullptr;
+
+ if (!pSrcTab || !pDestTab)
+ return;
+
+ rDestDoc.GetFormatTable()->MergeFormatter(*GetFormatTable());
+ SvNumberFormatterMergeMap aMap = rDestDoc.GetFormatTable()->ConvertMergeTableToMap();
+
+ pSrcTab->CopyStaticToDocument(
+ rSrcRange.aStart.Col(), rSrcRange.aStart.Row(), rSrcRange.aEnd.Col(), rSrcRange.aEnd.Row(),
+ aMap, pDestTab);
+}
+
+void ScDocument::CopyCellToDocument( const ScAddress& rSrcPos, const ScAddress& rDestPos, ScDocument& rDestDoc )
+{
+ if (!TableExists(rSrcPos.Tab()) || !rDestDoc.TableExists(rDestPos.Tab()))
+ return;
+
+ ScTable& rSrcTab = *maTabs[rSrcPos.Tab()];
+ ScTable& rDestTab = *rDestDoc.maTabs[rDestPos.Tab()];
+
+ rSrcTab.CopyCellToDocument(rSrcPos.Col(), rSrcPos.Row(), rDestPos.Col(), rDestPos.Row(), rDestTab);
+}
+
+void ScDocument::CopyTabToClip(SCCOL nCol1, SCROW nRow1,
+ SCCOL nCol2, SCROW nRow2,
+ SCTAB nTab, ScDocument* pClipDoc)
+{
+ if (bIsClip)
+ return;
+
+ if (!pClipDoc)
+ {
+ SAL_WARN("sc", "CopyTabToClip: no ClipDoc");
+ pClipDoc = ScModule::GetClipDoc();
+ }
+
+ if (mpShell->GetMedium())
+ {
+ pClipDoc->maFileURL = mpShell->GetMedium()->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::ToIUri);
+ // for unsaved files use the title name and adjust during save of file
+ if (pClipDoc->maFileURL.isEmpty())
+ pClipDoc->maFileURL = mpShell->GetName();
+ }
+ else
+ {
+ pClipDoc->maFileURL = mpShell->GetName();
+ }
+
+ //init maTabNames
+ for (const auto& rxTab : maTabs)
+ {
+ if( rxTab )
+ {
+ OUString aTabName = rxTab->GetName();
+ pClipDoc->maTabNames.push_back(aTabName);
+ }
+ else
+ pClipDoc->maTabNames.emplace_back();
+ }
+
+ PutInOrder( nCol1, nCol2 );
+ PutInOrder( nRow1, nRow2 );
+
+ ScClipParam& rClipParam = pClipDoc->GetClipParam();
+ pClipDoc->aDocName = aDocName;
+ rClipParam.maRanges.RemoveAll();
+ rClipParam.maRanges.push_back(ScRange(nCol1, nRow1, 0, nCol2, nRow2, 0));
+ pClipDoc->ResetClip( this, nTab );
+
+ sc::CopyToClipContext aCxt(*pClipDoc, false);
+ if (nTab < static_cast<SCTAB>(maTabs.size()) && nTab < static_cast<SCTAB>(pClipDoc->maTabs.size()))
+ if (maTabs[nTab] && pClipDoc->maTabs[nTab])
+ maTabs[nTab]->CopyToClip(aCxt, nCol1, nRow1, nCol2, nRow2, pClipDoc->maTabs[nTab].get());
+
+ pClipDoc->GetClipParam().mbCutMode = false;
+}
+
+void ScDocument::TransposeClip(ScDocument* pTransClip, InsertDeleteFlags nFlags, bool bAsLink,
+ bool bIncludeFiltered)
+{
+ OSL_ENSURE( bIsClip && pTransClip && pTransClip->bIsClip,
+ "TransposeClip with wrong Document" );
+
+ // initialize
+ // -> pTransClip has to be deleted before the original document!
+
+ pTransClip->ResetClip(this, nullptr); // all
+
+ // Take over range
+
+ if (pRangeName)
+ {
+ pTransClip->GetRangeName()->clear();
+ for (const auto& rEntry : *pRangeName)
+ {
+ sal_uInt16 nIndex = rEntry.second->GetIndex();
+ ScRangeData* pData = new ScRangeData(*rEntry.second);
+ if (pTransClip->pRangeName->insert(pData))
+ pData->SetIndex(nIndex);
+ }
+ }
+
+ ScRange aCombinedClipRange = GetClipParam().getWholeRange();
+
+ if (!ValidRow(aCombinedClipRange.aEnd.Row() - aCombinedClipRange.aStart.Row()))
+ {
+ SAL_WARN("sc", "TransposeClip: Too big");
+ return;
+ }
+
+ // Transpose of filtered multi range row selection is a special case since filtering
+ // and selection are in the same dimension (i.e. row).
+ // The filtered row status and the selection ranges are not available at the same time,
+ // handle this case specially, do not use GetClipParam().getWholeRange(),
+ // instead loop through the ranges, calculate the row offset and handle filtered rows and
+ // create in ScClipParam::transpose() a unified range.
+ const bool bIsMultiRangeRowFilteredTranspose
+ = !bIncludeFiltered && GetClipParam().isMultiRange()
+ && HasFilteredRows(aCombinedClipRange.aStart.Row(), aCombinedClipRange.aEnd.Row(),
+ aCombinedClipRange.aStart.Tab())
+ && GetClipParam().meDirection == ScClipParam::Row;
+
+ ScRangeList aClipRanges;
+ if (bIsMultiRangeRowFilteredTranspose)
+ aClipRanges = GetClipParam().maRanges;
+ else
+ aClipRanges = ScRangeList(aCombinedClipRange);
+
+ // The data
+ ScRange aClipRange;
+ SCROW nRowCount = 0; // next consecutive row
+ for (size_t j = 0, n = aClipRanges.size(); j < n; ++j)
+ {
+ aClipRange = aClipRanges[j];
+
+ SCROW nRowOffset = 0;
+ if (bIsMultiRangeRowFilteredTranspose)
+ {
+ // adjust for the rows that are filtered
+ nRowOffset = nRowCount;
+
+ // calculate filtered rows of current clip range
+ SCROW nRowCountNonFiltered = CountNonFilteredRows(
+ aClipRange.aStart.Row(), aClipRange.aEnd.Row(), aClipRange.aStart.Tab());
+ assert(!bIncludeFiltered && "bIsMultiRangeRowFilteredTranspose can only be true if bIncludeFiltered is false");
+ nRowCount += nRowCountNonFiltered; // for next iteration
+ }
+
+ for (SCTAB i = 0; i < static_cast<SCTAB>(maTabs.size()); i++)
+ {
+ if (maTabs[i])
+ {
+ OSL_ENSURE(pTransClip->maTabs[i], "TransposeClip: Table not there");
+ maTabs[i]->TransposeClip(
+ aClipRange.aStart.Col(), aClipRange.aStart.Row(), aClipRange.aEnd.Col(),
+ aClipRange.aEnd.Row(), aCombinedClipRange.aStart.Row(), nRowOffset,
+ pTransClip->maTabs[i].get(), nFlags, bAsLink, bIncludeFiltered);
+
+ if ( mpDrawLayer && ( nFlags & InsertDeleteFlags::OBJECTS ) )
+ {
+ // Drawing objects are copied to the new area without transposing.
+ // CopyFromClip is used to adjust the objects to the transposed block's
+ // cell range area.
+ // (mpDrawLayer in the original clipboard document is set only if there
+ // are drawing objects to copy)
+
+ pTransClip->InitDrawLayer();
+ tools::Rectangle aSourceRect = GetMMRect( aClipRange.aStart.Col(), aClipRange.aStart.Row(),
+ aClipRange.aEnd.Col(), aClipRange.aEnd.Row(), i );
+ tools::Rectangle aDestRect = pTransClip->GetMMRect( 0, 0,
+ static_cast<SCCOL>(aClipRange.aEnd.Row() - aClipRange.aStart.Row()),
+ static_cast<SCROW>(aClipRange.aEnd.Col() - aClipRange.aStart.Col()), i );
+ pTransClip->mpDrawLayer->CopyFromClip( mpDrawLayer.get(), i, aSourceRect, ScAddress(0,0,i), aDestRect );
+ }
+ }
+ }
+ }
+
+ pTransClip->SetClipParam(GetClipParam());
+ pTransClip->GetClipParam().transpose(*this, bIncludeFiltered,
+ bIsMultiRangeRowFilteredTranspose);
+
+ // This happens only when inserting...
+
+ GetClipParam().mbCutMode = false;
+}
+
+namespace {
+
+void copyUsedNamesToClip(ScRangeName* pClipRangeName, ScRangeName* pRangeName,
+ const sc::UpdatedRangeNames::NameIndicesType& rUsedNames)
+{
+ pClipRangeName->clear();
+ for (const auto& rEntry : *pRangeName) //TODO: also DB and Pivot regions!!!
+ {
+ sal_uInt16 nIndex = rEntry.second->GetIndex();
+ bool bInUse = (rUsedNames.count(nIndex) > 0);
+ if (!bInUse)
+ continue;
+
+ ScRangeData* pData = new ScRangeData(*rEntry.second);
+ if (pClipRangeName->insert(pData))
+ pData->SetIndex(nIndex);
+ }
+}
+
+}
+
+void ScDocument::CopyRangeNamesToClip(ScDocument* pClipDoc, const ScRange& rClipRange, const ScMarkData* pMarks)
+{
+ if (!pRangeName || pRangeName->empty())
+ return;
+
+ sc::UpdatedRangeNames aUsedNames; // indexes of named ranges that are used in the copied cells
+ SCTAB nMinSizeBothTabs = static_cast<SCTAB>(std::min(maTabs.size(), pClipDoc->maTabs.size()));
+ for (SCTAB i = 0; i < nMinSizeBothTabs; ++i)
+ if (maTabs[i] && pClipDoc->maTabs[i])
+ if ( !pMarks || pMarks->GetTableSelect(i) )
+ maTabs[i]->FindRangeNamesInUse(
+ rClipRange.aStart.Col(), rClipRange.aStart.Row(),
+ rClipRange.aEnd.Col(), rClipRange.aEnd.Row(), aUsedNames);
+
+ /* TODO: handle also sheet-local names */
+ sc::UpdatedRangeNames::NameIndicesType aUsedGlobalNames( aUsedNames.getUpdatedNames(-1));
+ copyUsedNamesToClip(pClipDoc->GetRangeName(), pRangeName.get(), aUsedGlobalNames);
+}
+
+ScDocument::NumFmtMergeHandler::NumFmtMergeHandler(ScDocument& rDoc, const ScDocument& rSrcDoc)
+ : mrDoc(rDoc)
+{
+ mrDoc.MergeNumberFormatter(rSrcDoc);
+}
+
+ScDocument::NumFmtMergeHandler::~NumFmtMergeHandler()
+{
+ ScMutationGuard aGuard(mrDoc, ScMutationGuardFlags::CORE);
+ mrDoc.pFormatExchangeList = nullptr;
+}
+
+void ScDocument::PrepareFormulaCalc()
+{
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ mpFormulaGroupCxt.reset();
+}
+
+SvtBroadcaster* ScDocument::GetBroadcaster( const ScAddress& rPos )
+{
+ ScTable* pTab = FetchTable(rPos.Tab());
+ if (!pTab)
+ return nullptr;
+
+ return pTab->GetBroadcaster(rPos.Col(), rPos.Row());
+}
+
+const SvtBroadcaster* ScDocument::GetBroadcaster( const ScAddress& rPos ) const
+{
+ const ScTable* pTab = FetchTable(rPos.Tab());
+ if (!pTab)
+ return nullptr;
+
+ return pTab->GetBroadcaster(rPos.Col(), rPos.Row());
+}
+
+void ScDocument::DeleteBroadcasters( sc::ColumnBlockPosition& rBlockPos, const ScAddress& rTopPos, SCROW nLength )
+{
+ ScTable* pTab = FetchTable(rTopPos.Tab());
+ if (!pTab || nLength <= 0)
+ return;
+
+ pTab->DeleteBroadcasters(rBlockPos, rTopPos.Col(), rTopPos.Row(), rTopPos.Row()+nLength-1);
+}
+
+#if DUMP_COLUMN_STORAGE
+void ScDocument::DumpColumnStorage( SCTAB nTab, SCCOL nCol ) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ pTab->DumpColumnStorage(nCol);
+}
+#endif
+
+#if DEBUG_AREA_BROADCASTER
+void ScDocument::DumpAreaBroadcasters() const
+{
+ if (pBASM)
+ pBASM->Dump();
+}
+#endif
+
+bool ScDocument::TableExists( SCTAB nTab ) const
+{
+ return ValidTab(nTab) && o3tl::make_unsigned(nTab) < maTabs.size() && maTabs[nTab];
+}
+
+ScTable* ScDocument::FetchTable( SCTAB nTab )
+{
+ if (!TableExists(nTab))
+ return nullptr;
+
+ return maTabs[nTab].get();
+}
+
+const ScTable* ScDocument::FetchTable( SCTAB nTab ) const
+{
+ if (!TableExists(nTab))
+ return nullptr;
+
+ return maTabs[nTab].get();
+}
+
+ScColumnsRange ScDocument::GetWritableColumnsRange( SCTAB nTab, SCCOL nColBegin, SCCOL nColEnd)
+{
+ if (!TableExists(nTab))
+ {
+ SAL_WARN("sc", "GetWritableColumnsRange() called for non-existent table");
+ return ScColumnsRange(-1, -1);
+ }
+ return maTabs[nTab]->GetWritableColumnsRange(nColBegin, nColEnd);
+}
+
+ScColumnsRange ScDocument::GetAllocatedColumnsRange( SCTAB nTab, SCCOL nColBegin, SCCOL nColEnd) const
+{
+ if (!TableExists(nTab))
+ return ScColumnsRange(-1, -1);
+ return maTabs[nTab]->GetAllocatedColumnsRange(nColBegin, nColEnd);
+}
+
+ScColumnsRange ScDocument::GetColumnsRange( SCTAB nTab, SCCOL nColBegin, SCCOL nColEnd) const
+{
+ if (!TableExists(nTab))
+ return ScColumnsRange(-1, -1);
+ return maTabs[nTab]->GetColumnsRange(nColBegin, nColEnd);
+}
+
+void ScDocument::MergeNumberFormatter(const ScDocument& rSrcDoc)
+{
+ SvNumberFormatter* pThisFormatter = mxPoolHelper->GetFormTable();
+ SvNumberFormatter* pOtherFormatter = rSrcDoc.mxPoolHelper->GetFormTable();
+ if (pOtherFormatter && pOtherFormatter != pThisFormatter)
+ {
+ SvNumberFormatterIndexTable* pExchangeList =
+ pThisFormatter->MergeFormatter(*pOtherFormatter);
+ if (!pExchangeList->empty())
+ {
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ pFormatExchangeList = pExchangeList;
+ }
+ }
+}
+
+ScClipParam& ScDocument::GetClipParam()
+{
+ if (!mpClipParam)
+ mpClipParam.reset(new ScClipParam);
+
+ return *mpClipParam;
+}
+
+void ScDocument::SetClipParam(const ScClipParam& rParam)
+{
+ mpClipParam.reset(new ScClipParam(rParam));
+}
+
+bool ScDocument::IsClipboardSource() const
+{
+ if (bIsClip || mpShell == nullptr || mpShell->IsLoading())
+ return false;
+
+ ScDocument* pClipDoc = ScModule::GetClipDoc();
+ return pClipDoc && pClipDoc->bIsClip && pClipDoc->mxPoolHelper.is() && mxPoolHelper.is() &&
+ mxPoolHelper->GetDocPool() == pClipDoc->mxPoolHelper->GetDocPool();
+}
+
+void ScDocument::StartListeningFromClip(
+ sc::StartListeningContext& rStartCxt, sc::EndListeningContext& rEndCxt,
+ SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
+{
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ pTab->StartListeningFormulaCells(rStartCxt, rEndCxt, nCol1, nRow1, nCol2, nRow2);
+}
+
+void ScDocument::StartListeningFromClip( SCCOL nCol1, SCROW nRow1,
+ SCCOL nCol2, SCROW nRow2,
+ const ScMarkData& rMark, InsertDeleteFlags nInsFlag )
+{
+ if (!(nInsFlag & InsertDeleteFlags::CONTENTS))
+ return;
+
+ auto pSet = std::make_shared<sc::ColumnBlockPositionSet>(*this);
+
+ sc::StartListeningContext aStartCxt(*this, pSet);
+ sc::EndListeningContext aEndCxt(*this, pSet, nullptr);
+
+ for (SCTAB nTab : rMark)
+ StartListeningFromClip(aStartCxt, aEndCxt, nTab, nCol1, nRow1, nCol2, nRow2);
+}
+
+void ScDocument::SetDirtyFromClip(
+ SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, const ScMarkData& rMark,
+ InsertDeleteFlags nInsFlag, sc::ColumnSpanSet& rBroadcastSpans )
+{
+ if (nInsFlag & InsertDeleteFlags::CONTENTS)
+ {
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+ if (maTabs[rTab])
+ maTabs[rTab]->SetDirtyFromClip(nCol1, nRow1, nCol2, nRow2, rBroadcastSpans);
+ }
+ }
+}
+
+bool ScDocument::InitColumnBlockPosition( sc::ColumnBlockPosition& rBlockPos, SCTAB nTab, SCCOL nCol )
+{
+ if (!TableExists(nTab))
+ return false;
+
+ return maTabs[nTab]->InitColumnBlockPosition(rBlockPos, nCol);
+}
+
+void ScDocument::CopyBlockFromClip(
+ sc::CopyFromClipContext& rCxt, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ const ScMarkData& rMark, SCCOL nDx, SCROW nDy )
+{
+ TableContainer& rClipTabs = rCxt.getClipDoc()->maTabs;
+ SCTAB nTabEnd = rCxt.getTabEnd();
+ SCTAB nClipTab = 0;
+ for (SCTAB i = rCxt.getTabStart(); i <= nTabEnd && i < static_cast<SCTAB>(maTabs.size()); i++)
+ {
+ if (maTabs[i] && rMark.GetTableSelect(i) )
+ {
+ while (!rClipTabs[nClipTab]) nClipTab = (nClipTab+1) % static_cast<SCTAB>(rClipTabs.size());
+
+ maTabs[i]->CopyFromClip(
+ rCxt, nCol1, nRow1, nCol2, nRow2, nDx, nDy, rClipTabs[nClipTab].get());
+
+ if (rCxt.getClipDoc()->mpDrawLayer && (rCxt.getInsertFlag() & InsertDeleteFlags::OBJECTS))
+ {
+ // also copy drawing objects
+
+ // drawing layer must be created before calling CopyFromClip
+ // (ScDocShell::MakeDrawLayer also does InitItems etc.)
+ OSL_ENSURE( mpDrawLayer, "CopyBlockFromClip: No drawing layer" );
+ if ( mpDrawLayer )
+ {
+ // For GetMMRect, the row heights in the target document must already be valid
+ // (copied in an extra step before pasting, or updated after pasting cells, but
+ // before pasting objects).
+
+ tools::Rectangle aSourceRect = rCxt.getClipDoc()->GetMMRect(
+ nCol1-nDx, nRow1-nDy, nCol2-nDx, nRow2-nDy, nClipTab );
+ tools::Rectangle aDestRect = GetMMRect( nCol1, nRow1, nCol2, nRow2, i );
+ mpDrawLayer->CopyFromClip(rCxt.getClipDoc()->mpDrawLayer.get(), nClipTab, aSourceRect,
+ ScAddress( nCol1, nRow1, i ), aDestRect );
+ }
+ }
+
+ nClipTab = (nClipTab+1) % static_cast<SCTAB>(rClipTabs.size());
+ }
+ }
+ if (!(rCxt.getInsertFlag() & InsertDeleteFlags::CONTENTS))
+ return;
+
+ nClipTab = 0;
+ for (SCTAB i = rCxt.getTabStart(); i <= nTabEnd && i < static_cast<SCTAB>(maTabs.size()); i++)
+ {
+ if (maTabs[i] && rMark.GetTableSelect(i) )
+ {
+ while (!rClipTabs[nClipTab]) nClipTab = (nClipTab+1) % static_cast<SCTAB>(rClipTabs.size());
+ SCTAB nDz = i - nClipTab;
+
+ // ranges of consecutive selected tables (in clipboard and dest. doc)
+ // must be handled in one UpdateReference call
+ SCTAB nFollow = 0;
+ while ( i + nFollow < nTabEnd
+ && rMark.GetTableSelect( i + nFollow + 1 )
+ && nClipTab + nFollow < MAXTAB
+ && rClipTabs[(nClipTab + nFollow + 1) % static_cast<SCTAB>(rClipTabs.size())] )
+ ++nFollow;
+
+ sc::RefUpdateContext aRefCxt(*this, rCxt.getClipDoc());
+ aRefCxt.maRange = ScRange(nCol1, nRow1, i, nCol2, nRow2, i+nFollow);
+ aRefCxt.mnColDelta = nDx;
+ aRefCxt.mnRowDelta = nDy;
+ aRefCxt.mnTabDelta = nDz;
+ aRefCxt.setBlockPositionReference(rCxt.getBlockPositionSet()); // share mdds position caching
+ if (rCxt.getClipDoc()->GetClipParam().mbCutMode)
+ {
+ // Update references only if cut originates from the same
+ // document we are pasting into.
+ if (rCxt.getClipDoc()->GetPool() == GetPool())
+ {
+ bool bOldInserting = IsInsertingFromOtherDoc();
+ SetInsertingFromOtherDoc( true);
+ aRefCxt.meMode = URM_MOVE;
+ UpdateReference(aRefCxt, rCxt.getUndoDoc(), false);
+
+ // For URM_MOVE group listeners may have been removed,
+ // re-establish them.
+ if (!aRefCxt.maRegroupCols.empty())
+ {
+ /* TODO: holding the ColumnSet in a shared_ptr at
+ * RefUpdateContext would eliminate the need of
+ * copying it here. */
+ auto pColSet = std::make_shared<sc::ColumnSet>( aRefCxt.maRegroupCols);
+ StartNeededListeners( pColSet);
+ }
+
+ SetInsertingFromOtherDoc( bOldInserting);
+ }
+ }
+ else
+ {
+ aRefCxt.meMode = URM_COPY;
+ UpdateReference(aRefCxt, rCxt.getUndoDoc(), false);
+ }
+
+ nClipTab = (nClipTab+nFollow+1) % static_cast<SCTAB>(rClipTabs.size());
+ i = sal::static_int_cast<SCTAB>( i + nFollow );
+ }
+ }
+}
+
+SCROW ScDocument::CopyNonFilteredFromClip(sc::CopyFromClipContext& rCxt, SCCOL nCol1, SCROW nRow1,
+ SCCOL nCol2, SCROW nRow2, const ScMarkData& rMark,
+ SCCOL nDx, SCROW& rClipStartRow, SCROW nClipEndRow)
+{
+ // call CopyBlockFromClip for ranges of consecutive non-filtered rows
+ // nCol1/nRow1 etc. is in target doc
+
+ // filtered state is taken from first used table in clipboard (as in GetClipArea)
+ SCTAB nFlagTab = 0;
+ TableContainer& rClipTabs = rCxt.getClipDoc()->maTabs;
+ while ( nFlagTab < static_cast<SCTAB>(rClipTabs.size()) && !rClipTabs[nFlagTab] )
+ ++nFlagTab;
+
+ SCROW nSourceRow = rClipStartRow;
+ SCROW nSourceEnd = nClipEndRow;
+ SCROW nDestRow = nRow1;
+ SCROW nFilteredRows = 0;
+
+ while ( nSourceRow <= nSourceEnd && nDestRow <= nRow2 )
+ {
+ // skip filtered rows
+ SCROW nSourceRowOriginal = nSourceRow;
+ nSourceRow = rCxt.getClipDoc()->FirstNonFilteredRow(nSourceRow, nSourceEnd, nFlagTab);
+ nFilteredRows += nSourceRow - nSourceRowOriginal;
+
+ if ( nSourceRow <= nSourceEnd )
+ {
+ // look for more non-filtered rows following
+ SCROW nLastRow = nSourceRow;
+ (void)rCxt.getClipDoc()->RowFiltered(nSourceRow, nFlagTab, nullptr, &nLastRow);
+ SCROW nFollow = nLastRow - nSourceRow;
+
+ if (nFollow > nSourceEnd - nSourceRow)
+ nFollow = nSourceEnd - nSourceRow;
+ if (nFollow > nRow2 - nDestRow)
+ nFollow = nRow2 - nDestRow;
+
+ SCROW nNewDy = nDestRow - nSourceRow;
+ CopyBlockFromClip(
+ rCxt, nCol1, nDestRow, nCol2, nDestRow + nFollow, rMark, nDx, nNewDy);
+
+ nSourceRow += nFollow + 1;
+ nDestRow += nFollow + 1;
+ }
+ }
+ rClipStartRow = nSourceRow;
+ return nFilteredRows;
+}
+
+namespace {
+
+class BroadcastAction : public sc::ColumnSpanSet::ColumnAction
+{
+ ScDocument& mrDoc;
+ ScColumn* mpCol;
+
+public:
+ explicit BroadcastAction( ScDocument& rDoc ) : mrDoc(rDoc), mpCol(nullptr) {}
+
+ virtual void startColumn( ScColumn* pCol ) override
+ {
+ mpCol = pCol;
+ }
+
+ virtual void execute( SCROW nRow1, SCROW nRow2, bool bVal ) override
+ {
+ if (!bVal)
+ return;
+
+ assert(mpCol);
+ ScRange aRange(mpCol->GetCol(), nRow1, mpCol->GetTab());
+ aRange.aEnd.SetRow(nRow2);
+ mrDoc.BroadcastCells(aRange, SfxHintId::ScDataChanged);
+ };
+};
+
+}
+
+void ScDocument::CopyFromClip(
+ const ScRange& rDestRange, const ScMarkData& rMark, InsertDeleteFlags nInsFlag,
+ ScDocument* pRefUndoDoc, ScDocument* pClipDoc, bool bResetCut,
+ bool bAsLink, bool bIncludeFiltered, bool bSkipEmptyCells,
+ const ScRangeList * pDestRanges )
+{
+ if (bIsClip)
+ return;
+
+ if (!pClipDoc)
+ {
+ OSL_FAIL("CopyFromClip: no ClipDoc");
+ pClipDoc = ScModule::GetClipDoc();
+ }
+
+ if (!pClipDoc->bIsClip || !pClipDoc->GetTableCount())
+ return;
+
+ sc::AutoCalcSwitch aACSwitch(*this, false); // temporarily turn off auto calc.
+
+ NumFmtMergeHandler aNumFmtMergeHdl(*this, *pClipDoc);
+
+ SCCOL nAllCol1 = rDestRange.aStart.Col();
+ SCROW nAllRow1 = rDestRange.aStart.Row();
+ SCCOL nAllCol2 = rDestRange.aEnd.Col();
+ SCROW nAllRow2 = rDestRange.aEnd.Row();
+
+ SCCOL nXw = 0;
+ SCROW nYw = 0;
+ ScRange aClipRange = pClipDoc->GetClipParam().getWholeRange();
+ for (SCTAB nTab = 0; nTab < static_cast<SCTAB>(pClipDoc->maTabs.size()); nTab++) // find largest merge overlap
+ if (pClipDoc->maTabs[nTab]) // all sheets of the clipboard content
+ {
+ SCCOL nThisEndX = aClipRange.aEnd.Col();
+ SCROW nThisEndY = aClipRange.aEnd.Row();
+ pClipDoc->ExtendMerge( aClipRange.aStart.Col(),
+ aClipRange.aStart.Row(),
+ nThisEndX, nThisEndY, nTab );
+ // only extra value from ExtendMerge
+ nThisEndX = sal::static_int_cast<SCCOL>( nThisEndX - aClipRange.aEnd.Col() );
+ nThisEndY = sal::static_int_cast<SCROW>( nThisEndY - aClipRange.aEnd.Row() );
+ if ( nThisEndX > nXw )
+ nXw = nThisEndX;
+ if ( nThisEndY > nYw )
+ nYw = nThisEndY;
+ }
+
+ SCCOL nDestAddX;
+ SCROW nDestAddY;
+ pClipDoc->GetClipArea( nDestAddX, nDestAddY, bIncludeFiltered );
+ nXw = sal::static_int_cast<SCCOL>( nXw + nDestAddX );
+ nYw = sal::static_int_cast<SCROW>( nYw + nDestAddY ); // ClipArea, plus ExtendMerge value
+
+ /* Decide which contents to delete before copying. Delete all
+ contents if nInsFlag contains any real content flag.
+ #i102056# Notes are pasted from clipboard in a second pass,
+ together with the special flag InsertDeleteFlags::ADDNOTES that states to not
+ overwrite/delete existing cells but to insert the notes into
+ these cells. In this case, just delete old notes from the
+ destination area. */
+ InsertDeleteFlags nDelFlag = InsertDeleteFlags::NONE;
+ if ( (nInsFlag & (InsertDeleteFlags::CONTENTS | InsertDeleteFlags::ADDNOTES)) == (InsertDeleteFlags::NOTE | InsertDeleteFlags::ADDNOTES) )
+ nDelFlag |= InsertDeleteFlags::NOTE;
+ else if ( nInsFlag & InsertDeleteFlags::CONTENTS )
+ nDelFlag |= InsertDeleteFlags::CONTENTS;
+
+ if (nInsFlag & InsertDeleteFlags::ATTRIB)
+ nDelFlag |= InsertDeleteFlags::ATTRIB;
+
+ sc::CopyFromClipContext aCxt(*this, pRefUndoDoc, pClipDoc, nInsFlag, bAsLink, bSkipEmptyCells);
+ std::pair<SCTAB,SCTAB> aTabRanges = getMarkedTableRange(maTabs, rMark);
+ aCxt.setTabRange(aTabRanges.first, aTabRanges.second);
+ aCxt.setDeleteFlag(nDelFlag);
+
+ ScRangeList aLocalRangeList;
+ if (!pDestRanges)
+ {
+ aLocalRangeList.push_back( rDestRange);
+ pDestRanges = &aLocalRangeList;
+ }
+
+ bInsertingFromOtherDoc = true; // No Broadcast/Listener created at Insert
+
+ sc::ColumnSpanSet aBroadcastSpans;
+
+ SCCOL nClipStartCol = aClipRange.aStart.Col();
+ SCROW nClipStartRow = aClipRange.aStart.Row();
+ SCROW nClipEndRow = aClipRange.aEnd.Row();
+ for ( size_t nRange = 0; nRange < pDestRanges->size(); ++nRange )
+ {
+ const ScRange & rRange = (*pDestRanges)[nRange];
+ SCCOL nCol1 = rRange.aStart.Col();
+ SCROW nRow1 = rRange.aStart.Row();
+ SCCOL nCol2 = rRange.aEnd.Col();
+ SCROW nRow2 = rRange.aEnd.Row();
+
+ aCxt.setDestRange(nCol1, nRow1, nCol2, nRow2);
+ DeleteBeforeCopyFromClip(aCxt, rMark, aBroadcastSpans); // <- this removes existing formula listeners
+
+ if (CopyOneCellFromClip(aCxt, nCol1, nRow1, nCol2, nRow2))
+ continue;
+
+ SCCOL nC1 = nCol1;
+ SCROW nR1 = nRow1;
+ SCCOL nC2 = nC1 + nXw;
+ if (nC2 > nCol2)
+ nC2 = nCol2;
+ SCROW nR2 = nR1 + nYw;
+ if (nR2 > nRow2)
+ nR2 = nRow2;
+
+ const SCCOLROW nThreshold = 8192;
+ bool bPreallocatePattern = ((nInsFlag & InsertDeleteFlags::ATTRIB) && (nRow2 - nRow1 > nThreshold));
+ std::vector< SCTAB > vTables;
+
+ if (bPreallocatePattern)
+ {
+ for (SCTAB i = aCxt.getTabStart(); i <= aCxt.getTabEnd(); ++i)
+ if (maTabs[i] && rMark.GetTableSelect( i ) )
+ vTables.push_back( i );
+ }
+
+ do
+ {
+ // Pasting is done column-wise, when pasting to a filtered
+ // area this results in partitioning and we have to
+ // remember and reset the start row for each column until
+ // it can be advanced for the next chunk of unfiltered
+ // rows.
+ SCROW nSaveClipStartRow = nClipStartRow;
+ do
+ {
+ nClipStartRow = nSaveClipStartRow;
+ SCCOL nDx = nC1 - nClipStartCol;
+ SCROW nDy = nR1 - nClipStartRow;
+ if ( bIncludeFiltered )
+ {
+ CopyBlockFromClip(
+ aCxt, nC1, nR1, nC2, nR2, rMark, nDx, nDy);
+ nClipStartRow += nR2 - nR1 + 1;
+ }
+ else
+ {
+ CopyNonFilteredFromClip(aCxt, nC1, nR1, nC2, nR2, rMark, nDx, nClipStartRow,
+ nClipEndRow);
+ }
+ nC1 = nC2 + 1;
+ nC2 = std::min(static_cast<SCCOL>(nC1 + nXw), nCol2);
+ } while (nC1 <= nCol2);
+ if (nClipStartRow > nClipEndRow)
+ nClipStartRow = aClipRange.aStart.Row();
+ nC1 = nCol1;
+ nC2 = nC1 + nXw;
+ if (nC2 > nCol2)
+ nC2 = nCol2;
+
+ // Preallocate pattern memory once if further chunks are to be pasted.
+ if (bPreallocatePattern && (nR2+1) <= nRow2)
+ {
+ SCROW nR3 = nR2 + 1;
+ for (SCTAB nTab : vTables)
+ {
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ {
+ // Pattern count of the first chunk pasted.
+ SCSIZE nChunk = GetPatternCount( nTab, nCol, nR1, nR2);
+ // If it is only one pattern per chunk and chunks are
+ // pasted consecutively then it will get its range
+ // enlarged for each chunk and no further allocation
+ // happens. For non-consecutive chunks we're out of
+ // luck in this case.
+ if (nChunk > 1)
+ {
+ SCSIZE nNeeded = nChunk * (nRow2 - nR3 + 1) / (nYw + 1);
+ SCSIZE nRemain = GetPatternCount( nTab, nCol, nR3, nRow2);
+ if (nNeeded > nRemain)
+ {
+ SCSIZE nCurr = GetPatternCount( nTab, nCol);
+ ReservePatternCount( nTab, nCol, nCurr + nNeeded);
+ }
+ }
+ }
+ }
+ bPreallocatePattern = false;
+ }
+
+ nR1 = nR2 + 1;
+ nR2 = std::min(static_cast<SCROW>(nR1 + nYw), nRow2);
+ } while (nR1 <= nRow2);
+ }
+
+ bInsertingFromOtherDoc = false;
+
+ if (nInsFlag & InsertDeleteFlags::CONTENTS)
+ {
+ for (SCTAB nTab : rMark)
+ aCxt.setListeningFormulaSpans(nTab, nAllCol1, nAllRow1, nAllCol2, nAllRow2);
+ }
+
+ // Create Listener after everything has been inserted
+ aCxt.startListeningFormulas();
+
+ {
+ ScBulkBroadcast aBulkBroadcast( GetBASM(), SfxHintId::ScDataChanged);
+
+ // Set all formula cells dirty, and collect non-empty non-formula cell
+ // positions so that we can broadcast on them below.
+ SetDirtyFromClip(nAllCol1, nAllRow1, nAllCol2, nAllRow2, rMark, nInsFlag, aBroadcastSpans);
+
+ BroadcastAction aAction(*this);
+ aBroadcastSpans.executeColumnAction(*this, aAction);
+ }
+
+ if (bResetCut)
+ pClipDoc->GetClipParam().mbCutMode = false;
+}
+
+void ScDocument::CopyMultiRangeFromClip(const ScAddress& rDestPos, const ScMarkData& rMark,
+ InsertDeleteFlags nInsFlag, ScDocument* pClipDoc,
+ bool bResetCut, bool bAsLink, bool bIncludeFiltered,
+ bool bSkipAttrForEmpty)
+{
+ if (bIsClip)
+ return;
+
+ if (!pClipDoc->bIsClip || !pClipDoc->GetTableCount())
+ // There is nothing in the clip doc to copy.
+ return;
+
+ // Right now, we don't allow pasting into filtered rows, so we don't even handle it here.
+
+ sc::AutoCalcSwitch aACSwitch(*this, false); // turn of auto calc temporarily.
+ NumFmtMergeHandler aNumFmtMergeHdl(*this, *pClipDoc);
+
+ const ScRange& aDestRange = rMark.GetMarkArea();
+
+ bInsertingFromOtherDoc = true; // No Broadcast/Listener created at Insert
+
+ SCCOL nCol1 = rDestPos.Col();
+ SCROW nRow1 = rDestPos.Row();
+ ScClipParam& rClipParam = pClipDoc->GetClipParam();
+
+ sc::ColumnSpanSet aBroadcastSpans;
+
+ if (!bSkipAttrForEmpty)
+ {
+ // Do the deletion first.
+ SCCOL nColSize = rClipParam.getPasteColSize();
+ SCROW nRowSize = rClipParam.getPasteRowSize(*pClipDoc, bIncludeFiltered);
+
+ DeleteArea(nCol1, nRow1, nCol1+nColSize-1, nRow1+nRowSize-1, rMark, InsertDeleteFlags::CONTENTS, false, &aBroadcastSpans);
+ }
+
+ sc::CopyFromClipContext aCxt(*this, nullptr, pClipDoc, nInsFlag, bAsLink, bSkipAttrForEmpty);
+ std::pair<SCTAB,SCTAB> aTabRanges = getMarkedTableRange(maTabs, rMark);
+ aCxt.setTabRange(aTabRanges.first, aTabRanges.second);
+
+ for (size_t i = 0, n = rClipParam.maRanges.size(); i < n; ++i)
+ {
+ const ScRange & rRange = rClipParam.maRanges[i];
+
+ SCROW nRowCount = rRange.aEnd.Row() - rRange.aStart.Row() + 1;
+ SCCOL nDx = static_cast<SCCOL>(nCol1 - rRange.aStart.Col());
+ SCROW nDy = static_cast<SCROW>(nRow1 - rRange.aStart.Row());
+ SCCOL nCol2 = nCol1 + rRange.aEnd.Col() - rRange.aStart.Col();
+ SCROW nEndRow = nRow1 + nRowCount - 1;
+ SCROW nFilteredRows = 0;
+
+ if (bIncludeFiltered)
+ {
+ CopyBlockFromClip(aCxt, nCol1, nRow1, nCol2, nEndRow, rMark, nDx, nDy);
+ }
+ else
+ {
+ SCROW nClipStartRow = rRange.aStart.Row();
+ SCROW nClipEndRow = rRange.aEnd.Row();
+ nFilteredRows += CopyNonFilteredFromClip(aCxt, nCol1, nRow1, nCol2, nEndRow, rMark, nDx,
+ nClipStartRow, nClipEndRow);
+ nRowCount -= nFilteredRows;
+ }
+
+ switch (rClipParam.meDirection)
+ {
+ case ScClipParam::Row:
+ // Begin row for the next range being pasted.
+ nRow1 += nRowCount;
+ break;
+ case ScClipParam::Column:
+ nCol1 += rRange.aEnd.Col() - rRange.aStart.Col() + 1;
+ break;
+ default:
+ ;
+ }
+ }
+
+ bInsertingFromOtherDoc = false;
+
+ // Create Listener after everything has been inserted
+ StartListeningFromClip(aDestRange.aStart.Col(), aDestRange.aStart.Row(),
+ aDestRange.aEnd.Col(), aDestRange.aEnd.Row(), rMark, nInsFlag );
+
+ {
+ ScBulkBroadcast aBulkBroadcast( GetBASM(), SfxHintId::ScDataChanged);
+
+ // Set formula cells dirty and collect non-formula cells.
+ SetDirtyFromClip(
+ aDestRange.aStart.Col(), aDestRange.aStart.Row(), aDestRange.aEnd.Col(), aDestRange.aEnd.Row(),
+ rMark, nInsFlag, aBroadcastSpans);
+
+ BroadcastAction aAction(*this);
+ aBroadcastSpans.executeColumnAction(*this, aAction);
+ }
+
+ if (bResetCut)
+ pClipDoc->GetClipParam().mbCutMode = false;
+}
+
+void ScDocument::SetClipArea( const ScRange& rArea, bool bCut )
+{
+ if (bIsClip)
+ {
+ ScClipParam& rClipParam = GetClipParam();
+ rClipParam.maRanges.RemoveAll();
+ rClipParam.maRanges.push_back(rArea);
+ rClipParam.mbCutMode = bCut;
+ }
+ else
+ {
+ OSL_FAIL("SetClipArea: No Clip");
+ }
+}
+
+void ScDocument::GetClipArea(SCCOL& nClipX, SCROW& nClipY, bool bIncludeFiltered)
+{
+ if (!bIsClip)
+ {
+ OSL_FAIL("GetClipArea: No Clip");
+ return;
+ }
+
+ ScRangeList& rClipRanges = GetClipParam().maRanges;
+ if (rClipRanges.empty())
+ // No clip range. Bail out.
+ return;
+
+ ScRange const & rRange = rClipRanges.front();
+ SCCOL nStartCol = rRange.aStart.Col();
+ SCCOL nEndCol = rRange.aEnd.Col();
+ SCROW nStartRow = rRange.aStart.Row();
+ SCROW nEndRow = rRange.aEnd.Row();
+ for ( size_t i = 1, n = rClipRanges.size(); i < n; ++i )
+ {
+ ScRange const & rRange2 = rClipRanges[ i ];
+ if (rRange2.aStart.Col() < nStartCol)
+ nStartCol = rRange2.aStart.Col();
+ if (rRange2.aStart.Row() < nStartRow)
+ nStartRow = rRange2.aStart.Row();
+ if (rRange2.aEnd.Col() > nEndCol)
+ nEndCol = rRange2.aEnd.Col();
+ if (rRange2.aEnd.Row() > nEndRow)
+ nEndRow = rRange2.aEnd.Row();
+ }
+
+ nClipX = nEndCol - nStartCol;
+
+ if ( bIncludeFiltered )
+ nClipY = nEndRow - nStartRow;
+ else
+ {
+ // count non-filtered rows
+ // count on first used table in clipboard
+ SCTAB nCountTab = 0;
+ while ( nCountTab < static_cast<SCTAB>(maTabs.size()) && !maTabs[nCountTab] )
+ ++nCountTab;
+
+ SCROW nResult = CountNonFilteredRows(nStartRow, nEndRow, nCountTab);
+
+ if ( nResult > 0 )
+ nClipY = nResult - 1;
+ else
+ nClipY = 0; // always return at least 1 row
+ }
+}
+
+void ScDocument::GetClipStart(SCCOL& nClipX, SCROW& nClipY)
+{
+ if (bIsClip)
+ {
+ ScRangeList& rClipRanges = GetClipParam().maRanges;
+ if ( !rClipRanges.empty() )
+ {
+ nClipX = rClipRanges.front().aStart.Col();
+ nClipY = rClipRanges.front().aStart.Row();
+ }
+ }
+ else
+ {
+ OSL_FAIL("GetClipStart: No Clip");
+ }
+}
+
+bool ScDocument::HasClipFilteredRows()
+{
+ // count on first used table in clipboard
+ SCTAB nCountTab = 0;
+ while ( nCountTab < static_cast<SCTAB>(maTabs.size()) && !maTabs[nCountTab] )
+ ++nCountTab;
+
+ ScRangeList& rClipRanges = GetClipParam().maRanges;
+ if ( rClipRanges.empty() )
+ return false;
+
+ for ( size_t i = 0, n = rClipRanges.size(); i < n; ++i )
+ {
+ ScRange & rRange = rClipRanges[ i ];
+ bool bAnswer = maTabs[nCountTab]->HasFilteredRows(rRange.aStart.Row(), rRange.aEnd.Row());
+ if (bAnswer)
+ return true;
+ }
+ return false;
+}
+
+void ScDocument::MixDocument( const ScRange& rRange, ScPasteFunc nFunction, bool bSkipEmpty,
+ ScDocument& rSrcDoc )
+{
+ SCTAB nTab1 = rRange.aStart.Tab();
+ SCTAB nTab2 = rRange.aEnd.Tab();
+ sc::MixDocContext aCxt(*this);
+ SCTAB nMinSizeBothTabs = static_cast<SCTAB>(std::min(maTabs.size(), rSrcDoc.maTabs.size()));
+ for (SCTAB i = nTab1; i <= nTab2 && i < nMinSizeBothTabs; i++)
+ {
+ ScTable* pTab = FetchTable(i);
+ const ScTable* pSrcTab = rSrcDoc.FetchTable(i);
+ if (!pTab || !pSrcTab)
+ continue;
+
+ pTab->MixData(
+ aCxt, rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row(),
+ nFunction, bSkipEmpty, pSrcTab);
+ }
+}
+
+void ScDocument::FillTab( const ScRange& rSrcArea, const ScMarkData& rMark,
+ InsertDeleteFlags nFlags, ScPasteFunc nFunction,
+ bool bSkipEmpty, bool bAsLink )
+{
+ InsertDeleteFlags nDelFlags = nFlags;
+ if (nDelFlags & InsertDeleteFlags::CONTENTS)
+ nDelFlags |= InsertDeleteFlags::CONTENTS; // Either all contents or delete nothing!
+
+ SCTAB nSrcTab = rSrcArea.aStart.Tab();
+
+ if (ValidTab(nSrcTab) && nSrcTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nSrcTab])
+ {
+ SCCOL nStartCol = rSrcArea.aStart.Col();
+ SCROW nStartRow = rSrcArea.aStart.Row();
+ SCCOL nEndCol = rSrcArea.aEnd.Col();
+ SCROW nEndRow = rSrcArea.aEnd.Row();
+ ScDocumentUniquePtr pMixDoc;
+ bool bDoMix = ( bSkipEmpty || nFunction != ScPasteFunc::NONE ) && ( nFlags & InsertDeleteFlags::CONTENTS );
+
+ bool bOldAutoCalc = GetAutoCalc();
+ SetAutoCalc( false ); // avoid multiple calculations
+
+ sc::CopyToDocContext aCxt(*this);
+ sc::MixDocContext aMixDocCxt(*this);
+
+ SCTAB nCount = static_cast<SCTAB>(maTabs.size());
+ for (const SCTAB& i : rMark)
+ {
+ if (i >= nCount)
+ break;
+ if (i != nSrcTab && maTabs[i])
+ {
+ if (bDoMix)
+ {
+ if (!pMixDoc)
+ {
+ pMixDoc.reset(new ScDocument(SCDOCMODE_UNDO));
+ pMixDoc->InitUndo( *this, i, i );
+ }
+ else
+ pMixDoc->AddUndoTab( i, i );
+
+ // context used for copying content to the temporary mix document.
+ sc::CopyToDocContext aMixCxt(*pMixDoc);
+ maTabs[i]->CopyToTable(aMixCxt, nStartCol,nStartRow, nEndCol,nEndRow,
+ InsertDeleteFlags::CONTENTS, false, pMixDoc->maTabs[i].get(),
+ /*pMarkData*/nullptr, /*bAsLink*/false, /*bColRowFlags*/true,
+ /*bGlobalNamesToLocal*/false, /*bCopyCaptions*/true );
+ }
+ maTabs[i]->DeleteArea( nStartCol,nStartRow, nEndCol,nEndRow, nDelFlags);
+ maTabs[nSrcTab]->CopyToTable(aCxt, nStartCol,nStartRow, nEndCol,nEndRow,
+ nFlags, false, maTabs[i].get(), nullptr, bAsLink,
+ /*bColRowFlags*/true, /*bGlobalNamesToLocal*/false, /*bCopyCaptions*/true );
+
+ if (bDoMix)
+ maTabs[i]->MixData(aMixDocCxt, nStartCol,nStartRow, nEndCol,nEndRow,
+ nFunction, bSkipEmpty, pMixDoc->maTabs[i].get() );
+ }
+ }
+
+ SetAutoCalc( bOldAutoCalc );
+ }
+ else
+ {
+ OSL_FAIL("wrong table");
+ }
+}
+
+void ScDocument::FillTabMarked( SCTAB nSrcTab, const ScMarkData& rMark,
+ InsertDeleteFlags nFlags, ScPasteFunc nFunction,
+ bool bSkipEmpty, bool bAsLink )
+{
+ InsertDeleteFlags nDelFlags = nFlags;
+ if (nDelFlags & InsertDeleteFlags::CONTENTS)
+ nDelFlags |= InsertDeleteFlags::CONTENTS; // Either all contents or delete nothing!
+
+ if (ValidTab(nSrcTab) && nSrcTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nSrcTab])
+ {
+ ScDocumentUniquePtr pMixDoc;
+ bool bDoMix = ( bSkipEmpty || nFunction != ScPasteFunc::NONE ) && ( nFlags & InsertDeleteFlags::CONTENTS );
+
+ bool bOldAutoCalc = GetAutoCalc();
+ SetAutoCalc( false ); // avoid multiple calculations
+
+ const ScRange& aArea = rMark.GetMultiMarkArea();
+ SCCOL nStartCol = aArea.aStart.Col();
+ SCROW nStartRow = aArea.aStart.Row();
+ SCCOL nEndCol = aArea.aEnd.Col();
+ SCROW nEndRow = aArea.aEnd.Row();
+
+ sc::CopyToDocContext aCxt(*this);
+ sc::MixDocContext aMixDocCxt(*this);
+ SCTAB nCount = static_cast<SCTAB>(maTabs.size());
+ for (const SCTAB& i : rMark)
+ {
+ if (i >= nCount)
+ break;
+ if ( i != nSrcTab && maTabs[i] )
+ {
+ if (bDoMix)
+ {
+ if (!pMixDoc)
+ {
+ pMixDoc.reset(new ScDocument(SCDOCMODE_UNDO));
+ pMixDoc->InitUndo( *this, i, i );
+ }
+ else
+ pMixDoc->AddUndoTab( i, i );
+
+ sc::CopyToDocContext aMixCxt(*pMixDoc);
+ maTabs[i]->CopyToTable(aMixCxt, nStartCol,nStartRow, nEndCol,nEndRow,
+ InsertDeleteFlags::CONTENTS, true, pMixDoc->maTabs[i].get(), &rMark,
+ /*bAsLink*/false, /*bColRowFlags*/true, /*bGlobalNamesToLocal*/false,
+ /*bCopyCaptions*/true );
+ }
+
+ maTabs[i]->DeleteSelection( nDelFlags, rMark );
+ maTabs[nSrcTab]->CopyToTable(aCxt, nStartCol,nStartRow, nEndCol,nEndRow,
+ nFlags, true, maTabs[i].get(), &rMark, bAsLink,
+ /*bColRowFlags*/true, /*bGlobalNamesToLocal*/false, /*bCopyCaptions*/true );
+
+ if (bDoMix)
+ maTabs[i]->MixMarked(aMixDocCxt, rMark, nFunction, bSkipEmpty, pMixDoc->maTabs[i].get());
+ }
+ }
+
+ SetAutoCalc( bOldAutoCalc );
+ }
+ else
+ {
+ OSL_FAIL("wrong table");
+ }
+}
+
+bool ScDocument::SetString( SCCOL nCol, SCROW nRow, SCTAB nTab, const OUString& rString,
+ const ScSetStringParam* pParam )
+{
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return false;
+
+ const ScFormulaCell* pCurCellFormula = pTab->GetFormulaCell(nCol, nRow);
+ if (pCurCellFormula && pCurCellFormula->IsShared())
+ {
+ // In case setting this string affects an existing formula group, end
+ // its listening to purge then empty cell broadcasters. Affected
+ // remaining split group listeners will be set up again via
+ // ScColumn::DetachFormulaCell() and
+ // ScColumn::StartListeningUnshared().
+
+ sc::EndListeningContext aCxt(*this);
+ ScAddress aPos(nCol, nRow, nTab);
+ EndListeningIntersectedGroup(aCxt, aPos, nullptr);
+ aCxt.purgeEmptyBroadcasters();
+ }
+
+ return pTab->SetString(nCol, nRow, nTab, rString, pParam);
+}
+
+bool ScDocument::SetString(
+ const ScAddress& rPos, const OUString& rString, const ScSetStringParam* pParam )
+{
+ return SetString(rPos.Col(), rPos.Row(), rPos.Tab(), rString, pParam);
+}
+
+bool ScDocument::SetEditText( const ScAddress& rPos, std::unique_ptr<EditTextObject> pEditText )
+{
+ if (!TableExists(rPos.Tab()))
+ {
+ return false;
+ }
+
+ return maTabs[rPos.Tab()]->SetEditText(rPos.Col(), rPos.Row(), std::move(pEditText));
+}
+
+void ScDocument::SetEditText( const ScAddress& rPos, const EditTextObject& rEditText, const SfxItemPool* pEditPool )
+{
+ if (!TableExists(rPos.Tab()))
+ return;
+
+ maTabs[rPos.Tab()]->SetEditText(rPos.Col(), rPos.Row(), rEditText, pEditPool);
+}
+
+void ScDocument::SetEditText( const ScAddress& rPos, const OUString& rStr )
+{
+ if (!TableExists(rPos.Tab()))
+ return;
+
+ ScFieldEditEngine& rEngine = GetEditEngine();
+ rEngine.SetTextCurrentDefaults(rStr);
+ maTabs[rPos.Tab()]->SetEditText(rPos.Col(), rPos.Row(), rEngine.CreateTextObject());
+}
+
+SCROW ScDocument::GetFirstEditTextRow( const ScRange& rRange ) const
+{
+ const ScTable* pTab = FetchTable(rRange.aStart.Tab());
+ if (!pTab)
+ return -1;
+
+ return pTab->GetFirstEditTextRow(rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row());
+}
+
+void ScDocument::SetTextCell( const ScAddress& rPos, const OUString& rStr )
+{
+ if (!TableExists(rPos.Tab()))
+ return;
+
+ if (ScStringUtil::isMultiline(rStr))
+ {
+ ScFieldEditEngine& rEngine = GetEditEngine();
+ rEngine.SetTextCurrentDefaults(rStr);
+ maTabs[rPos.Tab()]->SetEditText(rPos.Col(), rPos.Row(), rEngine.CreateTextObject());
+ }
+ else
+ {
+ ScSetStringParam aParam;
+ aParam.setTextInput();
+ maTabs[rPos.Tab()]->SetString(rPos.Col(), rPos.Row(), rPos.Tab(), rStr, &aParam);
+ }
+}
+
+void ScDocument::SetEmptyCell( const ScAddress& rPos )
+{
+ if (!TableExists(rPos.Tab()))
+ return;
+
+ maTabs[rPos.Tab()]->SetEmptyCell(rPos.Col(), rPos.Row());
+}
+
+void ScDocument::SetValue( SCCOL nCol, SCROW nRow, SCTAB nTab, const double& rVal )
+{
+ SetValue(ScAddress(nCol, nRow, nTab), rVal);
+}
+
+void ScDocument::SetValue( const ScAddress& rPos, double fVal )
+{
+ ScTable* pTab = FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ const ScFormulaCell* pCurCellFormula = pTab->GetFormulaCell(rPos.Col(), rPos.Row());
+ if (pCurCellFormula && pCurCellFormula->IsShared())
+ {
+ // In case setting this value affects an existing formula group, end
+ // its listening to purge then empty cell broadcasters. Affected
+ // remaining split group listeners will be set up again via
+ // ScColumn::DetachFormulaCell() and
+ // ScColumn::StartListeningUnshared().
+
+ sc::EndListeningContext aCxt(*this);
+ EndListeningIntersectedGroup(aCxt, rPos, nullptr);
+ aCxt.purgeEmptyBroadcasters();
+ }
+
+ pTab->SetValue(rPos.Col(), rPos.Row(), fVal);
+}
+
+OUString ScDocument::GetString( SCCOL nCol, SCROW nRow, SCTAB nTab, const ScInterpreterContext* pContext ) const
+{
+ if (TableExists(nTab))
+ return maTabs[nTab]->GetString(nCol, nRow, pContext);
+ return OUString();
+}
+
+OUString ScDocument::GetString( const ScAddress& rPos, const ScInterpreterContext* pContext ) const
+{
+ if (!TableExists(rPos.Tab()))
+ return OUString();
+
+ return maTabs[rPos.Tab()]->GetString(rPos.Col(), rPos.Row(), pContext);
+}
+
+double* ScDocument::GetValueCell( const ScAddress& rPos )
+{
+ if (!TableExists(rPos.Tab()))
+ return nullptr;
+
+ return maTabs[rPos.Tab()]->GetValueCell(rPos.Col(), rPos.Row());
+}
+
+svl::SharedString ScDocument::GetSharedString( const ScAddress& rPos ) const
+{
+ if (!TableExists(rPos.Tab()))
+ return svl::SharedString();
+
+ return maTabs[rPos.Tab()]->GetSharedString(rPos.Col(), rPos.Row());
+}
+
+std::shared_ptr<sc::FormulaGroupContext>& ScDocument::GetFormulaGroupContext()
+{
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ if (!mpFormulaGroupCxt)
+ mpFormulaGroupCxt = std::make_shared<sc::FormulaGroupContext>();
+
+ return mpFormulaGroupCxt;
+}
+
+void ScDocument::DiscardFormulaGroupContext()
+{
+ assert(!IsThreadedGroupCalcInProgress());
+ if( !mbFormulaGroupCxtBlockDiscard )
+ mpFormulaGroupCxt.reset();
+}
+
+OUString ScDocument::GetInputString(SCCOL nCol, SCROW nRow, SCTAB nTab, bool bForceSystemLocale ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetInputString( nCol, nRow, nullptr, bForceSystemLocale );
+ else
+ return OUString();
+}
+
+FormulaError ScDocument::GetStringForFormula( const ScAddress& rPos, OUString& rString )
+{
+ // Used in formulas (add-in parameters etc), so it must use the same semantics as
+ // ScInterpreter::GetCellString: always format values as numbers.
+ // The return value is the error code.
+
+ ScRefCellValue aCell(*this, rPos);
+ if (aCell.isEmpty())
+ {
+ rString.clear();
+ return FormulaError::NONE;
+ }
+
+ FormulaError nErr = FormulaError::NONE;
+ OUString aStr;
+ SvNumberFormatter* pFormatter = GetFormatTable();
+ switch (aCell.meType)
+ {
+ case CELLTYPE_STRING:
+ case CELLTYPE_EDIT:
+ aStr = aCell.getString(this);
+ break;
+ case CELLTYPE_FORMULA:
+ {
+ ScFormulaCell* pFCell = aCell.mpFormula;
+ nErr = pFCell->GetErrCode();
+ if (pFCell->IsValue())
+ {
+ double fVal = pFCell->GetValue();
+ sal_uInt32 nIndex = pFormatter->GetStandardFormat(
+ SvNumFormatType::NUMBER,
+ ScGlobal::eLnge);
+ pFormatter->GetInputLineString(fVal, nIndex, aStr);
+ }
+ else
+ aStr = pFCell->GetString().getString();
+ }
+ break;
+ case CELLTYPE_VALUE:
+ {
+ double fVal = aCell.mfValue;
+ sal_uInt32 nIndex = pFormatter->GetStandardFormat(
+ SvNumFormatType::NUMBER,
+ ScGlobal::eLnge);
+ pFormatter->GetInputLineString(fVal, nIndex, aStr);
+ }
+ break;
+ default:
+ ;
+ }
+
+ rString = aStr;
+ return nErr;
+}
+
+const EditTextObject* ScDocument::GetEditText( const ScAddress& rPos ) const
+{
+ SCTAB nTab = rPos.Tab();
+ if (!TableExists(nTab))
+ return nullptr;
+
+ return maTabs[nTab]->GetEditText(rPos.Col(), rPos.Row());
+}
+
+void ScDocument::RemoveEditTextCharAttribs( const ScAddress& rPos, const ScPatternAttr& rAttr )
+{
+ if (!TableExists(rPos.Tab()))
+ return;
+
+ return maTabs[rPos.Tab()]->RemoveEditTextCharAttribs(rPos.Col(), rPos.Row(), rAttr);
+}
+
+double ScDocument::GetValue( const ScAddress& rPos ) const
+{
+ SCTAB nTab = rPos.Tab();
+ if ( nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetValue(rPos.Col(), rPos.Row());
+ return 0.0;
+}
+
+double ScDocument::GetValue( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
+{
+ ScAddress aAdr(nCol, nRow, nTab);
+ return GetValue(aAdr);
+}
+
+sal_uInt32 ScDocument::GetNumberFormat( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ if (maTabs[nTab])
+ {
+ return maTabs[nTab]->GetNumberFormat( nCol, nRow );
+ }
+ return 0;
+}
+
+sal_uInt32 ScDocument::GetNumberFormat( const ScRange& rRange ) const
+{
+ SCTAB nTab1 = rRange.aStart.Tab(), nTab2 = rRange.aEnd.Tab();
+ SCCOL nCol1 = rRange.aStart.Col(), nCol2 = rRange.aEnd.Col();
+ SCROW nRow1 = rRange.aStart.Row(), nRow2 = rRange.aEnd.Row();
+
+ if (!TableExists(nTab1) || !TableExists(nTab2))
+ return 0;
+
+ sal_uInt32 nFormat = 0;
+ bool bFirstItem = true;
+ for (SCTAB nTab = nTab1; nTab <= nTab2 && nTab < static_cast<SCTAB>(maTabs.size()) ; ++nTab)
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ {
+ sal_uInt32 nThisFormat = maTabs[nTab]->GetNumberFormat(nCol, nRow1, nRow2);
+ if (bFirstItem)
+ {
+ nFormat = nThisFormat;
+ bFirstItem = false;
+ }
+ else if (nThisFormat != nFormat)
+ return 0;
+ }
+
+ return nFormat;
+}
+
+sal_uInt32 ScDocument::GetNumberFormat( const ScInterpreterContext& rContext, const ScAddress& rPos ) const
+{
+ SCTAB nTab = rPos.Tab();
+ if (!TableExists(nTab))
+ return 0;
+
+ return maTabs[nTab]->GetNumberFormat( rContext, rPos );
+}
+
+void ScDocument::SetNumberFormat( const ScAddress& rPos, sal_uInt32 nNumberFormat )
+{
+ assert(!IsThreadedGroupCalcInProgress());
+ SCTAB nTab = rPos.Tab();
+ if (!TableExists(nTab))
+ return;
+
+ maTabs[nTab]->SetNumberFormat(rPos.Col(), rPos.Row(), nNumberFormat);
+}
+
+void ScDocument::GetNumberFormatInfo( const ScInterpreterContext& rContext, SvNumFormatType& nType, sal_uInt32& nIndex,
+ const ScAddress& rPos ) const
+{
+ SCTAB nTab = rPos.Tab();
+ if ( nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ {
+ nIndex = maTabs[nTab]->GetNumberFormat( rContext, rPos );
+ nType = rContext.GetNumberFormatType( nIndex );
+ }
+ else
+ {
+ nType = SvNumFormatType::UNDEFINED;
+ nIndex = 0;
+ }
+}
+
+OUString ScDocument::GetFormula( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetFormula( nCol, nRow );
+
+ return OUString();
+}
+
+const ScFormulaCell* ScDocument::GetFormulaCell( const ScAddress& rPos ) const
+{
+ if (!TableExists(rPos.Tab()))
+ return nullptr;
+
+ return maTabs[rPos.Tab()]->GetFormulaCell(rPos.Col(), rPos.Row());
+}
+
+ScFormulaCell* ScDocument::GetFormulaCell( const ScAddress& rPos )
+{
+ if (!TableExists(rPos.Tab()))
+ return nullptr;
+
+ return maTabs[rPos.Tab()]->GetFormulaCell(rPos.Col(), rPos.Row());
+}
+
+CellType ScDocument::GetCellType( const ScAddress& rPos ) const
+{
+ SCTAB nTab = rPos.Tab();
+ if ( nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetCellType( rPos );
+ return CELLTYPE_NONE;
+}
+
+CellType ScDocument::GetCellType( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetCellType( nCol, nRow );
+
+ return CELLTYPE_NONE;
+}
+
+bool ScDocument::HasStringData( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab]
+ && nCol < maTabs[nTab]->GetAllocatedColumnsCount())
+ return maTabs[nTab]->HasStringData( nCol, nRow );
+ else
+ return false;
+}
+
+bool ScDocument::HasValueData( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab]
+ && nCol < maTabs[nTab]->GetAllocatedColumnsCount())
+ return maTabs[nTab]->HasValueData( nCol, nRow );
+ else
+ return false;
+}
+
+bool ScDocument::HasValueData( const ScAddress& rPos ) const
+{
+ return HasValueData(rPos.Col(), rPos.Row(), rPos.Tab());
+}
+
+bool ScDocument::HasStringCells( const ScRange& rRange ) const
+{
+ // true, if String- or Edit cells in range
+
+ SCCOL nStartCol = rRange.aStart.Col();
+ SCROW nStartRow = rRange.aStart.Row();
+ SCTAB nStartTab = rRange.aStart.Tab();
+ SCCOL nEndCol = rRange.aEnd.Col();
+ SCROW nEndRow = rRange.aEnd.Row();
+ SCTAB nEndTab = rRange.aEnd.Tab();
+
+ for ( SCTAB nTab=nStartTab; nTab<=nEndTab && nTab < static_cast<SCTAB>(maTabs.size()); nTab++ )
+ if ( maTabs[nTab] && maTabs[nTab]->HasStringCells( nStartCol, nStartRow, nEndCol, nEndRow ) )
+ return true;
+
+ return false;
+}
+
+bool ScDocument::HasSelectionData( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
+{
+ sal_uInt32 nValidation = GetAttr( nCol, nRow, nTab, ATTR_VALIDDATA )->GetValue();
+ if( nValidation )
+ {
+ const ScValidationData* pData = GetValidationEntry( nValidation );
+ if( pData && pData->HasSelectionList() )
+ return true;
+ }
+ return HasStringCells( ScRange( nCol, 0, nTab, nCol, MaxRow(), nTab ) );
+}
+
+bool ScDocument::HasValidationData( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
+{
+ sal_uInt32 nValidation = GetAttr( nCol, nRow, nTab, ATTR_VALIDDATA )->GetValue();
+ if( nValidation )
+ {
+ const ScValidationData* pData = GetValidationEntry( nValidation );
+ if( pData && pData->GetDataMode() != ScValidationMode::SC_VALID_ANY )
+ return true;
+ }
+ return false;
+}
+
+void ScDocument::CheckVectorizationState()
+{
+ bool bOldAutoCalc = GetAutoCalc();
+ bAutoCalc = false; // no multiple calculations
+
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->CheckVectorizationState();
+ }
+
+ SetAutoCalc(bOldAutoCalc);
+}
+
+void ScDocument::SetAllFormulasDirty( const sc::SetFormulaDirtyContext& rCxt )
+{
+ bool bOldAutoCalc = GetAutoCalc();
+ bAutoCalc = false; // no multiple calculations
+ { // scope for bulk broadcast
+ ScBulkBroadcast aBulkBroadcast( GetBASM(), SfxHintId::ScDataChanged);
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->SetAllFormulasDirty(rCxt);
+ }
+ }
+
+ // Although Charts are also set to dirty in Tracking without AutoCalc
+ // if all formulas are dirty, the charts can no longer be caught
+ // (#45205#) - that is why all Charts have to be explicitly handled again
+ if (pChartListenerCollection)
+ pChartListenerCollection->SetDirty();
+
+ SetAutoCalc( bOldAutoCalc );
+}
+
+void ScDocument::SetDirty( const ScRange& rRange, bool bIncludeEmptyCells )
+{
+ bool bOldAutoCalc = GetAutoCalc();
+ bAutoCalc = false; // no multiple calculations
+ { // scope for bulk broadcast
+ ScBulkBroadcast aBulkBroadcast( GetBASM(), SfxHintId::ScDataChanged);
+ SCTAB nTab2 = rRange.aEnd.Tab();
+ for (SCTAB i=rRange.aStart.Tab(); i<=nTab2 && i < static_cast<SCTAB>(maTabs.size()); i++)
+ if (maTabs[i]) maTabs[i]->SetDirty( rRange,
+ (bIncludeEmptyCells ? ScColumn::BROADCAST_BROADCASTERS : ScColumn::BROADCAST_DATA_POSITIONS));
+
+ /* TODO: this now also notifies conditional formatting and does a UNO
+ * broadcast, which wasn't done here before. Is that an actually
+ * desired side effect, or should we come up with a method that
+ * doesn't? */
+ if (bIncludeEmptyCells)
+ BroadcastCells( rRange, SfxHintId::ScDataChanged, false);
+ }
+ SetAutoCalc( bOldAutoCalc );
+}
+
+void ScDocument::SetTableOpDirty( const ScRange& rRange )
+{
+ bool bOldAutoCalc = GetAutoCalc();
+ bAutoCalc = false; // no multiple recalculation
+ SCTAB nTab2 = rRange.aEnd.Tab();
+ for (SCTAB i=rRange.aStart.Tab(); i<=nTab2 && i < static_cast<SCTAB>(maTabs.size()); i++)
+ if (maTabs[i]) maTabs[i]->SetTableOpDirty( rRange );
+ SetAutoCalc( bOldAutoCalc );
+}
+
+void ScDocument::InterpretDirtyCells( const ScRangeList& rRanges )
+{
+ if (!GetAutoCalc())
+ return;
+
+ PrepareFormulaCalc();
+
+ for (size_t nPos=0, nRangeCount = rRanges.size(); nPos < nRangeCount; nPos++)
+ {
+ const ScRange& rRange = rRanges[nPos];
+ for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab)
+ {
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ pTab->InterpretDirtyCells(
+ rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row());
+ }
+ }
+
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ mpFormulaGroupCxt.reset();
+}
+
+bool ScDocument::InterpretCellsIfNeeded( const ScRangeList& rRanges )
+{
+ bool allInterpreted = true;
+ for (size_t nPos=0, nRangeCount = rRanges.size(); nPos < nRangeCount; nPos++)
+ {
+ const ScRange& rRange = rRanges[nPos];
+ for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab)
+ {
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ break;
+
+ if( !pTab->InterpretCellsIfNeeded(
+ rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row()))
+ {
+ allInterpreted = false;
+ }
+ }
+ }
+ return allInterpreted;
+}
+
+void ScDocument::AddTableOpFormulaCell( ScFormulaCell* pCell )
+{
+ if (m_TableOpList.empty())
+ return;
+
+ ScInterpreterTableOpParams *const p = m_TableOpList.back();
+ if ( p->bCollectNotifications )
+ {
+ if ( p->bRefresh )
+ { // refresh pointers only
+ p->aNotifiedFormulaCells.push_back( pCell );
+ }
+ else
+ { // init both, address and pointer
+ p->aNotifiedFormulaCells.push_back( pCell );
+ p->aNotifiedFormulaPos.push_back( pCell->aPos );
+ }
+ }
+}
+
+void ScDocument::CalcAll()
+{
+ PrepareFormulaCalc();
+ ClearLookupCaches(); // Ensure we don't deliver zombie data.
+ sc::AutoCalcSwitch aSwitch(*this, true);
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->SetDirtyVar();
+ }
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->CalcAll();
+ }
+ ClearFormulaTree();
+
+ // In eternal hard recalc state caches were not added as listeners,
+ // invalidate them so the next non-CalcAll() normal lookup will not be
+ // presented with outdated data.
+ if (GetHardRecalcState() == HardRecalcState::ETERNAL)
+ ClearLookupCaches();
+}
+
+void ScDocument::CompileAll()
+{
+ sc::CompileFormulaContext aCxt(*this);
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->CompileAll(aCxt);
+ }
+
+ sc::SetFormulaDirtyContext aFormulaDirtyCxt;
+ SetAllFormulasDirty(aFormulaDirtyCxt);
+}
+
+void ScDocument::CompileXML()
+{
+ bool bOldAutoCalc = GetAutoCalc();
+ SetAutoCalc( false );
+ ScProgress aProgress( GetDocumentShell(), ScResId(
+ STR_PROGRESS_CALCULATING ), GetXMLImportedFormulaCount(), true );
+
+ sc::CompileFormulaContext aCxt(*this);
+
+ // set AutoNameCache to speed up automatic name lookup
+ OSL_ENSURE( !pAutoNameCache, "AutoNameCache already set" );
+ pAutoNameCache.reset( new ScAutoNameCache( *this ) );
+
+ if (pRangeName)
+ pRangeName->CompileUnresolvedXML(aCxt);
+
+ std::for_each(maTabs.begin(), maTabs.end(),
+ [&](ScTableUniquePtr & pTab)
+ {
+ if (pTab)
+ pTab->CompileXML(aCxt, aProgress);
+ }
+ );
+ StartAllListeners();
+
+ pAutoNameCache.reset(); // valid only during CompileXML, where cell contents don't change
+
+ if ( pValidationList )
+ {
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ pValidationList->CompileXML();
+ }
+
+ // Track all formula cells that were appended to the FormulaTrack during
+ // import or CompileXML().
+ TrackFormulas();
+
+ SetAutoCalc( bOldAutoCalc );
+}
+
+bool ScDocument::CompileErrorCells(FormulaError nErrCode)
+{
+ bool bCompiled = false;
+ sc::CompileFormulaContext aCxt(*this);
+ for (const auto& a : maTabs)
+ {
+ if (!a)
+ continue;
+
+ if (a->CompileErrorCells(aCxt, nErrCode))
+ bCompiled = true;
+ }
+
+ return bCompiled;
+}
+
+void ScDocument::CalcAfterLoad( bool bStartListening )
+{
+ if (bIsClip) // Excel data is loaded from the Clipboard to a Clip-Doc
+ return; // the calculation is then only performed when inserting into the real document
+
+ bCalcingAfterLoad = true;
+ sc::CompileFormulaContext aCxt(*this);
+ {
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->CalcAfterLoad(aCxt, bStartListening);
+ }
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->SetDirtyAfterLoad();
+ }
+ }
+ bCalcingAfterLoad = false;
+
+ SetDetectiveDirty(false); // No real changes yet
+
+ // #i112436# If formula cells are already dirty, they don't broadcast further changes.
+ // So the source ranges of charts must be interpreted even if they are not visible,
+ // similar to ScMyShapeResizer::CreateChartListener for loading own files (i104899).
+ if (pChartListenerCollection)
+ {
+ const ScChartListenerCollection::ListenersType& rListeners = pChartListenerCollection->getListeners();
+ for (auto const& it : rListeners)
+ {
+ const ScChartListener *const p = it.second.get();
+ InterpretDirtyCells(*p->GetRangeList());
+ }
+ }
+}
+
+FormulaError ScDocument::GetErrCode( const ScAddress& rPos ) const
+{
+ SCTAB nTab = rPos.Tab();
+ if ( nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetErrCode( rPos );
+ return FormulaError::NONE;
+}
+
+void ScDocument::ResetChanged( const ScRange& rRange )
+{
+ SCTAB nTabSize = static_cast<SCTAB>(maTabs.size());
+ SCTAB nTab1 = rRange.aStart.Tab();
+ SCTAB nTab2 = rRange.aEnd.Tab();
+ for (SCTAB nTab = nTab1; nTab1 <= nTab2 && nTab < nTabSize; ++nTab)
+ if (maTabs[nTab])
+ maTabs[nTab]->ResetChanged(rRange);
+}
+
+// Column widths / Row heights --------------------------------------
+
+void ScDocument::SetColWidth( SCCOL nCol, SCTAB nTab, sal_uInt16 nNewWidth )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->SetColWidth( nCol, nNewWidth );
+}
+
+void ScDocument::SetColWidthOnly( SCCOL nCol, SCTAB nTab, sal_uInt16 nNewWidth )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->SetColWidthOnly( nCol, nNewWidth );
+}
+
+void ScDocument::SetRowHeight( SCROW nRow, SCTAB nTab, sal_uInt16 nNewHeight )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->SetRowHeight( nRow, nNewHeight );
+}
+
+void ScDocument::SetRowHeightRange( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, sal_uInt16 nNewHeight )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->SetRowHeightRange
+ ( nStartRow, nEndRow, nNewHeight, 1.0, true );
+}
+
+void ScDocument::SetRowHeightOnly( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, sal_uInt16 nNewHeight )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->SetRowHeightOnly( nStartRow, nEndRow, nNewHeight );
+}
+
+void ScDocument::SetManualHeight( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bManual )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->SetManualHeight( nStartRow, nEndRow, bManual );
+}
+
+sal_uInt16 ScDocument::GetColWidth( SCCOL nCol, SCTAB nTab, bool bHiddenAsZero ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetColWidth( nCol, bHiddenAsZero );
+ OSL_FAIL("wrong table number");
+ return 0;
+}
+
+tools::Long ScDocument::GetColWidth( SCCOL nStartCol, SCCOL nEndCol, SCTAB nTab ) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return 0;
+
+ return pTab->GetColWidth(nStartCol, nEndCol);
+}
+
+sal_uInt16 ScDocument::GetOriginalWidth( SCCOL nCol, SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetOriginalWidth( nCol );
+ OSL_FAIL("wrong table number");
+ return 0;
+}
+
+sal_uInt16 ScDocument::GetCommonWidth( SCCOL nEndCol, SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetCommonWidth( nEndCol );
+ OSL_FAIL("Wrong table number");
+ return 0;
+}
+
+sal_uInt16 ScDocument::GetOriginalHeight( SCROW nRow, SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetOriginalHeight( nRow );
+ OSL_FAIL("Wrong table number");
+ return 0;
+}
+
+sal_uInt16 ScDocument::GetRowHeight( SCROW nRow, SCTAB nTab, bool bHiddenAsZero ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetRowHeight( nRow, nullptr, nullptr, bHiddenAsZero );
+ OSL_FAIL("Wrong sheet number");
+ return 0;
+}
+
+sal_uInt16 ScDocument::GetRowHeight( SCROW nRow, SCTAB nTab, SCROW* pStartRow, SCROW* pEndRow, bool bHiddenAsZero ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetRowHeight( nRow, pStartRow, pEndRow, bHiddenAsZero );
+ OSL_FAIL("Wrong sheet number");
+ return 0;
+}
+
+tools::Long ScDocument::GetRowHeight( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bHiddenAsZero ) const
+{
+ if (nStartRow == nEndRow)
+ return GetRowHeight( nStartRow, nTab, bHiddenAsZero ); // faster for a single row
+
+ // check bounds because this method replaces former for(i=start;i<=end;++i) loops
+ if (nStartRow > nEndRow)
+ return 0;
+
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetRowHeight( nStartRow, nEndRow, bHiddenAsZero );
+
+ OSL_FAIL("wrong sheet number");
+ return 0;
+}
+
+SCROW ScDocument::GetRowForHeight( SCTAB nTab, tools::Long nHeight ) const
+{
+ return maTabs[nTab]->GetRowForHeight(nHeight);
+}
+
+tools::Long ScDocument::GetScaledRowHeight( SCROW nStartRow, SCROW nEndRow,
+ SCTAB nTab, double fScale ) const
+{
+ // faster for a single row
+ if (nStartRow == nEndRow)
+ return static_cast<tools::Long>(GetRowHeight( nStartRow, nTab) * fScale);
+
+ // check bounds because this method replaces former for(i=start;i<=end;++i) loops
+ if (nStartRow > nEndRow)
+ return 0;
+
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetScaledRowHeight( nStartRow, nEndRow, fScale);
+
+ OSL_FAIL("wrong sheet number");
+ return 0;
+}
+
+SCROW ScDocument::GetHiddenRowCount( SCROW nRow, SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetHiddenRowCount( nRow );
+ OSL_FAIL("wrong table number");
+ return 0;
+}
+
+tools::Long ScDocument::GetColOffset( SCCOL nCol, SCTAB nTab, bool bHiddenAsZero ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetColOffset( nCol, bHiddenAsZero );
+ OSL_FAIL("wrong table number");
+ return 0;
+}
+
+tools::Long ScDocument::GetRowOffset( SCROW nRow, SCTAB nTab, bool bHiddenAsZero ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetRowOffset( nRow, bHiddenAsZero );
+ OSL_FAIL("wrong table number");
+ return 0;
+}
+
+sal_uInt16 ScDocument::GetOptimalColWidth( SCCOL nCol, SCTAB nTab, OutputDevice* pDev,
+ double nPPTX, double nPPTY,
+ const Fraction& rZoomX, const Fraction& rZoomY,
+ bool bFormula, const ScMarkData* pMarkData,
+ const ScColWidthParam* pParam )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetOptimalColWidth( nCol, pDev, nPPTX, nPPTY,
+ rZoomX, rZoomY, bFormula, pMarkData, pParam );
+ OSL_FAIL("wrong table number");
+ return 0;
+}
+
+tools::Long ScDocument::GetNeededSize( SCCOL nCol, SCROW nRow, SCTAB nTab,
+ OutputDevice* pDev,
+ double nPPTX, double nPPTY,
+ const Fraction& rZoomX, const Fraction& rZoomY,
+ bool bWidth, bool bTotalSize, bool bInPrintTwips )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetNeededSize
+ ( nCol, nRow, pDev, nPPTX, nPPTY, rZoomX, rZoomY, bWidth, bTotalSize, bInPrintTwips );
+ OSL_FAIL("wrong table number");
+ return 0;
+}
+
+bool ScDocument::SetOptimalHeight( sc::RowHeightContext& rCxt, SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bApi )
+{
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return false;
+
+ return pTab->SetOptimalHeight(rCxt, nStartRow, nEndRow, bApi);
+}
+
+void ScDocument::UpdateAllRowHeights( sc::RowHeightContext& rCxt, const ScMarkData* pTabMark )
+{
+ // one progress across all (selected) sheets
+
+ sal_uInt64 nCellCount = 0;
+ for ( SCTAB nTab=0; nTab< static_cast<SCTAB>(maTabs.size()); nTab++ )
+ if ( maTabs[nTab] && ( !pTabMark || pTabMark->GetTableSelect(nTab) ) )
+ nCellCount += maTabs[nTab]->GetWeightedCount();
+
+ ScProgress aProgress( GetDocumentShell(), ScResId(STR_PROGRESS_HEIGHTING), nCellCount, true );
+
+ sal_uInt64 nProgressStart = 0;
+ for ( SCTAB nTab=0; nTab< static_cast<SCTAB>(maTabs.size()); nTab++ )
+ if ( maTabs[nTab] && ( !pTabMark || pTabMark->GetTableSelect(nTab) ) )
+ {
+ maTabs[nTab]->SetOptimalHeightOnly(rCxt, 0, MaxRow(), &aProgress, nProgressStart);
+ maTabs[nTab]->SetDrawPageSize();
+ nProgressStart += maTabs[nTab]->GetWeightedCount();
+ }
+}
+
+// Column/Row - Flags ----------------------------------------------
+
+void ScDocument::ShowCol(SCCOL nCol, SCTAB nTab, bool bShow)
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->ShowCol( nCol, bShow );
+}
+
+void ScDocument::ShowRow(SCROW nRow, SCTAB nTab, bool bShow)
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->ShowRow( nRow, bShow );
+}
+
+void ScDocument::ShowRows(SCROW nRow1, SCROW nRow2, SCTAB nTab, bool bShow)
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->ShowRows( nRow1, nRow2, bShow );
+}
+
+void ScDocument::SetRowFlags( SCROW nRow, SCTAB nTab, CRFlags nNewFlags )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->SetRowFlags( nRow, nNewFlags );
+}
+
+void ScDocument::SetRowFlags( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, CRFlags nNewFlags )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->SetRowFlags( nStartRow, nEndRow, nNewFlags );
+}
+
+CRFlags ScDocument::GetColFlags( SCCOL nCol, SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetColFlags( nCol );
+ OSL_FAIL("wrong table number");
+ return CRFlags::NONE;
+}
+
+CRFlags ScDocument::GetRowFlags( SCROW nRow, SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetRowFlags( nRow );
+ OSL_FAIL("wrong table number");
+ return CRFlags::NONE;
+}
+
+void ScDocument::GetAllRowBreaks(set<SCROW>& rBreaks, SCTAB nTab, bool bPage, bool bManual) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return;
+ maTabs[nTab]->GetAllRowBreaks(rBreaks, bPage, bManual);
+}
+
+void ScDocument::GetAllColBreaks(set<SCCOL>& rBreaks, SCTAB nTab, bool bPage, bool bManual) const
+{
+ if (!ValidTab(nTab) || !maTabs[nTab])
+ return;
+
+ maTabs[nTab]->GetAllColBreaks(rBreaks, bPage, bManual);
+}
+
+ScBreakType ScDocument::HasRowBreak(SCROW nRow, SCTAB nTab) const
+{
+ ScBreakType nType = ScBreakType::NONE;
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab] || !ValidRow(nRow))
+ return nType;
+
+ if (maTabs[nTab]->HasRowPageBreak(nRow))
+ nType |= ScBreakType::Page;
+
+ if (maTabs[nTab]->HasRowManualBreak(nRow))
+ nType |= ScBreakType::Manual;
+
+ return nType;
+}
+
+ScBreakType ScDocument::HasColBreak(SCCOL nCol, SCTAB nTab) const
+{
+ ScBreakType nType = ScBreakType::NONE;
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab] || !ValidCol(nCol))
+ return nType;
+
+ if (maTabs[nTab]->HasColPageBreak(nCol))
+ nType |= ScBreakType::Page;
+
+ if (maTabs[nTab]->HasColManualBreak(nCol))
+ nType |= ScBreakType::Manual;
+
+ return nType;
+}
+
+void ScDocument::SetRowBreak(SCROW nRow, SCTAB nTab, bool bPage, bool bManual)
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab] || !ValidRow(nRow))
+ return;
+
+ maTabs[nTab]->SetRowBreak(nRow, bPage, bManual);
+}
+
+void ScDocument::SetColBreak(SCCOL nCol, SCTAB nTab, bool bPage, bool bManual)
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab] || !ValidCol(nCol))
+ return;
+
+ maTabs[nTab]->SetColBreak(nCol, bPage, bManual);
+}
+
+void ScDocument::RemoveRowBreak(SCROW nRow, SCTAB nTab, bool bPage, bool bManual)
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab] || !ValidRow(nRow))
+ return;
+
+ maTabs[nTab]->RemoveRowBreak(nRow, bPage, bManual);
+}
+
+void ScDocument::RemoveColBreak(SCCOL nCol, SCTAB nTab, bool bPage, bool bManual)
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab] || !ValidCol(nCol))
+ return;
+
+ maTabs[nTab]->RemoveColBreak(nCol, bPage, bManual);
+}
+
+Sequence<TablePageBreakData> ScDocument::GetRowBreakData(SCTAB nTab) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return Sequence<TablePageBreakData>();
+
+ return maTabs[nTab]->GetRowBreakData();
+}
+
+bool ScDocument::RowHidden(SCROW nRow, SCTAB nTab, SCROW* pFirstRow, SCROW* pLastRow) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return false;
+
+ return maTabs[nTab]->RowHidden(nRow, pFirstRow, pLastRow);
+}
+
+bool ScDocument::HasHiddenRows(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return false;
+
+ return maTabs[nTab]->HasHiddenRows(nStartRow, nEndRow);
+}
+
+bool ScDocument::ColHidden(SCCOL nCol, SCTAB nTab, SCCOL* pFirstCol, SCCOL* pLastCol) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ {
+ if (pFirstCol)
+ *pFirstCol = nCol;
+ if (pLastCol)
+ *pLastCol = nCol;
+ return false;
+ }
+
+ return maTabs[nTab]->ColHidden(nCol, pFirstCol, pLastCol);
+}
+
+void ScDocument::SetRowHidden(SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bHidden)
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return;
+
+ maTabs[nTab]->SetRowHidden(nStartRow, nEndRow, bHidden);
+}
+
+void ScDocument::SetColHidden(SCCOL nStartCol, SCCOL nEndCol, SCTAB nTab, bool bHidden)
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return;
+
+ maTabs[nTab]->SetColHidden(nStartCol, nEndCol, bHidden);
+}
+
+SCROW ScDocument::FirstVisibleRow(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return ::std::numeric_limits<SCROW>::max();
+
+ return maTabs[nTab]->FirstVisibleRow(nStartRow, nEndRow);
+}
+
+SCROW ScDocument::LastVisibleRow(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return ::std::numeric_limits<SCROW>::max();
+
+ return maTabs[nTab]->LastVisibleRow(nStartRow, nEndRow);
+}
+
+SCROW ScDocument::CountVisibleRows(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return 0;
+
+ return maTabs[nTab]->CountVisibleRows(nStartRow, nEndRow);
+}
+
+bool ScDocument::RowFiltered(SCROW nRow, SCTAB nTab, SCROW* pFirstRow, SCROW* pLastRow) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return false;
+
+ return maTabs[nTab]->RowFiltered(nRow, pFirstRow, pLastRow);
+}
+
+bool ScDocument::HasFilteredRows(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return false;
+
+ return maTabs[nTab]->HasFilteredRows(nStartRow, nEndRow);
+}
+
+bool ScDocument::ColFiltered(SCCOL nCol, SCTAB nTab) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return false;
+
+ return maTabs[nTab]->ColFiltered(nCol);
+}
+
+void ScDocument::SetRowFiltered(SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bFiltered)
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return;
+
+ maTabs[nTab]->SetRowFiltered(nStartRow, nEndRow, bFiltered);
+}
+
+SCROW ScDocument::FirstNonFilteredRow(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return ::std::numeric_limits<SCROW>::max();
+
+ return maTabs[nTab]->FirstNonFilteredRow(nStartRow, nEndRow);
+}
+
+SCROW ScDocument::LastNonFilteredRow(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return ::std::numeric_limits<SCROW>::max();
+
+ return maTabs[nTab]->LastNonFilteredRow(nStartRow, nEndRow);
+}
+
+SCROW ScDocument::CountNonFilteredRows(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return 0;
+
+ return maTabs[nTab]->CountNonFilteredRows(nStartRow, nEndRow);
+}
+
+bool ScDocument::IsManualRowHeight(SCROW nRow, SCTAB nTab) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return false;
+
+ return maTabs[nTab]->IsManualRowHeight(nRow);
+}
+
+void ScDocument::SyncColRowFlags()
+{
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->SyncColRowFlags();
+ }
+}
+
+SCROW ScDocument::GetLastFlaggedRow( SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetLastFlaggedRow();
+ return 0;
+}
+
+SCCOL ScDocument::GetLastChangedColFlagsWidth( SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetLastChangedColFlagsWidth();
+ return 0;
+}
+
+SCROW ScDocument::GetLastChangedRowFlagsWidth( SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetLastChangedRowFlagsWidth();
+ return 0;
+}
+
+SCCOL ScDocument::GetNextDifferentChangedColFlagsWidth( SCTAB nTab, SCCOL nStart) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ {
+ CRFlags nStartFlags = maTabs[nTab]->GetColFlags(nStart);
+ sal_uInt16 nStartWidth = maTabs[nTab]->GetOriginalWidth(nStart);
+ for (SCCOL nCol : maTabs[nTab]->GetColumnsRange( nStart + 1, MaxCol()))
+ {
+ if (((nStartFlags & CRFlags::ManualBreak) != (maTabs[nTab]->GetColFlags(nCol) & CRFlags::ManualBreak)) ||
+ (nStartWidth != maTabs[nTab]->GetOriginalWidth(nCol)) ||
+ ((nStartFlags & CRFlags::Hidden) != (maTabs[nTab]->GetColFlags(nCol) & CRFlags::Hidden)) )
+ return nCol;
+ }
+ return MaxCol()+1;
+ }
+ return 0;
+}
+
+SCROW ScDocument::GetNextDifferentChangedRowFlagsWidth( SCTAB nTab, SCROW nStart) const
+{
+ if (!ValidTab(nTab) || nTab >= static_cast<SCTAB>(maTabs.size()) || !maTabs[nTab])
+ return 0;
+
+ const ScBitMaskCompressedArray<SCROW, CRFlags>* pRowFlagsArray = maTabs[nTab]->GetRowFlagsArray();
+ if (!pRowFlagsArray)
+ return 0;
+
+ if (!maTabs[nTab]->mpRowHeights || !maTabs[nTab]->mpHiddenRows)
+ return 0;
+
+ size_t nIndex; // ignored
+ SCROW nFlagsEndRow;
+ SCROW nHiddenEndRow;
+ SCROW nHeightEndRow;
+ CRFlags nFlags;
+ bool bHidden;
+ sal_uInt16 nHeight;
+ CRFlags nStartFlags = nFlags = pRowFlagsArray->GetValue( nStart, nIndex, nFlagsEndRow);
+ bool bStartHidden = bHidden = maTabs[nTab]->RowHidden( nStart, nullptr, &nHiddenEndRow);
+ sal_uInt16 nStartHeight = nHeight = maTabs[nTab]->GetRowHeight( nStart, nullptr, &nHeightEndRow, false);
+ SCROW nRow;
+ while ((nRow = std::min( nHiddenEndRow, std::min( nFlagsEndRow, nHeightEndRow)) + 1) <= MaxRow())
+ {
+ if (nFlagsEndRow < nRow)
+ nFlags = pRowFlagsArray->GetValue( nRow, nIndex, nFlagsEndRow);
+ if (nHiddenEndRow < nRow)
+ bHidden = maTabs[nTab]->RowHidden( nRow, nullptr, &nHiddenEndRow);
+ if (nHeightEndRow < nRow)
+ nHeight = maTabs[nTab]->GetRowHeight( nRow, nullptr, &nHeightEndRow, false);
+
+ if (((nStartFlags & CRFlags::ManualBreak) != (nFlags & CRFlags::ManualBreak)) ||
+ ((nStartFlags & CRFlags::ManualSize) != (nFlags & CRFlags::ManualSize)) ||
+ (bStartHidden != bHidden) ||
+ (nStartHeight != nHeight))
+ return nRow;
+ }
+
+ return MaxRow()+1;
+}
+
+void ScDocument::GetColDefault( SCTAB nTab, SCCOL nCol, SCROW nLastRow, SCROW& nDefault)
+{
+ nDefault = 0;
+ ScDocAttrIterator aDocAttrItr(*this, nTab, nCol, 0, nCol, nLastRow);
+ SCCOL nColumn;
+ SCROW nStartRow;
+ SCROW nEndRow;
+ const ScPatternAttr* pAttr = aDocAttrItr.GetNext(nColumn, nStartRow, nEndRow);
+ if (nEndRow >= nLastRow)
+ return;
+
+ ScDefaultAttrSet aSet;
+ ScDefaultAttrSet::iterator aItr = aSet.end();
+ while (pAttr)
+ {
+ ScDefaultAttr aAttr(pAttr);
+ aItr = aSet.find(aAttr);
+ if (aItr == aSet.end())
+ {
+ aAttr.nCount = static_cast<SCSIZE>(nEndRow - nStartRow + 1);
+ aAttr.nFirst = nStartRow;
+ aSet.insert(aAttr);
+ }
+ else
+ {
+ aAttr.nCount = aItr->nCount + static_cast<SCSIZE>(nEndRow - nStartRow + 1);
+ aAttr.nFirst = aItr->nFirst;
+ aSet.erase(aItr);
+ aSet.insert(aAttr);
+ }
+ pAttr = aDocAttrItr.GetNext(nColumn, nStartRow, nEndRow);
+ }
+ ScDefaultAttrSet::iterator aDefaultItr = aSet.begin();
+ aItr = aDefaultItr;
+ ++aItr;
+ while (aItr != aSet.end())
+ {
+ // for entries with equal count, use the one with the lowest start row,
+ // don't use the random order of pointer comparisons
+ if ( aItr->nCount > aDefaultItr->nCount ||
+ ( aItr->nCount == aDefaultItr->nCount && aItr->nFirst < aDefaultItr->nFirst ) )
+ aDefaultItr = aItr;
+ ++aItr;
+ }
+ nDefault = aDefaultItr->nFirst;
+}
+
+void ScDocument::StripHidden( SCCOL& rX1, SCROW& rY1, SCCOL& rX2, SCROW& rY2, SCTAB nTab )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->StripHidden( rX1, rY1, rX2, rY2 );
+}
+
+void ScDocument::ExtendHidden( SCCOL& rX1, SCROW& rY1, SCCOL& rX2, SCROW& rY2, SCTAB nTab )
+{
+ if ( ValidTab(nTab) && maTabs[nTab] )
+ maTabs[nTab]->ExtendHidden( rX1, rY1, rX2, rY2 );
+}
+
+// Attribute ----------------------------------------------------------
+
+const SfxPoolItem* ScDocument::GetAttr( SCCOL nCol, SCROW nRow, SCTAB nTab, sal_uInt16 nWhich ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ {
+ const SfxPoolItem* pTemp = maTabs[nTab]->GetAttr( nCol, nRow, nWhich );
+ if (pTemp)
+ return pTemp;
+ else
+ {
+ OSL_FAIL( "Attribute Null" );
+ }
+ }
+ return &mxPoolHelper->GetDocPool()->GetDefaultItem( nWhich );
+}
+
+const SfxPoolItem* ScDocument::GetAttr( SCCOL nCol, SCROW nRow, SCTAB nTab, sal_uInt16 nWhich, SCROW& nStartRow, SCROW& nEndRow ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ {
+ const SfxPoolItem* pTemp = maTabs[nTab]->GetAttr( nCol, nRow, nWhich, nStartRow, nEndRow );
+ if (pTemp)
+ return pTemp;
+ else
+ {
+ OSL_FAIL( "Attribute Null" );
+ }
+ }
+ return &mxPoolHelper->GetDocPool()->GetDefaultItem( nWhich );
+}
+
+const SfxPoolItem* ScDocument::GetAttr( const ScAddress& rPos, sal_uInt16 nWhich ) const
+{
+ return GetAttr(rPos.Col(), rPos.Row(), rPos.Tab(), nWhich);
+}
+
+const ScPatternAttr* ScDocument::GetPattern( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
+{
+ if (TableExists(nTab))
+ return maTabs[nTab]->GetPattern( nCol, nRow );
+ return nullptr;
+}
+
+const ScPatternAttr* ScDocument::GetPattern( const ScAddress& rPos ) const
+{
+ if (TableExists(rPos.Tab()))
+ return maTabs[rPos.Tab()]->GetPattern(rPos.Col(), rPos.Row());
+
+ return nullptr;
+}
+
+const ScPatternAttr* ScDocument::GetMostUsedPattern( SCCOL nCol, SCROW nStartRow, SCROW nEndRow, SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetMostUsedPattern( nCol, nStartRow, nEndRow );
+ return nullptr;
+}
+
+void ScDocument::ApplyAttr( SCCOL nCol, SCROW nRow, SCTAB nTab, const SfxPoolItem& rAttr )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->ApplyAttr( nCol, nRow, rAttr );
+}
+
+void ScDocument::ApplyPattern( SCCOL nCol, SCROW nRow, SCTAB nTab, const ScPatternAttr& rAttr )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->ApplyPattern( nCol, nRow, rAttr );
+}
+
+void ScDocument::ApplyPatternArea( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow,
+ const ScMarkData& rMark,
+ const ScPatternAttr& rAttr,
+ ScEditDataArray* pDataArray,
+ bool* const pIsChanged )
+{
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+ if (maTabs[rTab])
+ maTabs[rTab]->ApplyPatternArea( nStartCol, nStartRow, nEndCol, nEndRow, rAttr, pDataArray, pIsChanged );
+ }
+}
+
+void ScDocument::ApplyPatternAreaTab( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow, SCTAB nTab, const ScPatternAttr& rAttr )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ if (maTabs[nTab])
+ maTabs[nTab]->ApplyPatternArea( nStartCol, nStartRow, nEndCol, nEndRow, rAttr );
+}
+
+void ScDocument::ApplyPatternIfNumberformatIncompatible( const ScRange& rRange,
+ const ScMarkData& rMark, const ScPatternAttr& rPattern, SvNumFormatType nNewType )
+{
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+ if (maTabs[rTab])
+ maTabs[rTab]->ApplyPatternIfNumberformatIncompatible( rRange, rPattern, nNewType );
+ }
+}
+
+void ScDocument::AddCondFormatData( const ScRangeList& rRange, SCTAB nTab, sal_uInt32 nIndex )
+{
+ if(o3tl::make_unsigned(nTab) >= maTabs.size())
+ return;
+
+ if(!maTabs[nTab])
+ return;
+
+ maTabs[nTab]->AddCondFormatData(rRange, nIndex);
+}
+
+void ScDocument::RemoveCondFormatData( const ScRangeList& rRange, SCTAB nTab, sal_uInt32 nIndex )
+{
+ if(o3tl::make_unsigned(nTab) >= maTabs.size())
+ return;
+
+ if(!maTabs[nTab])
+ return;
+
+ maTabs[nTab]->RemoveCondFormatData(rRange, nIndex);
+}
+
+void ScDocument::ApplyStyle( SCCOL nCol, SCROW nRow, SCTAB nTab, const ScStyleSheet& rStyle)
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ if (maTabs[nTab])
+ maTabs[nTab]->ApplyStyle( nCol, nRow, &rStyle );
+}
+
+void ScDocument::ApplyStyleArea( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow,
+ const ScMarkData& rMark,
+ const ScStyleSheet& rStyle)
+{
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+ if (maTabs[rTab])
+ maTabs[rTab]->ApplyStyleArea( nStartCol, nStartRow, nEndCol, nEndRow, rStyle );
+ }
+}
+
+void ScDocument::ApplyStyleAreaTab( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow, SCTAB nTab, const ScStyleSheet& rStyle)
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ if (maTabs[nTab])
+ maTabs[nTab]->ApplyStyleArea( nStartCol, nStartRow, nEndCol, nEndRow, rStyle );
+}
+
+void ScDocument::ApplySelectionStyle(const ScStyleSheet& rStyle, const ScMarkData& rMark)
+{
+ // ApplySelectionStyle needs multi mark
+ if ( rMark.IsMarked() && !rMark.IsMultiMarked() )
+ {
+ const ScRange& aRange = rMark.GetMarkArea();
+ ApplyStyleArea( aRange.aStart.Col(), aRange.aStart.Row(),
+ aRange.aEnd.Col(), aRange.aEnd.Row(), rMark, rStyle );
+ }
+ else
+ {
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+ if ( maTabs[rTab] )
+ maTabs[rTab]->ApplySelectionStyle( rStyle, rMark );
+ }
+ }
+}
+
+void ScDocument::ApplySelectionLineStyle( const ScMarkData& rMark,
+ const SvxBorderLine* pLine, bool bColorOnly )
+{
+ if ( bColorOnly && !pLine )
+ return;
+
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+ if (maTabs[rTab])
+ maTabs[rTab]->ApplySelectionLineStyle( rMark, pLine, bColorOnly );
+ }
+}
+
+const ScStyleSheet* ScDocument::GetStyle( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetStyle(nCol, nRow);
+ else
+ return nullptr;
+}
+
+const ScStyleSheet* ScDocument::GetSelectionStyle( const ScMarkData& rMark ) const
+{
+ bool bEqual = true;
+ bool bFound;
+
+ const ScStyleSheet* pStyle = nullptr;
+ const ScStyleSheet* pNewStyle;
+
+ if ( rMark.IsMultiMarked() )
+ {
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+
+ if (maTabs[rTab])
+ {
+ pNewStyle = maTabs[rTab]->GetSelectionStyle( rMark, bFound );
+ if (bFound)
+ {
+ if ( !pNewStyle || ( pStyle && pNewStyle != pStyle ) )
+ bEqual = false; // different
+ pStyle = pNewStyle;
+ }
+ }
+ }
+ }
+ if ( rMark.IsMarked() )
+ {
+ const ScRange& aRange = rMark.GetMarkArea();
+ for (SCTAB i=aRange.aStart.Tab(); i<=aRange.aEnd.Tab() && bEqual && i < static_cast<SCTAB>(maTabs.size()); i++)
+ if (maTabs[i] && rMark.GetTableSelect(i))
+ {
+ pNewStyle = maTabs[i]->GetAreaStyle( bFound,
+ aRange.aStart.Col(), aRange.aStart.Row(),
+ aRange.aEnd.Col(), aRange.aEnd.Row() );
+ if (bFound)
+ {
+ if ( !pNewStyle || ( pStyle && pNewStyle != pStyle ) )
+ bEqual = false; // different
+ pStyle = pNewStyle;
+ }
+ }
+ }
+
+ return bEqual ? pStyle : nullptr;
+}
+
+void ScDocument::StyleSheetChanged( const SfxStyleSheetBase* pStyleSheet, bool bRemoved,
+ OutputDevice* pDev,
+ double nPPTX, double nPPTY,
+ const Fraction& rZoomX, const Fraction& rZoomY )
+{
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->StyleSheetChanged
+ ( pStyleSheet, bRemoved, pDev, nPPTX, nPPTY, rZoomX, rZoomY );
+ }
+
+ if ( pStyleSheet && pStyleSheet->GetName() == ScResId(STR_STYLENAME_STANDARD) )
+ {
+ // update attributes for all note objects
+ ScDetectiveFunc::UpdateAllComments( *this );
+ }
+}
+
+bool ScDocument::IsStyleSheetUsed( const ScStyleSheet& rStyle ) const
+{
+ if ( bStyleSheetUsageInvalid || rStyle.GetUsage() == ScStyleSheet::Usage::UNKNOWN )
+ {
+ SfxStyleSheetIterator aIter( mxPoolHelper->GetStylePool(),
+ SfxStyleFamily::Para );
+ for ( const SfxStyleSheetBase* pStyle = aIter.First(); pStyle;
+ pStyle = aIter.Next() )
+ {
+ if (pStyle->isScStyleSheet())
+ {
+ const ScStyleSheet* pScStyle = static_cast<const ScStyleSheet*>( pStyle );
+ pScStyle->SetUsage( ScStyleSheet::Usage::NOTUSED );
+ }
+ }
+
+ bool bIsUsed = false;
+
+ for (const auto& a : maTabs)
+ {
+ if (a && a->IsStyleSheetUsed( rStyle ) )
+ bIsUsed = true;
+ }
+
+ bStyleSheetUsageInvalid = false;
+
+ return bIsUsed;
+ }
+
+ return rStyle.GetUsage() == ScStyleSheet::Usage::USED;
+}
+
+bool ScDocument::ApplyFlagsTab( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow, SCTAB nTab, ScMF nFlags )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ if (maTabs[nTab])
+ return maTabs[nTab]->ApplyFlags( nStartCol, nStartRow, nEndCol, nEndRow, nFlags );
+
+ OSL_FAIL("ApplyFlags: wrong table");
+ return false;
+}
+
+bool ScDocument::RemoveFlagsTab( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow, SCTAB nTab, ScMF nFlags )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ if (maTabs[nTab])
+ return maTabs[nTab]->RemoveFlags( nStartCol, nStartRow, nEndCol, nEndRow, nFlags );
+
+ OSL_FAIL("RemoveFlags: wrong table");
+ return false;
+}
+
+const ScPatternAttr* ScDocument::SetPattern( SCCOL nCol, SCROW nRow, SCTAB nTab, std::unique_ptr<ScPatternAttr> pAttr )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ if (maTabs[nTab])
+ return maTabs[nTab]->SetPattern( nCol, nRow, std::move(pAttr) );
+ return nullptr;
+}
+
+const ScPatternAttr* ScDocument::SetPattern( const ScAddress& rPos, std::unique_ptr<ScPatternAttr> pAttr )
+{
+ return SetPattern(rPos.Col(), rPos.Row(), rPos.Tab(), std::move(pAttr));
+}
+
+void ScDocument::SetPattern( SCCOL nCol, SCROW nRow, SCTAB nTab, const ScPatternAttr& rAttr )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ if (maTabs[nTab])
+ maTabs[nTab]->SetPattern( nCol, nRow, rAttr );
+}
+
+void ScDocument::SetPattern( const ScAddress& rPos, const ScPatternAttr& rAttr )
+{
+ SCTAB nTab = rPos.Tab();
+ if ( nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->SetPattern( rPos, rAttr );
+}
+
+std::unique_ptr<ScPatternAttr> ScDocument::CreateSelectionPattern( const ScMarkData& rMark, bool bDeep )
+{
+ ScMergePatternState aState;
+
+ if ( rMark.IsMultiMarked() ) // multi selection
+ {
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+ if (maTabs[rTab])
+ maTabs[rTab]->MergeSelectionPattern( aState, rMark, bDeep );
+ }
+ }
+ if ( rMark.IsMarked() ) // single selection
+ {
+ const ScRange& aRange = rMark.GetMarkArea();
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+ if (maTabs[rTab])
+ maTabs[rTab]->MergePatternArea( aState,
+ aRange.aStart.Col(), aRange.aStart.Row(),
+ aRange.aEnd.Col(), aRange.aEnd.Row(), bDeep );
+ }
+ }
+
+ OSL_ENSURE( aState.pItemSet, "SelectionPattern Null" );
+ if (aState.pItemSet)
+ {
+ std::unique_ptr<ScPatternAttr> pPattern(new ScPatternAttr( std::move(*aState.pItemSet) ));
+ if (aState.mbValidPatternId)
+ pPattern->SetKey(aState.mnPatternId);
+
+ return pPattern;
+ }
+ else
+ return std::unique_ptr<ScPatternAttr>(new ScPatternAttr( GetPool() )); // empty
+}
+
+const ScPatternAttr* ScDocument::GetSelectionPattern( const ScMarkData& rMark )
+{
+ pSelectionAttr = CreateSelectionPattern( rMark );
+ return pSelectionAttr.get();
+}
+
+void ScDocument::GetSelectionFrame( const ScMarkData& rMark,
+ SvxBoxItem& rLineOuter,
+ SvxBoxInfoItem& rLineInner )
+{
+ rLineOuter.SetLine(nullptr, SvxBoxItemLine::TOP);
+ rLineOuter.SetLine(nullptr, SvxBoxItemLine::BOTTOM);
+ rLineOuter.SetLine(nullptr, SvxBoxItemLine::LEFT);
+ rLineOuter.SetLine(nullptr, SvxBoxItemLine::RIGHT);
+ rLineOuter.SetAllDistances(0);
+
+ rLineInner.SetLine(nullptr, SvxBoxInfoItemLine::HORI);
+ rLineInner.SetLine(nullptr, SvxBoxInfoItemLine::VERT);
+ rLineInner.SetTable(true);
+ rLineInner.SetDist(true);
+ rLineInner.SetMinDist(false);
+
+ ScLineFlags aFlags;
+
+ if( rMark.IsMultiMarked() )
+ {
+ ScRangeList aRangeList;
+ rMark.FillRangeListWithMarks( &aRangeList, false );
+ size_t nRangeCount = aRangeList.size();
+ bool bMultipleRows = false, bMultipleCols = false;
+ for( size_t nRangeIdx = 0; nRangeIdx < nRangeCount; ++nRangeIdx )
+ {
+ const ScRange & rRange = aRangeList[ nRangeIdx ];
+ bMultipleRows = ( bMultipleRows || ( rRange.aStart.Row() != rRange.aEnd.Row() ) );
+ bMultipleCols = ( bMultipleCols || ( rRange.aStart.Col() != rRange.aEnd.Col() ) );
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+
+ if (maTabs[rTab])
+ maTabs[rTab]->MergeBlockFrame( &rLineOuter, &rLineInner, aFlags,
+ rRange.aStart.Col(), rRange.aStart.Row(),
+ rRange.aEnd.Col(), rRange.aEnd.Row() );
+ }
+ }
+ rLineInner.EnableHor( bMultipleRows );
+ rLineInner.EnableVer( bMultipleCols );
+ }
+ else if( rMark.IsMarked() )
+ {
+ const ScRange& aRange = rMark.GetMarkArea();
+ rLineInner.EnableHor( aRange.aStart.Row() != aRange.aEnd.Row() );
+ rLineInner.EnableVer( aRange.aStart.Col() != aRange.aEnd.Col() );
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+
+ if (maTabs[rTab])
+ maTabs[rTab]->MergeBlockFrame( &rLineOuter, &rLineInner, aFlags,
+ aRange.aStart.Col(), aRange.aStart.Row(),
+ aRange.aEnd.Col(), aRange.aEnd.Row() );
+ }
+ }
+
+ // Evaluate don't care Status
+
+ rLineInner.SetValid( SvxBoxInfoItemValidFlags::LEFT, ( aFlags.nLeft != SC_LINE_DONTCARE ) );
+ rLineInner.SetValid( SvxBoxInfoItemValidFlags::RIGHT, ( aFlags.nRight != SC_LINE_DONTCARE ) );
+ rLineInner.SetValid( SvxBoxInfoItemValidFlags::TOP, ( aFlags.nTop != SC_LINE_DONTCARE ) );
+ rLineInner.SetValid( SvxBoxInfoItemValidFlags::BOTTOM, ( aFlags.nBottom != SC_LINE_DONTCARE ) );
+ rLineInner.SetValid( SvxBoxInfoItemValidFlags::HORI, ( aFlags.nHori != SC_LINE_DONTCARE ) );
+ rLineInner.SetValid( SvxBoxInfoItemValidFlags::VERT, ( aFlags.nVert != SC_LINE_DONTCARE ) );
+}
+
+static HasAttrFlags OptimizeHasAttrib( HasAttrFlags nMask, const ScDocumentPool* pPool )
+{
+ if ( nMask & HasAttrFlags::Rotate )
+ {
+ // Is attribute used in document?
+ // (as in fillinfo)
+
+ bool bAnyItem = false;
+ for (const SfxPoolItem* pItem : pPool->GetItemSurrogates(ATTR_ROTATE_VALUE))
+ {
+ // 90 or 270 degrees is former SvxOrientationItem - only look for other values
+ // (see ScPatternAttr::GetCellOrientation)
+ Degree100 nAngle = static_cast<const ScRotateValueItem*>(pItem)->GetValue();
+ if ( nAngle && nAngle != 9000_deg100 && nAngle != 27000_deg100 )
+ {
+ bAnyItem = true;
+ break;
+ }
+ }
+ if (!bAnyItem)
+ nMask &= ~HasAttrFlags::Rotate;
+ }
+ return nMask;
+}
+
+bool ScDocument::HasAttrib( SCCOL nCol1, SCROW nRow1, SCTAB nTab1,
+ SCCOL nCol2, SCROW nRow2, SCTAB nTab2, HasAttrFlags nMask ) const
+{
+ nMask = OptimizeHasAttrib( nMask, mxPoolHelper->GetDocPool());
+
+ if (nMask == HasAttrFlags::NONE)
+ return false;
+
+ for (SCTAB i=nTab1; i<=nTab2 && i < static_cast<SCTAB>(maTabs.size()); i++)
+ if (maTabs[i])
+ {
+ if ( nMask & HasAttrFlags::RightOrCenter )
+ {
+ // On a RTL sheet, don't start to look for the default left value
+ // (which is then logically right), instead always assume true.
+ // That way, ScAttrArray::HasAttrib doesn't have to handle RTL sheets.
+
+ if ( IsLayoutRTL(i) )
+ return true;
+ }
+
+ if( maTabs[i]->HasAttrib( nCol1, nRow1, nCol2, nRow2, nMask ))
+ return true;
+ }
+
+ return false;
+}
+
+bool ScDocument::HasAttrib( SCCOL nCol, SCROW nRow, SCTAB nTab, HasAttrFlags nMask, SCROW* nStartRow, SCROW* nEndRow ) const
+{
+ nMask = OptimizeHasAttrib( nMask, mxPoolHelper->GetDocPool());
+
+ if (nMask == HasAttrFlags::NONE || nTab >= static_cast<SCTAB>(maTabs.size()))
+ {
+ if( nStartRow )
+ *nStartRow = 0;
+ if( nEndRow )
+ *nEndRow = MaxRow();
+ return false;
+ }
+
+ if ( nMask & HasAttrFlags::RightOrCenter )
+ {
+ // On a RTL sheet, don't start to look for the default left value
+ // (which is then logically right), instead always assume true.
+ // That way, ScAttrArray::HasAttrib doesn't have to handle RTL sheets.
+
+ if ( IsLayoutRTL(nTab) )
+ {
+ if( nStartRow )
+ *nStartRow = 0;
+ if( nEndRow )
+ *nEndRow = MaxRow();
+ return true;
+ }
+ }
+
+ return maTabs[nTab]->HasAttrib( nCol, nRow, nMask, nStartRow, nEndRow );
+}
+
+bool ScDocument::HasAttrib( const ScRange& rRange, HasAttrFlags nMask ) const
+{
+ return HasAttrib( rRange.aStart.Col(), rRange.aStart.Row(), rRange.aStart.Tab(),
+ rRange.aEnd.Col(), rRange.aEnd.Row(), rRange.aEnd.Tab(),
+ nMask );
+}
+
+void ScDocument::FindMaxRotCol( SCTAB nTab, RowInfo* pRowInfo, SCSIZE nArrCount,
+ SCCOL nX1, SCCOL nX2 ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->FindMaxRotCol( pRowInfo, nArrCount, nX1, nX2 );
+ else
+ {
+ OSL_FAIL("FindMaxRotCol: wrong table");
+ }
+}
+
+void ScDocument::GetBorderLines( SCCOL nCol, SCROW nRow, SCTAB nTab,
+ const SvxBorderLine** ppLeft, const SvxBorderLine** ppTop,
+ const SvxBorderLine** ppRight, const SvxBorderLine** ppBottom ) const
+{
+ //TODO: consider page limits for printing !!!!!
+
+ const SvxBoxItem* pThisAttr = GetEffItem( nCol, nRow, nTab, ATTR_BORDER );
+ OSL_ENSURE(pThisAttr,"where is the attribute?");
+
+ const SvxBorderLine* pLeftLine = pThisAttr->GetLeft();
+ const SvxBorderLine* pTopLine = pThisAttr->GetTop();
+ const SvxBorderLine* pRightLine = pThisAttr->GetRight();
+ const SvxBorderLine* pBottomLine = pThisAttr->GetBottom();
+
+ if ( nCol > 0 )
+ {
+ const SvxBorderLine* pOther = GetEffItem( nCol-1, nRow, nTab, ATTR_BORDER )->GetRight();
+ if ( ScHasPriority( pOther, pLeftLine ) )
+ pLeftLine = pOther;
+ }
+ if ( nRow > 0 )
+ {
+ const SvxBorderLine* pOther = GetEffItem( nCol, nRow-1, nTab, ATTR_BORDER )->GetBottom();
+ if ( ScHasPriority( pOther, pTopLine ) )
+ pTopLine = pOther;
+ }
+ if ( nCol < MaxCol() )
+ {
+ const SvxBorderLine* pOther = GetEffItem( nCol+1, nRow, nTab, ATTR_BORDER )->GetLeft();
+ if ( ScHasPriority( pOther, pRightLine ) )
+ pRightLine = pOther;
+ }
+ if ( nRow < MaxRow() )
+ {
+ const SvxBorderLine* pOther = GetEffItem( nCol, nRow+1, nTab, ATTR_BORDER )->GetTop();
+ if ( ScHasPriority( pOther, pBottomLine ) )
+ pBottomLine = pOther;
+ }
+
+ if (ppLeft)
+ *ppLeft = pLeftLine;
+ if (ppTop)
+ *ppTop = pTopLine;
+ if (ppRight)
+ *ppRight = pRightLine;
+ if (ppBottom)
+ *ppBottom = pBottomLine;
+}
+
+bool ScDocument::IsBlockEmpty(SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow, SCTAB nTab) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ if (maTabs[nTab])
+ return maTabs[nTab]->IsBlockEmpty( nStartCol, nStartRow, nEndCol, nEndRow );
+
+ OSL_FAIL("wrong table number");
+ return false;
+}
+
+void ScDocument::LockTable(SCTAB nTab)
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->LockTable();
+ else
+ {
+ OSL_FAIL("wrong table number");
+ }
+}
+
+void ScDocument::UnlockTable(SCTAB nTab)
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->UnlockTable();
+ else
+ {
+ OSL_FAIL("wrong table number");
+ }
+}
+
+bool ScDocument::IsBlockEditable( SCTAB nTab, SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow,
+ bool* pOnlyNotBecauseOfMatrix /* = NULL */,
+ bool bNoMatrixAtAll ) const
+{
+ // import into read-only document is possible
+ if (!bImportingXML && !mbChangeReadOnlyEnabled && mpShell && mpShell->IsReadOnly())
+ {
+ if ( pOnlyNotBecauseOfMatrix )
+ *pOnlyNotBecauseOfMatrix = false;
+ return false;
+ }
+
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ if (maTabs[nTab])
+ return maTabs[nTab]->IsBlockEditable( nStartCol, nStartRow, nEndCol,
+ nEndRow, pOnlyNotBecauseOfMatrix, bNoMatrixAtAll );
+
+ OSL_FAIL("wrong table number");
+ if ( pOnlyNotBecauseOfMatrix )
+ *pOnlyNotBecauseOfMatrix = false;
+ return false;
+}
+
+bool ScDocument::IsSelectionEditable( const ScMarkData& rMark,
+ bool* pOnlyNotBecauseOfMatrix /* = NULL */ ) const
+{
+ // import into read-only document is possible
+ if ( !bImportingXML && !mbChangeReadOnlyEnabled && mpShell && mpShell->IsReadOnly() )
+ {
+ if ( pOnlyNotBecauseOfMatrix )
+ *pOnlyNotBecauseOfMatrix = false;
+ return false;
+ }
+
+ const ScRange& aRange = rMark.GetMarkArea();
+
+ bool bOk = true;
+ bool bMatrix = ( pOnlyNotBecauseOfMatrix != nullptr );
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+
+ if ( maTabs[rTab] )
+ {
+ if (rMark.IsMarked())
+ {
+ if ( !maTabs[rTab]->IsBlockEditable( aRange.aStart.Col(),
+ aRange.aStart.Row(), aRange.aEnd.Col(),
+ aRange.aEnd.Row(), pOnlyNotBecauseOfMatrix ) )
+ {
+ bOk = false;
+ if ( pOnlyNotBecauseOfMatrix )
+ bMatrix = *pOnlyNotBecauseOfMatrix;
+ }
+ }
+ if (rMark.IsMultiMarked())
+ {
+ if ( !maTabs[rTab]->IsSelectionEditable( rMark, pOnlyNotBecauseOfMatrix ) )
+ {
+ bOk = false;
+ if ( pOnlyNotBecauseOfMatrix )
+ bMatrix = *pOnlyNotBecauseOfMatrix;
+ }
+ }
+ }
+
+ if (!bOk && !bMatrix)
+ break;
+ }
+
+ if ( pOnlyNotBecauseOfMatrix )
+ *pOnlyNotBecauseOfMatrix = ( !bOk && bMatrix );
+
+ return bOk;
+}
+
+bool ScDocument::HasSelectedBlockMatrixFragment( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow,
+ const ScMarkData& rMark ) const
+{
+ bool bOk = true;
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+
+ if (maTabs[rTab] && maTabs[rTab]->HasBlockMatrixFragment( nStartCol, nStartRow, nEndCol, nEndRow ))
+ bOk = false;
+
+ if (!bOk)
+ break;
+ }
+
+ return !bOk;
+}
+
+bool ScDocument::GetMatrixFormulaRange( const ScAddress& rCellPos, ScRange& rMatrix )
+{
+ // if rCell is part of a matrix formula, return its complete range
+
+ ScFormulaCell* pFCell = GetFormulaCell(rCellPos);
+ if (!pFCell)
+ // not a formula cell. Bail out.
+ return false;
+
+ ScAddress aOrigin = rCellPos;
+ if (!pFCell->GetMatrixOrigin(*this, aOrigin))
+ // Failed to get the address of the matrix origin.
+ return false;
+
+ if (aOrigin != rCellPos)
+ {
+ pFCell = GetFormulaCell(aOrigin);
+ if (!pFCell)
+ // The matrix origin cell is not a formula cell !? Something is up...
+ return false;
+ }
+
+ SCCOL nSizeX;
+ SCROW nSizeY;
+ pFCell->GetMatColsRows(nSizeX, nSizeY);
+ if (nSizeX <= 0 || nSizeY <= 0)
+ {
+ // GetMatrixEdge computes also dimensions of the matrix
+ // if not already done (may occur if document is loaded
+ // from old file format).
+ // Needs an "invalid" initialized address.
+ aOrigin.SetInvalid();
+ pFCell->GetMatrixEdge(*this, aOrigin);
+ pFCell->GetMatColsRows(nSizeX, nSizeY);
+ }
+
+ if (nSizeX <= 0 || nSizeY <= 0)
+ // Matrix size is still invalid. Give up.
+ return false;
+
+ ScAddress aEnd( aOrigin.Col() + nSizeX - 1,
+ aOrigin.Row() + nSizeY - 1,
+ aOrigin.Tab() );
+
+ rMatrix.aStart = aOrigin;
+ rMatrix.aEnd = aEnd;
+
+ return true;
+}
+
+void ScDocument::ExtendOverlapped( SCCOL& rStartCol, SCROW& rStartRow,
+ SCCOL nEndCol, SCROW nEndRow, SCTAB nTab ) const
+{
+ if ( ValidColRow(rStartCol,rStartRow) && ValidColRow(nEndCol,nEndRow) && ValidTab(nTab) )
+ {
+ if (nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ {
+ SCCOL nCol;
+ SCCOL nOldCol = rStartCol;
+ SCROW nOldRow = rStartRow;
+ for (nCol=nOldCol; nCol<=nEndCol; nCol++)
+ while (GetAttr(nCol,rStartRow,nTab,ATTR_MERGE_FLAG)->IsVerOverlapped())
+ --rStartRow;
+
+ //TODO: pass on ?
+
+ const ScAttrArray& pAttrArray = maTabs[nTab]->ColumnData(nOldCol).AttrArray();
+ SCSIZE nIndex;
+ if ( pAttrArray.Count() )
+ pAttrArray.Search( nOldRow, nIndex );
+ else
+ nIndex = 0;
+ SCROW nAttrPos = nOldRow;
+ while (nAttrPos<=nEndRow)
+ {
+ OSL_ENSURE( nIndex < pAttrArray.Count(), "Wrong index in AttrArray" );
+
+ bool bHorOverlapped;
+ if ( pAttrArray.Count() )
+ bHorOverlapped = pAttrArray.mvData[nIndex].pPattern->GetItem(ATTR_MERGE_FLAG).IsHorOverlapped();
+ else
+ bHorOverlapped = GetDefPattern()->GetItem(ATTR_MERGE_FLAG).IsHorOverlapped();
+ if ( bHorOverlapped )
+ {
+ SCROW nEndRowSeg = (pAttrArray.Count()) ? pAttrArray.mvData[nIndex].nEndRow : MaxRow();
+ SCROW nLoopEndRow = std::min( nEndRow, nEndRowSeg );
+ for (SCROW nAttrRow = nAttrPos; nAttrRow <= nLoopEndRow; nAttrRow++)
+ {
+ SCCOL nTempCol = nOldCol;
+ do
+ --nTempCol;
+ while (GetAttr(nTempCol,nAttrRow,nTab,ATTR_MERGE_FLAG)->IsHorOverlapped());
+ if (nTempCol < rStartCol)
+ rStartCol = nTempCol;
+ }
+ }
+ if ( pAttrArray.Count() )
+ {
+ nAttrPos = pAttrArray.mvData[nIndex].nEndRow + 1;
+ ++nIndex;
+ }
+ else
+ nAttrPos = MaxRow() + 1;
+ }
+ }
+ }
+ else
+ {
+ OSL_FAIL("ExtendOverlapped: invalid range");
+ }
+}
+
+void ScDocument::ExtendMergeSel( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL& rEndCol, SCROW& rEndRow,
+ const ScMarkData& rMark, bool bRefresh )
+{
+ // use all selected sheets from rMark
+
+ SCCOL nOldEndCol = rEndCol;
+ SCROW nOldEndRow = rEndRow;
+
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+
+ if ( maTabs[rTab] )
+ {
+ SCCOL nThisEndCol = nOldEndCol;
+ SCROW nThisEndRow = nOldEndRow;
+ ExtendMerge( nStartCol, nStartRow, nThisEndCol, nThisEndRow, rTab, bRefresh );
+ if ( nThisEndCol > rEndCol )
+ rEndCol = nThisEndCol;
+ if ( nThisEndRow > rEndRow )
+ rEndRow = nThisEndRow;
+ }
+ }
+}
+
+bool ScDocument::ExtendMerge( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL& rEndCol, SCROW& rEndRow,
+ SCTAB nTab, bool bRefresh )
+{
+ bool bFound = false;
+ if ( ValidColRow(nStartCol,nStartRow) && ValidColRow(rEndCol,rEndRow) && ValidTab(nTab) )
+ {
+ if (nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ bFound = maTabs[nTab]->ExtendMerge( nStartCol, nStartRow, rEndCol, rEndRow, bRefresh );
+
+ if (bRefresh)
+ RefreshAutoFilter( nStartCol, nStartRow, rEndCol, rEndRow, nTab );
+ }
+ else
+ {
+ OSL_FAIL("ExtendMerge: invalid range");
+ }
+
+ return bFound;
+}
+
+bool ScDocument::ExtendMerge( ScRange& rRange, bool bRefresh )
+{
+ bool bFound = false;
+ SCTAB nStartTab = rRange.aStart.Tab();
+ SCTAB nEndTab = rRange.aEnd.Tab();
+ SCCOL nEndCol = rRange.aEnd.Col();
+ SCROW nEndRow = rRange.aEnd.Row();
+
+ PutInOrder( nStartTab, nEndTab );
+ for (SCTAB nTab = nStartTab; nTab <= nEndTab && nTab < static_cast<SCTAB>(maTabs.size()); nTab++ )
+ {
+ SCCOL nExtendCol = rRange.aEnd.Col();
+ SCROW nExtendRow = rRange.aEnd.Row();
+ if (ExtendMerge( rRange.aStart.Col(), rRange.aStart.Row(),
+ nExtendCol, nExtendRow,
+ nTab, bRefresh ) )
+ {
+ bFound = true;
+ if (nExtendCol > nEndCol) nEndCol = nExtendCol;
+ if (nExtendRow > nEndRow) nEndRow = nExtendRow;
+ }
+ }
+
+ rRange.aEnd.SetCol(nEndCol);
+ rRange.aEnd.SetRow(nEndRow);
+
+ return bFound;
+}
+
+void ScDocument::ExtendTotalMerge( ScRange& rRange ) const
+{
+ // Extend range to merged cells without including any new non-overlapped cells
+ ScRange aExt = rRange;
+ // ExtendMerge() is non-const, but called without refresh.
+ if (!const_cast<ScDocument*>(this)->ExtendMerge( aExt ))
+ return;
+
+ if ( aExt.aEnd.Row() > rRange.aEnd.Row() )
+ {
+ ScRange aTest = aExt;
+ aTest.aStart.SetRow( rRange.aEnd.Row() + 1 );
+ if ( HasAttrib( aTest, HasAttrFlags::NotOverlapped ) )
+ aExt.aEnd.SetRow(rRange.aEnd.Row());
+ }
+ if ( aExt.aEnd.Col() > rRange.aEnd.Col() )
+ {
+ ScRange aTest = aExt;
+ aTest.aStart.SetCol( rRange.aEnd.Col() + 1 );
+ if ( HasAttrib( aTest, HasAttrFlags::NotOverlapped ) )
+ aExt.aEnd.SetCol(rRange.aEnd.Col());
+ }
+
+ rRange = aExt;
+}
+
+void ScDocument::ExtendOverlapped( ScRange& rRange ) const
+{
+ SCTAB nStartTab = rRange.aStart.Tab();
+ SCTAB nEndTab = rRange.aEnd.Tab();
+ SCCOL nStartCol = rRange.aStart.Col();
+ SCROW nStartRow = rRange.aStart.Row();
+
+ PutInOrder( nStartTab, nEndTab );
+ for (SCTAB nTab = nStartTab; nTab <= nEndTab && nTab < static_cast<SCTAB>(maTabs.size()); nTab++ )
+ {
+ SCCOL nExtendCol = rRange.aStart.Col();
+ SCROW nExtendRow = rRange.aStart.Row();
+ ExtendOverlapped( nExtendCol, nExtendRow,
+ rRange.aEnd.Col(), rRange.aEnd.Row(), nTab );
+ if (nExtendCol < nStartCol)
+ {
+ nStartCol = nExtendCol;
+ }
+ if (nExtendRow < nStartRow)
+ {
+ nStartRow = nExtendRow;
+ }
+ }
+
+ rRange.aStart.SetCol(nStartCol);
+ rRange.aStart.SetRow(nStartRow);
+}
+
+bool ScDocument::RefreshAutoFilter( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow, SCTAB nTab )
+{
+ SCTAB nDBTab;
+ SCCOL nDBStartCol;
+ SCROW nDBStartRow;
+ SCCOL nDBEndCol;
+ SCROW nDBEndRow;
+
+ // Delete Autofilter
+
+ bool bChange = RemoveFlagsTab( nStartCol,nStartRow, nEndCol,nEndRow, nTab, ScMF::Auto );
+
+ // Set Autofilter
+
+ const ScDBData* pData = nullptr;
+ ScDBCollection::NamedDBs& rDBs = pDBCollection->getNamedDBs();
+ for (const auto& rxDB : rDBs)
+ {
+ if (rxDB->HasAutoFilter())
+ {
+ rxDB->GetArea(nDBTab, nDBStartCol,nDBStartRow, nDBEndCol,nDBEndRow);
+ if ( nDBTab==nTab && nDBStartRow<=nEndRow && nDBEndRow>=nStartRow &&
+ nDBStartCol<=nEndCol && nDBEndCol>=nStartCol )
+ {
+ if (ApplyFlagsTab( nDBStartCol,nDBStartRow, nDBEndCol,nDBStartRow,
+ nDBTab, ScMF::Auto ))
+ bChange = true;
+ }
+ }
+ }
+ if (nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ pData = maTabs[nTab]->GetAnonymousDBData();
+ else
+ pData=nullptr;
+ if (pData && pData->HasAutoFilter())
+ {
+ pData->GetArea( nDBTab, nDBStartCol,nDBStartRow, nDBEndCol,nDBEndRow );
+ if ( nDBTab==nTab && nDBStartRow<=nEndRow && nDBEndRow>=nStartRow &&
+ nDBStartCol<=nEndCol && nDBEndCol>=nStartCol )
+ {
+ if (ApplyFlagsTab( nDBStartCol,nDBStartRow, nDBEndCol,nDBStartRow,
+ nDBTab, ScMF::Auto ))
+ bChange = true;
+ }
+ }
+ return bChange;
+}
+
+void ScDocument::SkipOverlapped( SCCOL& rCol, SCROW& rRow, SCTAB nTab ) const
+{
+ while (IsHorOverlapped(rCol, rRow, nTab))
+ --rCol;
+ while (IsVerOverlapped(rCol, rRow, nTab))
+ --rRow;
+}
+
+bool ScDocument::IsHorOverlapped( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
+{
+ const ScMergeFlagAttr* pAttr = GetAttr( nCol, nRow, nTab, ATTR_MERGE_FLAG );
+ if (pAttr)
+ return pAttr->IsHorOverlapped();
+ else
+ {
+ OSL_FAIL("Overlapped: Attr==0");
+ return false;
+ }
+}
+
+bool ScDocument::IsVerOverlapped( SCCOL nCol, SCROW nRow, SCTAB nTab, SCROW* nStartRow, SCROW* nEndRow ) const
+{
+ SCROW dummy;
+ const ScMergeFlagAttr* pAttr = GetAttr( nCol, nRow, nTab, ATTR_MERGE_FLAG,
+ nStartRow ? *nStartRow : dummy, nEndRow ? *nEndRow : dummy );
+ if (pAttr)
+ return pAttr->IsVerOverlapped();
+ else
+ {
+ OSL_FAIL("Overlapped: Attr==0");
+ return false;
+ }
+}
+
+void ScDocument::ApplySelectionFrame( const ScMarkData& rMark,
+ const SvxBoxItem& rLineOuter,
+ const SvxBoxInfoItem* pLineInner )
+{
+ ScRangeList aRangeList;
+ rMark.FillRangeListWithMarks( &aRangeList, false );
+ size_t nRangeCount = aRangeList.size();
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+
+ if (maTabs[rTab])
+ {
+ for ( size_t j=0; j < nRangeCount; j++ )
+ {
+ const ScRange & rRange = aRangeList[ j ];
+ maTabs[rTab]->ApplyBlockFrame( rLineOuter, pLineInner,
+ rRange.aStart.Col(), rRange.aStart.Row(),
+ rRange.aEnd.Col(), rRange.aEnd.Row() );
+ }
+ }
+ }
+ if (!rLineOuter.IsRemoveAdjacentCellBorder())
+ return;
+
+ SvxBoxItem aTmp0(rLineOuter);
+ aTmp0.SetLine( nullptr, SvxBoxItemLine::TOP );
+ aTmp0.SetLine( nullptr, SvxBoxItemLine::BOTTOM );
+ aTmp0.SetLine( nullptr, SvxBoxItemLine::LEFT );
+ aTmp0.SetLine( nullptr, SvxBoxItemLine::RIGHT );
+ SvxBoxItem aLeft( aTmp0 );
+ SvxBoxItem aRight( aTmp0 );
+ SvxBoxItem aTop( aTmp0 );
+ SvxBoxItem aBottom( aTmp0 );
+
+ SvxBoxInfoItem aTmp1( *pLineInner );
+ aTmp1.SetTable( false );
+ aTmp1.SetLine( nullptr, SvxBoxInfoItemLine::HORI );
+ aTmp1.SetLine( nullptr, SvxBoxInfoItemLine::VERT );
+ aTmp1.SetValid( SvxBoxInfoItemValidFlags::ALL, false );
+ aTmp1.SetValid( SvxBoxInfoItemValidFlags::DISTANCE );
+ SvxBoxInfoItem aLeftInfo( aTmp1 );
+ SvxBoxInfoItem aRightInfo( aTmp1 );
+ SvxBoxInfoItem aTopInfo( aTmp1 );
+ SvxBoxInfoItem aBottomInfo( aTmp1 );
+
+ if (pLineInner->IsValid( SvxBoxInfoItemValidFlags::TOP ) && !rLineOuter.GetTop())
+ aTopInfo.SetValid( SvxBoxInfoItemValidFlags::BOTTOM );
+
+ if (pLineInner->IsValid( SvxBoxInfoItemValidFlags::BOTTOM ) && !rLineOuter.GetBottom())
+ aBottomInfo.SetValid( SvxBoxInfoItemValidFlags::TOP );
+
+ if (pLineInner->IsValid( SvxBoxInfoItemValidFlags::LEFT ) && !rLineOuter.GetLeft())
+ aLeftInfo.SetValid( SvxBoxInfoItemValidFlags::RIGHT );
+
+ if (pLineInner->IsValid( SvxBoxInfoItemValidFlags::RIGHT ) && !rLineOuter.GetRight())
+ aRightInfo.SetValid( SvxBoxInfoItemValidFlags::LEFT );
+
+ const ScRangeList& rRangeListTopEnvelope = rMark.GetTopEnvelope();
+ const ScRangeList& rRangeListBottomEnvelope = rMark.GetBottomEnvelope();
+ const ScRangeList& rRangeListLeftEnvelope = rMark.GetLeftEnvelope();
+ const ScRangeList& rRangeListRightEnvelope = rMark.GetRightEnvelope();
+
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+
+ if ( maTabs[rTab] )
+ {
+ size_t nEnvelopeRangeCount = rRangeListTopEnvelope.size();
+ for ( size_t j=0; j < nEnvelopeRangeCount; j++ )
+ {
+ const ScRange & rRange = rRangeListTopEnvelope[ j ];
+ maTabs[rTab]->ApplyBlockFrame( aTop, &aTopInfo,
+ rRange.aStart.Col(), rRange.aStart.Row(),
+ rRange.aEnd.Col(), rRange.aEnd.Row() );
+ }
+ nEnvelopeRangeCount = rRangeListBottomEnvelope.size();
+ for ( size_t j=0; j < nEnvelopeRangeCount; j++ )
+ {
+ const ScRange & rRange = rRangeListBottomEnvelope[ j ];
+ maTabs[rTab]->ApplyBlockFrame( aBottom, &aBottomInfo,
+ rRange.aStart.Col(), rRange.aStart.Row(),
+ rRange.aEnd.Col(), rRange.aEnd.Row() );
+ }
+ nEnvelopeRangeCount = rRangeListLeftEnvelope.size();
+ for ( size_t j=0; j < nEnvelopeRangeCount; j++ )
+ {
+ const ScRange & rRange = rRangeListLeftEnvelope[ j ];
+ maTabs[rTab]->ApplyBlockFrame( aLeft, &aLeftInfo,
+ rRange.aStart.Col(), rRange.aStart.Row(),
+ rRange.aEnd.Col(), rRange.aEnd.Row() );
+ }
+ nEnvelopeRangeCount = rRangeListRightEnvelope.size();
+ for ( size_t j=0; j < nEnvelopeRangeCount; j++ )
+ {
+ const ScRange & rRange = rRangeListRightEnvelope[ j ];
+ maTabs[rTab]->ApplyBlockFrame( aRight, &aRightInfo,
+ rRange.aStart.Col(), rRange.aStart.Row(),
+ rRange.aEnd.Col(), rRange.aEnd.Row() );
+ }
+ }
+ }
+}
+
+void ScDocument::ApplyFrameAreaTab(const ScRange& rRange,
+ const SvxBoxItem& rLineOuter,
+ const SvxBoxInfoItem& rLineInner)
+{
+ SCTAB nStartTab = rRange.aStart.Tab();
+ SCTAB nEndTab = rRange.aStart.Tab();
+ for (SCTAB nTab=nStartTab; nTab<=nEndTab && nTab < static_cast<SCTAB>(maTabs.size()); nTab++)
+ if (maTabs[nTab])
+ maTabs[nTab]->ApplyBlockFrame(rLineOuter, &rLineInner,
+ rRange.aStart.Col(), rRange.aStart.Row(),
+ rRange.aEnd.Col(), rRange.aEnd.Row());
+}
+
+void ScDocument::ApplySelectionPattern( const ScPatternAttr& rAttr, const ScMarkData& rMark, ScEditDataArray* pDataArray, bool* const pIsChanged )
+{
+ const SfxItemSet* pSet = &rAttr.GetItemSet();
+ bool bSet = false;
+ sal_uInt16 i;
+ for (i=ATTR_PATTERN_START; i<=ATTR_PATTERN_END && !bSet; i++)
+ if (pSet->GetItemState(i) == SfxItemState::SET)
+ bSet = true;
+
+ if (!bSet)
+ return;
+
+ // ApplySelectionCache needs multi mark
+ if ( rMark.IsMarked() && !rMark.IsMultiMarked() )
+ {
+ const ScRange& aRange = rMark.GetMarkArea();
+ ApplyPatternArea( aRange.aStart.Col(), aRange.aStart.Row(),
+ aRange.aEnd.Col(), aRange.aEnd.Row(), rMark, rAttr, pDataArray, pIsChanged );
+ }
+ else
+ {
+ SfxItemPoolCache aCache( mxPoolHelper->GetDocPool(), pSet );
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+ if (maTabs[rTab])
+ maTabs[rTab]->ApplySelectionCache( &aCache, rMark, pDataArray, pIsChanged );
+ }
+ }
+}
+
+void ScDocument::ChangeSelectionIndent( bool bIncrement, const ScMarkData& rMark )
+{
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+ if (maTabs[rTab])
+ maTabs[rTab]->ChangeSelectionIndent( bIncrement, rMark );
+ }
+}
+
+void ScDocument::ClearSelectionItems( const sal_uInt16* pWhich, const ScMarkData& rMark )
+{
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+ if (maTabs[rTab])
+ maTabs[rTab]->ClearSelectionItems( pWhich, rMark );
+ }
+}
+
+void ScDocument::DeleteSelection( InsertDeleteFlags nDelFlag, const ScMarkData& rMark, bool bBroadcast )
+{
+ sc::AutoCalcSwitch aACSwitch(*this, false);
+
+ std::vector<ScAddress> aGroupPos;
+ // Destroy and reconstruct listeners only if content is affected.
+ bool bDelContent = ((nDelFlag & ~InsertDeleteFlags::CONTENTS) != nDelFlag);
+ if (bDelContent)
+ {
+ // Record the positions of top and/or bottom formula groups that
+ // intersect the area borders.
+ sc::EndListeningContext aCxt(*this);
+ ScRangeList aRangeList;
+ rMark.FillRangeListWithMarks( &aRangeList, false);
+ for (size_t i = 0; i < aRangeList.size(); ++i)
+ {
+ const ScRange & rRange = aRangeList[i];
+ EndListeningIntersectedGroups( aCxt, rRange, &aGroupPos);
+ }
+ aCxt.purgeEmptyBroadcasters();
+ }
+
+ SCTAB nMax = static_cast<SCTAB>(maTabs.size());
+ for (const auto& rTab : rMark)
+ {
+ if (rTab >= nMax)
+ break;
+ if (maTabs[rTab])
+ maTabs[rTab]->DeleteSelection(nDelFlag, rMark, bBroadcast);
+ }
+
+ if (!bDelContent)
+ return;
+
+ // Re-start listeners on those top bottom groups that have been split.
+ SetNeedsListeningGroups(aGroupPos);
+ StartNeededListeners();
+
+ // If formula groups were split their listeners were destroyed and may
+ // need to be notified now that they're restored,
+ // ScTable::DeleteSelection() couldn't do that.
+ if (aGroupPos.empty())
+ return;
+
+ ScRangeList aRangeList;
+ rMark.FillRangeListWithMarks( &aRangeList, false);
+ for (size_t i = 0; i < aRangeList.size(); ++i)
+ {
+ SetDirty( aRangeList[i], true);
+ }
+ //Notify listeners on top and bottom of the group that has been split
+ for (size_t i = 0; i < aGroupPos.size(); ++i) {
+ ScFormulaCell *pFormulaCell = GetFormulaCell(aGroupPos[i]);
+ if (pFormulaCell)
+ pFormulaCell->SetDirty(true);
+ }
+}
+
+void ScDocument::DeleteSelectionTab(
+ SCTAB nTab, InsertDeleteFlags nDelFlag, const ScMarkData& rMark )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ {
+ sc::AutoCalcSwitch aACSwitch(*this, false);
+
+ std::vector<ScAddress> aGroupPos;
+ // Destroy and reconstruct listeners only if content is affected.
+ bool bDelContent = ((nDelFlag & ~InsertDeleteFlags::CONTENTS) != nDelFlag);
+ if (bDelContent)
+ {
+ // Record the positions of top and/or bottom formula groups that
+ // intersect the area borders.
+ sc::EndListeningContext aCxt(*this);
+ ScRangeList aRangeList;
+ rMark.FillRangeListWithMarks( &aRangeList, false);
+ for (size_t i = 0; i < aRangeList.size(); ++i)
+ {
+ const ScRange & rRange = aRangeList[i];
+ if (rRange.aStart.Tab() <= nTab && nTab <= rRange.aEnd.Tab())
+ {
+ ScRange aRange( rRange);
+ aRange.aStart.SetTab( nTab);
+ aRange.aEnd.SetTab( nTab);
+ EndListeningIntersectedGroups( aCxt, aRange, &aGroupPos);
+ }
+ }
+ aCxt.purgeEmptyBroadcasters();
+ }
+
+ maTabs[nTab]->DeleteSelection(nDelFlag, rMark);
+
+ if (bDelContent)
+ {
+ // Re-start listeners on those top bottom groups that have been split.
+ SetNeedsListeningGroups(aGroupPos);
+ StartNeededListeners();
+
+ // If formula groups were split their listeners were destroyed and may
+ // need to be notified now that they're restored,
+ // ScTable::DeleteSelection() couldn't do that.
+ if (!aGroupPos.empty())
+ {
+ ScRangeList aRangeList;
+ rMark.FillRangeListWithMarks( &aRangeList, false);
+ for (size_t i = 0; i < aRangeList.size(); ++i)
+ {
+ const ScRange & rRange = aRangeList[i];
+ if (rRange.aStart.Tab() <= nTab && nTab <= rRange.aEnd.Tab())
+ {
+ ScRange aRange( rRange);
+ aRange.aStart.SetTab( nTab);
+ aRange.aEnd.SetTab( nTab);
+ SetDirty( aRange, true);
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ OSL_FAIL("wrong table");
+ }
+}
+
+ScPatternAttr* ScDocument::GetDefPattern() const
+{
+ return const_cast<ScPatternAttr*>(&mxPoolHelper->GetDocPool()->GetDefaultItem(ATTR_PATTERN));
+}
+
+ScDocumentPool* ScDocument::GetPool()
+{
+ return mxPoolHelper ? mxPoolHelper->GetDocPool() : nullptr;
+}
+
+ScStyleSheetPool* ScDocument::GetStyleSheetPool() const
+{
+ return mxPoolHelper ? mxPoolHelper->GetStylePool() : nullptr;
+}
+
+bool ScDocument::IsEmptyData(SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, SCTAB nTab) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->IsEmptyData(nStartCol, nStartRow, nEndCol, nEndRow);
+ return true;
+}
+
+SCSIZE ScDocument::GetEmptyLinesInBlock( SCCOL nStartCol, SCROW nStartRow, SCTAB nStartTab,
+ SCCOL nEndCol, SCROW nEndRow, SCTAB nEndTab, ScDirection eDir )
+{
+ PutInOrder(nStartCol, nEndCol);
+ PutInOrder(nStartRow, nEndRow);
+ PutInOrder(nStartTab, nEndTab);
+ if (ValidTab(nStartTab) && nStartTab < static_cast<SCTAB>(maTabs.size()))
+ {
+ if (maTabs[nStartTab])
+ return maTabs[nStartTab]->GetEmptyLinesInBlock(nStartCol, nStartRow, nEndCol, nEndRow, eDir);
+ else
+ return 0;
+ }
+ else
+ return 0;
+}
+
+void ScDocument::FindAreaPos( SCCOL& rCol, SCROW& rRow, SCTAB nTab, ScMoveDirection eDirection ) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->FindAreaPos( rCol, rRow, eDirection );
+}
+
+void ScDocument::GetNextPos( SCCOL& rCol, SCROW& rRow, SCTAB nTab, SCCOL nMovX, SCROW nMovY,
+ bool bMarked, bool bUnprotected, const ScMarkData& rMark, SCCOL nTabStartCol ) const
+{
+ OSL_ENSURE( !nMovX || !nMovY, "GetNextPos: only X or Y" );
+
+ ScMarkData aCopyMark = rMark;
+ aCopyMark.SetMarking(false);
+ aCopyMark.MarkToMulti();
+
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->GetNextPos( rCol, rRow, nMovX, nMovY, bMarked, bUnprotected, aCopyMark, nTabStartCol );
+}
+
+// Data operations
+
+void ScDocument::UpdStlShtPtrsFrmNms()
+{
+ ScDocumentPool* pPool = mxPoolHelper->GetDocPool();
+
+ for (const SfxPoolItem* pItem : pPool->GetItemSurrogates(ATTR_PATTERN))
+ {
+ auto pPattern = const_cast<ScPatternAttr*>(dynamic_cast<const ScPatternAttr*>(pItem));
+ if (pPattern)
+ pPattern->UpdateStyleSheet(*this);
+ }
+ const_cast<ScPatternAttr&>(pPool->GetDefaultItem(ATTR_PATTERN)).UpdateStyleSheet(*this);
+}
+
+void ScDocument::StylesToNames()
+{
+ ScDocumentPool* pPool = mxPoolHelper->GetDocPool();
+
+ for (const SfxPoolItem* pItem : pPool->GetItemSurrogates(ATTR_PATTERN))
+ {
+ auto pPattern = const_cast<ScPatternAttr*>(dynamic_cast<const ScPatternAttr*>(pItem));
+ if (pPattern)
+ pPattern->StyleToName();
+ }
+ const_cast<ScPatternAttr&>(pPool->GetDefaultItem(ATTR_PATTERN)).StyleToName();
+}
+
+sal_uInt64 ScDocument::GetCellCount() const
+{
+ sal_uInt64 nCellCount = 0;
+
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ nCellCount += a->GetCellCount();
+ }
+
+ return nCellCount;
+}
+
+sal_uInt64 ScDocument::GetFormulaGroupCount() const
+{
+ sal_uInt64 nFormulaGroupCount = 0;
+
+ ScFormulaGroupIterator aIter( *const_cast<ScDocument*>(this) );
+ for ( sc::FormulaGroupEntry* ptr = aIter.first(); ptr; ptr = aIter.next())
+ {
+ nFormulaGroupCount++;
+ }
+
+ return nFormulaGroupCount;
+}
+
+sal_uInt64 ScDocument::GetCodeCount() const
+{
+ sal_uInt64 nCodeCount = 0;
+
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ nCodeCount += a->GetCodeCount();
+ }
+
+ return nCodeCount;
+}
+
+void ScDocument::PageStyleModified( SCTAB nTab, const OUString& rNewName )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->PageStyleModified( rNewName );
+}
+
+void ScDocument::SetPageStyle( SCTAB nTab, const OUString& rName )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->SetPageStyle( rName );
+}
+
+OUString ScDocument::GetPageStyle( SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetPageStyle();
+
+ return OUString();
+}
+
+void ScDocument::SetPageSize( SCTAB nTab, const Size& rSize )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->SetPageSize( rSize );
+}
+
+Size ScDocument::GetPageSize( SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->GetPageSize();
+
+ OSL_FAIL("invalid tab");
+ return Size();
+}
+
+void ScDocument::SetRepeatArea( SCTAB nTab, SCCOL nStartCol, SCCOL nEndCol, SCROW nStartRow, SCROW nEndRow )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->SetRepeatArea( nStartCol, nEndCol, nStartRow, nEndRow );
+}
+
+void ScDocument::InvalidatePageBreaks(SCTAB nTab)
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->InvalidatePageBreaks();
+}
+
+void ScDocument::UpdatePageBreaks( SCTAB nTab, const ScRange* pUserArea )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->UpdatePageBreaks( pUserArea );
+}
+
+void ScDocument::RemoveManualBreaks( SCTAB nTab )
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ maTabs[nTab]->RemoveManualBreaks();
+}
+
+bool ScDocument::HasManualBreaks( SCTAB nTab ) const
+{
+ if ( ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] )
+ return maTabs[nTab]->HasManualBreaks();
+
+ OSL_FAIL("invalid tab");
+ return false;
+}
+
+void ScDocument::GetDocStat( ScDocStat& rDocStat )
+{
+ rDocStat.nTableCount = GetTableCount();
+ rDocStat.aDocName = aDocName;
+ rDocStat.nFormulaCount = GetFormulaGroupCount();
+ rDocStat.nCellCount = GetCellCount();
+}
+
+bool ScDocument::HasPrintRange()
+{
+ bool bResult = false;
+
+ for (const auto& a : maTabs)
+ {
+ if (!a)
+ continue;
+ bResult = a->IsPrintEntireSheet() || (a->GetPrintRangeCount() > 0);
+ if (bResult)
+ break;
+ }
+
+ return bResult;
+}
+
+bool ScDocument::IsPrintEntireSheet( SCTAB nTab ) const
+{
+ return (ValidTab(nTab) ) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->IsPrintEntireSheet();
+}
+
+sal_uInt16 ScDocument::GetPrintRangeCount( SCTAB nTab )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetPrintRangeCount();
+
+ return 0;
+}
+
+const ScRange* ScDocument::GetPrintRange( SCTAB nTab, sal_uInt16 nPos )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetPrintRange(nPos);
+
+ return nullptr;
+}
+
+std::optional<ScRange> ScDocument::GetRepeatColRange( SCTAB nTab )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetRepeatColRange();
+
+ return std::nullopt;
+}
+
+std::optional<ScRange> ScDocument::GetRepeatRowRange( SCTAB nTab )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetRepeatRowRange();
+
+ return std::nullopt;
+}
+
+void ScDocument::ClearPrintRanges( SCTAB nTab )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->ClearPrintRanges();
+}
+
+void ScDocument::AddPrintRange( SCTAB nTab, const ScRange& rNew )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->AddPrintRange( rNew );
+}
+
+void ScDocument::SetPrintEntireSheet( SCTAB nTab )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->SetPrintEntireSheet();
+}
+
+void ScDocument::SetRepeatColRange( SCTAB nTab, std::optional<ScRange> oNew )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->SetRepeatColRange( std::move(oNew) );
+}
+
+void ScDocument::SetRepeatRowRange( SCTAB nTab, std::optional<ScRange> oNew )
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->SetRepeatRowRange( std::move(oNew) );
+}
+
+std::unique_ptr<ScPrintRangeSaver> ScDocument::CreatePrintRangeSaver() const
+{
+ const SCTAB nCount = static_cast<SCTAB>(maTabs.size());
+ std::unique_ptr<ScPrintRangeSaver> pNew(new ScPrintRangeSaver( nCount ));
+ for (SCTAB i=0; i<nCount; i++)
+ if (maTabs[i])
+ maTabs[i]->FillPrintSaver( pNew->GetTabData(i) );
+ return pNew;
+}
+
+void ScDocument::RestorePrintRanges( const ScPrintRangeSaver& rSaver )
+{
+ const SCTAB nCount = rSaver.GetTabCount();
+ const SCTAB maxIndex = std::min(nCount, static_cast<SCTAB>(maTabs.size()));
+ for (SCTAB i=0; i<maxIndex; i++)
+ if (maTabs[i])
+ maTabs[i]->RestorePrintRanges( rSaver.GetTabData(i) );
+}
+
+bool ScDocument::NeedPageResetAfterTab( SCTAB nTab ) const
+{
+ // The page number count restarts at a sheet, if another template is set at
+ // the preceding one (only compare names) and if a pagenumber is specified (not 0)
+
+ if ( nTab + 1 < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab] && maTabs[nTab+1] )
+ {
+ const OUString & rNew = maTabs[nTab+1]->GetPageStyle();
+ if ( rNew != maTabs[nTab]->GetPageStyle() )
+ {
+ SfxStyleSheetBase* pStyle = mxPoolHelper->GetStylePool()->Find( rNew, SfxStyleFamily::Page );
+ if ( pStyle )
+ {
+ const SfxItemSet& rSet = pStyle->GetItemSet();
+ sal_uInt16 nFirst = rSet.Get(ATTR_PAGE_FIRSTPAGENO).GetValue();
+ if ( nFirst != 0 )
+ return true; // Specify page number in new template
+ }
+ }
+ }
+
+ return false; // otherwise not
+}
+
+SfxUndoManager* ScDocument::GetUndoManager()
+{
+ if (!mpUndoManager)
+ {
+ // to support enhanced text edit for draw objects, use an SdrUndoManager
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+
+ SdrUndoManager* pUndoManager = new SdrUndoManager;
+ pUndoManager->SetDocShell(GetDocumentShell());
+ mpUndoManager = pUndoManager;
+ }
+
+ return mpUndoManager;
+}
+
+ScRowBreakIterator* ScDocument::GetRowBreakIterator(SCTAB nTab) const
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return new ScRowBreakIterator(maTabs[nTab]->maRowPageBreaks);
+ return nullptr;
+}
+
+void ScDocument::AddSubTotalCell(ScFormulaCell* pCell)
+{
+ maSubTotalCells.insert(pCell);
+}
+
+void ScDocument::RemoveSubTotalCell(ScFormulaCell* pCell)
+{
+ maSubTotalCells.erase(pCell);
+}
+
+namespace {
+
+bool lcl_hasDirtyRange(const ScDocument& rDoc, ScFormulaCell* pCell, const ScRange& rDirtyRange)
+{
+ ScDetectiveRefIter aRefIter(rDoc, pCell);
+ ScRange aRange;
+ while (aRefIter.GetNextRef(aRange))
+ {
+ if (aRange.Intersects(rDirtyRange))
+ return true;
+ }
+ return false;
+}
+
+}
+
+void ScDocument::SetSubTotalCellsDirty(const ScRange& rDirtyRange)
+{
+ // to update the list by skipping cells that no longer contain subtotal function.
+ set<ScFormulaCell*> aNewSet;
+
+ bool bOldRecalc = GetAutoCalc();
+ SetAutoCalc(false);
+ for (ScFormulaCell* pCell : maSubTotalCells)
+ {
+ if (pCell->IsSubTotal())
+ {
+ aNewSet.insert(pCell);
+ if (lcl_hasDirtyRange(*this, pCell, rDirtyRange))
+ pCell->SetDirty();
+ }
+ }
+
+ SetAutoCalc(bOldRecalc);
+ maSubTotalCells.swap(aNewSet); // update the list.
+}
+
+sal_uInt16 ScDocument::GetTextWidth( const ScAddress& rPos ) const
+{
+ SCTAB nTab = rPos.Tab();
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetTextWidth(rPos.Col(), rPos.Row());
+
+ return 0;
+}
+
+SvtScriptType ScDocument::GetScriptType( const ScAddress& rPos ) const
+{
+ SCTAB nTab = rPos.Tab();
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ return maTabs[nTab]->GetScriptType(rPos.Col(), rPos.Row());
+
+ return SvtScriptType::NONE;
+}
+
+void ScDocument::SetScriptType( const ScAddress& rPos, SvtScriptType nType )
+{
+ SCTAB nTab = rPos.Tab();
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()) && maTabs[nTab])
+ maTabs[nTab]->SetScriptType(rPos.Col(), rPos.Row(), nType);
+}
+
+void ScDocument::EnableUndo( bool bVal )
+{
+ // The undo manager increases lock count every time undo is disabled.
+ // Because of this, we shouldn't disable undo unless it's currently
+ // enabled, or else re-enabling it may not actually re-enable undo unless
+ // the lock count becomes zero.
+
+ if (bVal != GetUndoManager()->IsUndoEnabled())
+ {
+ GetUndoManager()->EnableUndo(bVal);
+ if( mpDrawLayer ) mpDrawLayer->EnableUndo(bVal);
+ }
+
+ mbUndoEnabled = bVal;
+}
+
+void ScDocument::EnableUserInteraction( bool bVal )
+{
+ mbUserInteractionEnabled = bVal;
+}
+
+bool ScDocument::IsInVBAMode() const
+{
+ if (!mpShell)
+ return false;
+
+ try
+ {
+ uno::Reference<script::vba::XVBACompatibility> xVBA(
+ mpShell->GetBasicContainer(), uno::UNO_QUERY);
+
+ return xVBA.is() && xVBA->getVBACompatibilityMode();
+ }
+ catch (const lang::NotInitializedException&) {}
+
+ return false;
+}
+
+// Sparklines
+std::shared_ptr<sc::Sparkline> ScDocument::GetSparkline(ScAddress const& rPosition)
+{
+ SCTAB nTab = rPosition.Tab();
+
+ if (ValidTab(nTab) && nTab < SCTAB(maTabs.size()))
+ {
+ return maTabs[nTab]->GetSparkline(rPosition.Col(), rPosition.Row());
+ }
+ return std::shared_ptr<sc::Sparkline>();
+}
+
+bool ScDocument::HasSparkline(ScAddress const & rPosition)
+{
+ return bool(GetSparkline(rPosition));
+}
+
+sc::Sparkline* ScDocument::CreateSparkline(ScAddress const& rPosition, std::shared_ptr<sc::SparklineGroup> const& pSparklineGroup)
+{
+ SCTAB nTab = rPosition.Tab();
+
+ if (ValidTab(nTab) && nTab < SCTAB(maTabs.size()))
+ {
+ return maTabs[nTab]->CreateSparkline(rPosition.Col(), rPosition.Row(), pSparklineGroup);
+ }
+
+ return nullptr;
+}
+
+bool ScDocument::DeleteSparkline(ScAddress const & rPosition)
+{
+ SCTAB nTab = rPosition.Tab();
+
+ if (TableExists(nTab))
+ {
+ return maTabs[nTab]->DeleteSparkline(rPosition.Col(), rPosition.Row());
+ }
+
+ return false;
+}
+
+sc::SparklineList* ScDocument::GetSparklineList(SCTAB nTab)
+{
+ if (TableExists(nTab))
+ {
+ return &maTabs[nTab]->GetSparklineList();
+ }
+ return nullptr;
+}
+
+bool ScDocument::HasOneSparklineGroup(ScRange const& rRange)
+{
+ std::shared_ptr<sc::SparklineGroup> pSparklineGroup;
+ return GetSparklineGroupInRange(rRange, pSparklineGroup);
+}
+
+bool ScDocument::GetSparklineGroupInRange(ScRange const& rRange, std::shared_ptr<sc::SparklineGroup>& rGroup)
+{
+ std::shared_ptr<sc::SparklineGroup> pFoundGroup;
+ SCTAB nTab = rRange.aStart.Tab();
+
+ for (SCCOL nX = rRange.aStart.Col(); nX <= rRange.aEnd.Col(); nX++)
+ {
+ for (SCROW nY = rRange.aStart.Row(); nY <= rRange.aEnd.Row(); nY++)
+ {
+ auto pSparkline = GetSparkline(ScAddress(nX, nY, nTab));
+ if (!pSparkline)
+ {
+ return false;
+ }
+ else if (!pFoundGroup)
+ {
+ pFoundGroup = pSparkline->getSparklineGroup();
+ }
+ else if (pFoundGroup != pSparkline->getSparklineGroup())
+ {
+ return false;
+ }
+ }
+ }
+
+ rGroup = pFoundGroup;
+ return true;
+}
+
+std::shared_ptr<sc::SparklineGroup> ScDocument::SearchSparklineGroup(tools::Guid const& rGuid)
+{
+ for (auto const& rTable : maTabs)
+ {
+ if (!rTable)
+ continue;
+
+ auto& rSparklineList = rTable->GetSparklineList();
+
+ for (auto const& pSparklineGroup : rSparklineList.getSparklineGroups())
+ {
+ if (pSparklineGroup->getID() == rGuid)
+ return pSparklineGroup;
+ }
+ }
+
+ return std::shared_ptr<sc::SparklineGroup>();
+}
+
+// Notes
+
+ScPostIt* ScDocument::GetNote(const ScAddress& rPos)
+{
+ return GetNote(rPos.Col(), rPos.Row(), rPos.Tab());
+}
+
+ScPostIt* ScDocument::GetNote(SCCOL nCol, SCROW nRow, SCTAB nTab)
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ return maTabs[nTab]->GetNote(nCol, nRow);
+ else
+ return nullptr;
+
+}
+
+void ScDocument::SetNote(const ScAddress& rPos, std::unique_ptr<ScPostIt> pNote)
+{
+ return SetNote(rPos.Col(), rPos.Row(), rPos.Tab(), std::move(pNote));
+}
+
+void ScDocument::SetNote(SCCOL nCol, SCROW nRow, SCTAB nTab, std::unique_ptr<ScPostIt> pNote)
+{
+ if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabs.size()))
+ maTabs[nTab]->SetNote(nCol, nRow, std::move(pNote));
+}
+
+bool ScDocument::HasNote(const ScAddress& rPos) const
+{
+ return HasNote(rPos.Col(), rPos.Row(), rPos.Tab());
+}
+
+bool ScDocument::HasNote(SCCOL nCol, SCROW nRow, SCTAB nTab) const
+{
+ if (!ValidColRow(nCol, nRow))
+ return false;
+
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return false;
+
+ if (nCol >= pTab->GetAllocatedColumnsCount())
+ return false;
+
+ const ScPostIt* pNote = pTab->aCol[nCol].GetCellNote(nRow);
+ return pNote != nullptr;
+}
+
+bool ScDocument::HasNote(SCTAB nTab, SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return false;
+
+ nStartCol = pTab->ClampToAllocatedColumns(nStartCol);
+ nEndCol = pTab->ClampToAllocatedColumns(nEndCol);
+ for (SCCOL nCol = nStartCol; nCol < nEndCol; ++nCol)
+ if (pTab->aCol[nCol].HasCellNote(nStartRow, nEndRow))
+ return true;
+ return false;
+}
+
+bool ScDocument::HasColNotes(SCCOL nCol, SCTAB nTab) const
+{
+ if (!ValidCol(nCol))
+ return false;
+
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return false;
+
+ if (nCol >= pTab->GetAllocatedColumnsCount())
+ return false;
+
+ return pTab->aCol[nCol].HasCellNotes();
+}
+
+bool ScDocument::HasTabNotes(SCTAB nTab) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+
+ if ( !pTab )
+ return false;
+
+ for (SCCOL nCol=0, nColSize = pTab->aCol.size(); nCol < nColSize; ++nCol)
+ if ( HasColNotes(nCol, nTab) )
+ return true;
+
+ return false;
+}
+
+bool ScDocument::HasNotes() const
+{
+ for (SCTAB i = 0; i <= MAXTAB; ++i)
+ {
+ if (HasTabNotes(i))
+ return true;
+ }
+ return false;
+}
+
+std::unique_ptr<ScPostIt> ScDocument::ReleaseNote(const ScAddress& rPos)
+{
+ ScTable* pTab = FetchTable(rPos.Tab());
+ if (!pTab)
+ return nullptr;
+
+ return pTab->ReleaseNote(rPos.Col(), rPos.Row());
+}
+
+ScPostIt* ScDocument::GetOrCreateNote(const ScAddress& rPos)
+{
+ if (HasNote(rPos))
+ return GetNote(rPos);
+ else
+ return CreateNote(rPos);
+}
+
+ScPostIt* ScDocument::CreateNote(const ScAddress& rPos)
+{
+ ScPostIt* pPostIt = new ScPostIt(*this, rPos);
+ SetNote(rPos, std::unique_ptr<ScPostIt>(pPostIt));
+ return pPostIt;
+}
+
+size_t ScDocument::GetNoteCount( SCTAB nTab, SCCOL nCol ) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return 0;
+
+ return pTab->GetNoteCount(nCol);
+}
+
+void ScDocument::CreateAllNoteCaptions()
+{
+ for (const auto& a : maTabs)
+ {
+ if (a)
+ a->CreateAllNoteCaptions();
+ }
+}
+
+void ScDocument::ForgetNoteCaptions( const ScRangeList& rRanges, bool bPreserveData )
+{
+ for (size_t i = 0, n = rRanges.size(); i < n; ++i)
+ {
+ const ScRange & rRange = rRanges[i];
+ const ScAddress& s = rRange.aStart;
+ const ScAddress& e = rRange.aEnd;
+ for (SCTAB nTab = s.Tab(); nTab <= e.Tab(); ++nTab)
+ {
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ continue;
+
+ pTab->ForgetNoteCaptions(s.Col(), s.Row(), e.Col(), e.Row(), bPreserveData);
+ }
+ }
+}
+
+CommentCaptionState ScDocument::GetAllNoteCaptionsState( const ScRangeList& rRanges )
+{
+ CommentCaptionState aTmpState = CommentCaptionState::ALLHIDDEN;
+ CommentCaptionState aState = CommentCaptionState::ALLHIDDEN;
+ bool bFirstControl = true;
+ std::vector<sc::NoteEntry> aNotes;
+
+ for (size_t i = 0, n = rRanges.size(); i < n; ++i)
+ {
+ const ScRange & rRange = rRanges[i];
+
+ for( SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab )
+ {
+ aState = maTabs[nTab]->GetAllNoteCaptionsState( rRange, aNotes );
+
+ if (aState == CommentCaptionState::MIXED)
+ return aState;
+
+ if (bFirstControl) // it is possible that a range is ALLSHOWN, another range is ALLHIDDEN,
+ { // we have to detect that situation as mixed.
+ aTmpState = aState;
+ bFirstControl = false;
+ }
+ else if(aTmpState != aState)
+ {
+ aState = CommentCaptionState::MIXED;
+ return aState;
+ }
+ }
+ }
+ return aState;
+}
+
+ScAddress ScDocument::GetNotePosition( size_t nIndex ) const
+{
+ for (size_t nTab = 0; nTab < maTabs.size(); ++nTab)
+ {
+ for (SCCOL nCol : GetAllocatedColumnsRange(nTab, 0, MaxCol()))
+ {
+ size_t nColNoteCount = GetNoteCount(nTab, nCol);
+ if (!nColNoteCount)
+ continue;
+
+ if (nIndex >= nColNoteCount)
+ {
+ nIndex -= nColNoteCount;
+ continue;
+ }
+
+ SCROW nRow = GetNotePosition(nTab, nCol, nIndex);
+ if (nRow >= 0)
+ return ScAddress(nCol, nRow, nTab);
+
+ OSL_FAIL("note not found");
+ return ScAddress::INITIALIZE_INVALID;
+ }
+ }
+
+ OSL_FAIL("note not found");
+ return ScAddress::INITIALIZE_INVALID;
+}
+
+ScAddress ScDocument::GetNotePosition( size_t nIndex, SCTAB nTab ) const
+{
+ for (SCCOL nCol : GetAllocatedColumnsRange(nTab, 0, MaxCol()))
+ {
+ size_t nColNoteCount = GetNoteCount(nTab, nCol);
+ if (!nColNoteCount)
+ continue;
+
+ if (nIndex >= nColNoteCount)
+ {
+ nIndex -= nColNoteCount;
+ continue;
+ }
+
+ SCROW nRow = GetNotePosition(nTab, nCol, nIndex);
+ if (nRow >= 0)
+ return ScAddress(nCol, nRow, nTab);
+
+ OSL_FAIL("note not found");
+ return ScAddress::INITIALIZE_INVALID;
+ }
+
+ OSL_FAIL("note not found");
+ return ScAddress::INITIALIZE_INVALID;
+}
+
+SCROW ScDocument::GetNotePosition( SCTAB nTab, SCCOL nCol, size_t nIndex ) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return -1;
+
+ return pTab->GetNotePosition(nCol, nIndex);
+}
+
+void ScDocument::GetAllNoteEntries( std::vector<sc::NoteEntry>& rNotes ) const
+{
+ for (const auto & pTab : maTabs)
+ {
+ if (!pTab)
+ continue;
+
+ pTab->GetAllNoteEntries(rNotes);
+ }
+}
+
+void ScDocument::GetAllNoteEntries( SCTAB nTab, std::vector<sc::NoteEntry>& rNotes ) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ return pTab->GetAllNoteEntries( rNotes );
+}
+
+void ScDocument::GetNotesInRange( const ScRangeList& rRangeList, std::vector<sc::NoteEntry>& rNotes ) const
+{
+ for( size_t i = 0; i < rRangeList.size(); ++i)
+ {
+ const ScRange & rRange = rRangeList[i];
+ for( SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab )
+ {
+ maTabs[nTab]->GetNotesInRange( rRange, rNotes );
+ }
+ }
+}
+
+void ScDocument::GetUnprotectedCells( ScRangeList& rRangeList, SCTAB nTab ) const
+{
+ maTabs[nTab]->GetUnprotectedCells( rRangeList );
+}
+
+bool ScDocument::ContainsNotesInRange( const ScRangeList& rRangeList ) const
+{
+ for( size_t i = 0; i < rRangeList.size(); ++i)
+ {
+ const ScRange & rRange = rRangeList[i];
+ for( SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab )
+ {
+ bool bContainsNote = maTabs[nTab]->ContainsNotesInRange( rRange );
+ if(bContainsNote)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void ScDocument::SetAutoNameCache( std::unique_ptr<ScAutoNameCache> pCache )
+{
+ pAutoNameCache = std::move(pCache);
+}
+
+thread_local ScDocumentThreadSpecific ScDocument::maThreadSpecific;
+
+ScRecursionHelper& ScDocument::GetRecursionHelper()
+{
+ if (!IsThreadedGroupCalcInProgress())
+ {
+ if (!maNonThreaded.xRecursionHelper)
+ maNonThreaded.xRecursionHelper = std::make_unique<ScRecursionHelper>();
+ return *maNonThreaded.xRecursionHelper;
+ }
+ else
+ {
+ if (!maThreadSpecific.xRecursionHelper)
+ maThreadSpecific.xRecursionHelper = std::make_unique<ScRecursionHelper>();
+ return *maThreadSpecific.xRecursionHelper;
+ }
+}
+
+void ScDocument::SetupContextFromNonThreadedContext(ScInterpreterContext& /*threadedContext*/, int /*threadNumber*/)
+{
+ (void)this;
+ // lookup cache is now only in pooled ScInterpreterContext's
+}
+
+void ScDocument::MergeContextBackIntoNonThreadedContext(ScInterpreterContext& threadedContext, int /*threadNumber*/)
+{
+ // Move data from a context used by a calculation thread to the main thread's context.
+ // Called from the main thread after the calculation thread has already finished.
+ assert(!IsThreadedGroupCalcInProgress());
+ maInterpreterContext.maDelayedSetNumberFormat.insert(
+ maInterpreterContext.maDelayedSetNumberFormat.end(),
+ std::make_move_iterator(threadedContext.maDelayedSetNumberFormat.begin()),
+ std::make_move_iterator(threadedContext.maDelayedSetNumberFormat.end()));
+ // lookup cache is now only in pooled ScInterpreterContext's
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/document10.cxx b/sc/source/core/data/document10.cxx
new file mode 100644
index 000000000..ec61fa530
--- /dev/null
+++ b/sc/source/core/data/document10.cxx
@@ -0,0 +1,1106 @@
+/* -*- 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 <memory>
+#include <document.hxx>
+#include <clipcontext.hxx>
+#include <clipparam.hxx>
+#include <table.hxx>
+#include <tokenarray.hxx>
+#include <listenercontext.hxx>
+#include <tokenstringcontext.hxx>
+#include <poolhelp.hxx>
+#include <cellvalues.hxx>
+#include <docpool.hxx>
+#include <columniterator.hxx>
+
+#include <refupdatecontext.hxx>
+#include <sal/log.hxx>
+#include <editeng/colritem.hxx>
+#include <scitems.hxx>
+#include <datamapper.hxx>
+#include <docsh.hxx>
+
+// Add totally brand-new methods to this source file.
+
+bool ScDocument::IsMerged( const ScAddress& rPos ) const
+{
+ const ScTable* pTab = FetchTable(rPos.Tab());
+ if (!pTab)
+ return false;
+
+ return pTab->IsMerged(rPos.Col(), rPos.Row());
+}
+
+sc::MultiDataCellState ScDocument::HasMultipleDataCells( const ScRange& rRange ) const
+{
+ if (rRange.aStart.Tab() != rRange.aEnd.Tab())
+ // Currently we only support a single-sheet range.
+ return sc::MultiDataCellState();
+
+ const ScTable* pTab = FetchTable(rRange.aStart.Tab());
+ if (!pTab)
+ return sc::MultiDataCellState(sc::MultiDataCellState::Empty);
+
+ const ScAddress& s = rRange.aStart;
+ const ScAddress& e = rRange.aEnd;
+ return pTab->HasMultipleDataCells(s.Col(), s.Row(), e.Col(), e.Row());
+}
+
+void ScDocument::DeleteBeforeCopyFromClip(
+ sc::CopyFromClipContext& rCxt, const ScMarkData& rMark, sc::ColumnSpanSet& rBroadcastSpans )
+{
+ SCTAB nClipTab = 0;
+ const TableContainer& rClipTabs = rCxt.getClipDoc()->maTabs;
+ SCTAB nClipTabCount = rClipTabs.size();
+
+ for (SCTAB nTab = rCxt.getTabStart(); nTab <= rCxt.getTabEnd(); ++nTab)
+ {
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ continue;
+
+ if (!rMark.GetTableSelect(nTab))
+ continue;
+
+ while (!rClipTabs[nClipTab])
+ nClipTab = (nClipTab+1) % nClipTabCount;
+
+ pTab->DeleteBeforeCopyFromClip(rCxt, *rClipTabs[nClipTab], rBroadcastSpans);
+
+ nClipTab = (nClipTab+1) % nClipTabCount;
+ }
+}
+
+bool ScDocument::CopyOneCellFromClip(
+ sc::CopyFromClipContext& rCxt, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
+{
+ ScDocument* pClipDoc = rCxt.getClipDoc();
+ if (pClipDoc->GetClipParam().mbCutMode)
+ // We don't handle cut and paste or moving of cells here.
+ return false;
+
+ ScRange aClipRange = pClipDoc->GetClipParam().getWholeRange();
+ if (aClipRange.aStart.Row() != aClipRange.aEnd.Row())
+ // The source is not really a single row. Bail out.
+ return false;
+
+ SCCOL nSrcColSize = aClipRange.aEnd.Col() - aClipRange.aStart.Col() + 1;
+ SCCOL nDestColSize = nCol2 - nCol1 + 1;
+ if (nDestColSize < nSrcColSize)
+ return false;
+
+ if (pClipDoc->maTabs.size() > 1)
+ // Copying from multiple source sheets is not handled here.
+ return false;
+
+ ScAddress aSrcPos = aClipRange.aStart;
+
+ for (SCCOL nCol = aClipRange.aStart.Col(); nCol <= aClipRange.aEnd.Col(); ++nCol)
+ {
+ ScAddress aTestPos = aSrcPos;
+ aTestPos.SetCol(nCol);
+ if (pClipDoc->IsMerged(aTestPos))
+ // We don't handle merged source cell for this.
+ return false;
+ }
+
+ ScTable* pSrcTab = pClipDoc->FetchTable(aSrcPos.Tab());
+ if (!pSrcTab)
+ return false;
+
+ rCxt.setSingleCellColumnSize(nSrcColSize);
+
+ for (SCCOL nColOffset = 0; nColOffset < nSrcColSize; ++nColOffset, aSrcPos.IncCol())
+ {
+ const ScPatternAttr* pAttr = pClipDoc->GetPattern(aSrcPos);
+ rCxt.setSingleCellPattern(nColOffset, pAttr);
+
+ if ((rCxt.getInsertFlag() & (InsertDeleteFlags::NOTE | InsertDeleteFlags::ADDNOTES)) != InsertDeleteFlags::NONE)
+ rCxt.setSingleCellNote(nColOffset, pClipDoc->GetNote(aSrcPos));
+
+ if ((rCxt.getInsertFlag() & InsertDeleteFlags::SPARKLINES) != InsertDeleteFlags::NONE)
+ rCxt.setSingleSparkline(nColOffset, pClipDoc->GetSparkline(aSrcPos));
+
+ ScColumn* pSrcCol = pSrcTab->FetchColumn(aSrcPos.Col());
+ assert(pSrcCol);
+ // Determine the script type of the copied single cell.
+ pSrcCol->UpdateScriptTypes(aSrcPos.Row(), aSrcPos.Row());
+ rCxt.setSingleCell(aSrcPos, *pSrcCol);
+ }
+
+ // All good. Proceed with the pasting.
+
+ SCTAB nTabEnd = rCxt.getTabEnd();
+ for (SCTAB i = rCxt.getTabStart(); i <= nTabEnd && i < static_cast<SCTAB>(maTabs.size()); ++i)
+ {
+ maTabs[i]->CopyOneCellFromClip(rCxt, nCol1, nRow1, nCol2, nRow2, aClipRange.aStart.Row(), pSrcTab);
+ }
+
+ sc::RefUpdateContext aRefCxt(*this);
+ aRefCxt.maRange = ScRange(nCol1, nRow1, rCxt.getTabStart(), nCol2, nRow2, nTabEnd);
+ aRefCxt.mnColDelta = nCol1 - aSrcPos.Col();
+ aRefCxt.mnRowDelta = nRow1 - aSrcPos.Row();
+ aRefCxt.mnTabDelta = rCxt.getTabStart() - aSrcPos.Tab();
+ // Only Copy&Paste, for Cut&Paste we already bailed out early.
+ aRefCxt.meMode = URM_COPY;
+ UpdateReference(aRefCxt, rCxt.getUndoDoc(), false);
+
+ return true;
+}
+
+void ScDocument::SetValues( const ScAddress& rPos, const std::vector<double>& rVals )
+{
+ ScTable* pTab = FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ pTab->SetValues(rPos.Col(), rPos.Row(), rVals);
+}
+
+void ScDocument::TransferCellValuesTo( const ScAddress& rTopPos, size_t nLen, sc::CellValues& rDest )
+{
+ ScTable* pTab = FetchTable(rTopPos.Tab());
+ if (!pTab)
+ return;
+
+ pTab->TransferCellValuesTo(rTopPos.Col(), rTopPos.Row(), nLen, rDest);
+}
+
+void ScDocument::CopyCellValuesFrom( const ScAddress& rTopPos, const sc::CellValues& rSrc )
+{
+ ScTable* pTab = FetchTable(rTopPos.Tab());
+ if (!pTab)
+ return;
+
+ pTab->CopyCellValuesFrom(rTopPos.Col(), rTopPos.Row(), rSrc);
+}
+
+std::set<Color> ScDocument::GetDocColors()
+{
+ std::set<Color> aDocColors;
+ ScDocumentPool *pPool = GetPool();
+ const sal_uInt16 pAttribs[] = {ATTR_BACKGROUND, ATTR_FONT_COLOR};
+ for (sal_uInt16 nAttrib : pAttribs)
+ {
+ for (const SfxPoolItem* pItem : pPool->GetItemSurrogates(nAttrib))
+ {
+ const SvxColorItem *pColorItem = static_cast<const SvxColorItem*>(pItem);
+ Color aColor( pColorItem->GetValue() );
+ if (COL_AUTO != aColor)
+ aDocColors.insert(aColor);
+ }
+ }
+ return aDocColors;
+}
+
+void ScDocument::SetCalcConfig( const ScCalcConfig& rConfig )
+{
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ maCalcConfig = rConfig;
+}
+
+void ScDocument::ConvertFormulaToValue( const ScRange& rRange, sc::TableValues* pUndo )
+{
+ sc::EndListeningContext aCxt(*this);
+
+ for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab)
+ {
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ continue;
+
+ pTab->ConvertFormulaToValue(
+ aCxt, rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row(),
+ pUndo);
+ }
+
+ aCxt.purgeEmptyBroadcasters();
+}
+
+void ScDocument::SwapNonEmpty( sc::TableValues& rValues )
+{
+ const ScRange& rRange = rValues.getRange();
+ if (!rRange.IsValid())
+ return;
+
+ auto pPosSet = std::make_shared<sc::ColumnBlockPositionSet>(*this);
+ sc::StartListeningContext aStartCxt(*this, pPosSet);
+ sc::EndListeningContext aEndCxt(*this, pPosSet);
+
+ for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab)
+ {
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ continue;
+
+ pTab->SwapNonEmpty(rValues, aStartCxt, aEndCxt);
+ }
+
+ aEndCxt.purgeEmptyBroadcasters();
+}
+
+void ScDocument::PreprocessAllRangeNamesUpdate( const std::map<OUString, std::unique_ptr<ScRangeName>>& rRangeMap )
+{
+ // Update all existing names with new names.
+ // The prerequisites are that the name dialog preserves ScRangeData index
+ // for changes and does not reuse free index slots for new names.
+ // ScDocument::SetAllRangeNames() hereafter then will replace the
+ // ScRangeName containers of ScRangeData instances with empty
+ // ScRangeData::maNewName.
+ std::map<OUString, ScRangeName*> aRangeNameMap;
+ GetRangeNameMap( aRangeNameMap);
+ for (const auto& itTab : aRangeNameMap)
+ {
+ ScRangeName* pOldRangeNames = itTab.second;
+ if (!pOldRangeNames)
+ continue;
+
+ const auto& itNewTab( rRangeMap.find( itTab.first));
+ if (itNewTab == rRangeMap.end())
+ continue;
+
+ const ScRangeName* pNewRangeNames = itNewTab->second.get();
+ if (!pNewRangeNames)
+ continue;
+
+ for (const auto& rEntry : *pOldRangeNames)
+ {
+ ScRangeData* pOldData = rEntry.second.get();
+ if (!pOldData)
+ continue;
+
+ const ScRangeData* pNewData = pNewRangeNames->findByIndex( pOldData->GetIndex());
+ if (pNewData)
+ pOldData->SetNewName( pNewData->GetName());
+ }
+ }
+
+ sc::EndListeningContext aEndListenCxt(*this);
+ sc::CompileFormulaContext aCompileCxt(*this);
+
+ for (const auto& rxTab : maTabs)
+ {
+ ScTable* p = rxTab.get();
+ p->PreprocessRangeNameUpdate(aEndListenCxt, aCompileCxt);
+ }
+}
+
+void ScDocument::PreprocessRangeNameUpdate()
+{
+ sc::EndListeningContext aEndListenCxt(*this);
+ sc::CompileFormulaContext aCompileCxt(*this);
+
+ for (const auto& rxTab : maTabs)
+ {
+ ScTable* p = rxTab.get();
+ p->PreprocessRangeNameUpdate(aEndListenCxt, aCompileCxt);
+ }
+}
+
+void ScDocument::PreprocessDBDataUpdate()
+{
+ sc::EndListeningContext aEndListenCxt(*this);
+ sc::CompileFormulaContext aCompileCxt(*this);
+
+ for (const auto& rxTab : maTabs)
+ {
+ ScTable* p = rxTab.get();
+ p->PreprocessDBDataUpdate(aEndListenCxt, aCompileCxt);
+ }
+}
+
+void ScDocument::CompileHybridFormula()
+{
+ sc::StartListeningContext aStartListenCxt(*this);
+ sc::CompileFormulaContext aCompileCxt(*this);
+ for (const auto& rxTab : maTabs)
+ {
+ ScTable* p = rxTab.get();
+ p->CompileHybridFormula(aStartListenCxt, aCompileCxt);
+ }
+}
+
+void ScDocument::SharePooledResources( const ScDocument* pSrcDoc )
+{
+ ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
+ mxPoolHelper = pSrcDoc->mxPoolHelper;
+ mpCellStringPool = pSrcDoc->mpCellStringPool;
+}
+
+void ScDocument::UpdateScriptTypes( const ScAddress& rPos, SCCOL nColSize, SCROW nRowSize )
+{
+ ScTable* pTab = FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ pTab->UpdateScriptTypes(rPos.Col(), rPos.Row(), rPos.Col()+nColSize-1, rPos.Row()+nRowSize-1);
+}
+
+bool ScDocument::HasUniformRowHeight( SCTAB nTab, SCROW nRow1, SCROW nRow2 ) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return false;
+
+ return pTab->HasUniformRowHeight(nRow1, nRow2);
+}
+
+void ScDocument::UnshareFormulaCells( SCTAB nTab, SCCOL nCol, std::vector<SCROW>& rRows )
+{
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ pTab->UnshareFormulaCells(nCol, rRows);
+}
+
+void ScDocument::RegroupFormulaCells( SCTAB nTab, SCCOL nCol )
+{
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ pTab->RegroupFormulaCells(nCol);
+}
+
+void ScDocument::RegroupFormulaCells( const ScRange& rRange )
+{
+ for( SCTAB tab = rRange.aStart.Tab(); tab <= rRange.aEnd.Tab(); ++tab )
+ for( SCCOL col = rRange.aStart.Col(); col <= rRange.aEnd.Col(); ++col )
+ RegroupFormulaCells( tab, col );
+}
+
+void ScDocument::DelayFormulaGrouping( bool delay )
+{
+ if( delay )
+ {
+ if( !pDelayedFormulaGrouping )
+ pDelayedFormulaGrouping.reset( new ScRange( ScAddress::INITIALIZE_INVALID ));
+ }
+ else
+ {
+ if( pDelayedFormulaGrouping && pDelayedFormulaGrouping->IsValid())
+ RegroupFormulaCells( *pDelayedFormulaGrouping );
+ pDelayedFormulaGrouping.reset();
+ }
+}
+
+void ScDocument::AddDelayedFormulaGroupingCell( const ScFormulaCell* cell )
+{
+ if( !pDelayedFormulaGrouping->Contains( cell->aPos ))
+ pDelayedFormulaGrouping->ExtendTo( cell->aPos );
+}
+
+void ScDocument::EnableDelayStartListeningFormulaCells( ScColumn* column, bool delay )
+{
+ if( delay )
+ {
+ if( pDelayedStartListeningFormulaCells.find( column ) == pDelayedStartListeningFormulaCells.end())
+ pDelayedStartListeningFormulaCells[ column ] = std::pair<SCROW, SCROW>( -1, -1 );
+ }
+ else
+ {
+ auto it = pDelayedStartListeningFormulaCells.find( column );
+ if( it != pDelayedStartListeningFormulaCells.end())
+ {
+ if( it->second.first != -1 )
+ {
+ auto pPosSet = std::make_shared<sc::ColumnBlockPositionSet>(*this);
+ sc::StartListeningContext aStartCxt(*this, pPosSet);
+ sc::EndListeningContext aEndCxt(*this, pPosSet);
+ column->StartListeningFormulaCells(aStartCxt, aEndCxt, it->second.first, it->second.second);
+ }
+ pDelayedStartListeningFormulaCells.erase( it );
+ }
+ }
+}
+
+bool ScDocument::IsEnabledDelayStartListeningFormulaCells( ScColumn* column ) const
+{
+ return pDelayedStartListeningFormulaCells.find( column ) != pDelayedStartListeningFormulaCells.end();
+}
+
+bool ScDocument::CanDelayStartListeningFormulaCells( ScColumn* column, SCROW row1, SCROW row2 )
+{
+ auto it = pDelayedStartListeningFormulaCells.find( column );
+ if( it == pDelayedStartListeningFormulaCells.end())
+ return false; // not enabled
+ if( it->second.first == -1 && it->second.second == -1 ) // uninitialized
+ pDelayedStartListeningFormulaCells[ column ] = std::make_pair( row1, row2 );
+ else
+ {
+ if( row1 > it->second.second + 1 || row2 < it->second.first - 1 )
+ { // two non-adjacent ranges, just bail out
+ return false;
+ }
+ it->second.first = std::min( it->second.first, row1 );
+ it->second.second = std::max( it->second.second, row2 );
+ }
+ return true;
+}
+
+void ScDocument::EnableDelayDeletingBroadcasters( bool set )
+{
+ if( bDelayedDeletingBroadcasters == set )
+ return;
+ bDelayedDeletingBroadcasters = set;
+ if( !bDelayedDeletingBroadcasters )
+ {
+ for (auto& rxTab : maTabs)
+ if (rxTab)
+ rxTab->DeleteEmptyBroadcasters();
+ }
+}
+
+bool ScDocument::HasFormulaCell( const ScRange& rRange ) const
+{
+ if (!rRange.IsValid())
+ return false;
+
+ for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab)
+ {
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ continue;
+
+ if (pTab->HasFormulaCell(rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row()))
+ return true;
+ }
+
+ return false;
+}
+
+void ScDocument::EndListeningIntersectedGroup(
+ sc::EndListeningContext& rCxt, const ScAddress& rPos, std::vector<ScAddress>* pGroupPos )
+{
+ ScTable* pTab = FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ pTab->EndListeningIntersectedGroup(rCxt, rPos.Col(), rPos.Row(), pGroupPos);
+}
+
+void ScDocument::EndListeningIntersectedGroups(
+ sc::EndListeningContext& rCxt, const ScRange& rRange, std::vector<ScAddress>* pGroupPos )
+{
+ for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab)
+ {
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ continue;
+
+ pTab->EndListeningIntersectedGroups(
+ rCxt, rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row(),
+ pGroupPos);
+ }
+}
+
+void ScDocument::EndListeningGroups( const std::vector<ScAddress>& rPosArray )
+{
+ sc::EndListeningContext aCxt(*this);
+ for (const ScAddress& rPos : rPosArray)
+ {
+ ScTable* pTab = FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ pTab->EndListeningGroup(aCxt, rPos.Col(), rPos.Row());
+ }
+
+ aCxt.purgeEmptyBroadcasters();
+}
+
+void ScDocument::SetNeedsListeningGroups( const std::vector<ScAddress>& rPosArray )
+{
+ for (const ScAddress& rPos : rPosArray)
+ {
+ ScTable* pTab = FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ pTab->SetNeedsListeningGroup(rPos.Col(), rPos.Row());
+ }
+}
+
+namespace {
+
+class StartNeededListenersHandler
+{
+ std::shared_ptr<sc::StartListeningContext> mpCxt;
+public:
+ explicit StartNeededListenersHandler( ScDocument& rDoc ) : mpCxt(std::make_shared<sc::StartListeningContext>(rDoc)) {}
+ explicit StartNeededListenersHandler( ScDocument& rDoc, const std::shared_ptr<const sc::ColumnSet>& rpColSet ) :
+ mpCxt(std::make_shared<sc::StartListeningContext>(rDoc))
+ {
+ mpCxt->setColumnSet( rpColSet);
+ }
+
+ void operator() (const ScTableUniquePtr & p)
+ {
+ if (p)
+ p->StartListeners(*mpCxt, false);
+ }
+};
+
+}
+
+void ScDocument::StartNeededListeners()
+{
+ std::for_each(maTabs.begin(), maTabs.end(), StartNeededListenersHandler(*this));
+}
+
+void ScDocument::StartNeededListeners( const std::shared_ptr<const sc::ColumnSet>& rpColSet )
+{
+ std::for_each(maTabs.begin(), maTabs.end(), StartNeededListenersHandler(*this, rpColSet));
+}
+
+void ScDocument::StartAllListeners( const ScRange& rRange )
+{
+ if (IsClipOrUndo() || GetNoListening())
+ return;
+
+ auto pPosSet = std::make_shared<sc::ColumnBlockPositionSet>(*this);
+ sc::StartListeningContext aStartCxt(*this, pPosSet);
+ sc::EndListeningContext aEndCxt(*this, pPosSet);
+
+ for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab)
+ {
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ continue;
+
+ pTab->StartListeningFormulaCells(
+ aStartCxt, aEndCxt,
+ rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row());
+ }
+}
+
+void ScDocument::finalizeOutlineImport()
+{
+ for (const auto& rxTab : maTabs)
+ {
+ ScTable* p = rxTab.get();
+ p->finalizeOutlineImport();
+ }
+}
+
+bool ScDocument::FindRangeNamesReferencingSheet( sc::UpdatedRangeNames& rIndexes,
+ SCTAB nTokenTab, const sal_uInt16 nTokenIndex,
+ SCTAB nGlobalRefTab, SCTAB nLocalRefTab, SCTAB nOldTokenTab, SCTAB nOldTokenTabReplacement,
+ bool bSameDoc, int nRecursion) const
+{
+ if (nTokenTab < -1)
+ {
+ SAL_WARN("sc.core", "ScDocument::FindRangeNamesReferencingSheet - nTokenTab < -1 : " <<
+ nTokenTab << ", nTokenIndex " << nTokenIndex << " Fix the creator!");
+#if OSL_DEBUG_LEVEL > 0
+ const ScRangeData* pData = FindRangeNameBySheetAndIndex( nTokenTab, nTokenIndex);
+ SAL_WARN_IF( pData, "sc.core", "ScDocument::FindRangeNamesReferencingSheet - named expression is: " << pData->GetName());
+#endif
+ nTokenTab = -1;
+ }
+ SCTAB nRefTab = nGlobalRefTab;
+ if (nTokenTab == nOldTokenTab)
+ {
+ nTokenTab = nOldTokenTabReplacement;
+ nRefTab = nLocalRefTab;
+ }
+ else if (nTokenTab == nOldTokenTabReplacement)
+ {
+ nRefTab = nLocalRefTab;
+ }
+
+ if (rIndexes.isNameUpdated( nTokenTab, nTokenIndex))
+ return true;
+
+ ScRangeData* pData = FindRangeNameBySheetAndIndex( nTokenTab, nTokenIndex);
+ if (!pData)
+ return false;
+
+ ScTokenArray* pCode = pData->GetCode();
+ if (!pCode)
+ return false;
+
+ bool bRef = !bSameDoc; // include every name used when copying to other doc
+ if (nRecursion < 126) // whatever... 42*3
+ {
+ formula::FormulaTokenArrayPlainIterator aIter(*pCode);
+ for (const formula::FormulaToken* p = aIter.First(); p; p = aIter.Next())
+ {
+ if (p->GetOpCode() == ocName)
+ {
+ bRef |= FindRangeNamesReferencingSheet( rIndexes, p->GetSheet(), p->GetIndex(),
+ nGlobalRefTab, nLocalRefTab, nOldTokenTab, nOldTokenTabReplacement, bSameDoc, nRecursion+1);
+ }
+ }
+ }
+
+ if (!bRef)
+ {
+ SCTAB nPosTab = pData->GetPos().Tab();
+ if (nPosTab == nOldTokenTab)
+ nPosTab = nOldTokenTabReplacement;
+ bRef = pCode->ReferencesSheet( nRefTab, nPosTab);
+ }
+ if (bRef)
+ rIndexes.setUpdatedName( nTokenTab, nTokenIndex);
+
+ return bRef;
+}
+
+namespace {
+
+enum MightReferenceSheet
+{
+ UNKNOWN,
+ NONE,
+ CODE,
+ NAME
+};
+
+MightReferenceSheet mightRangeNameReferenceSheet( ScRangeData* pData, SCTAB nRefTab)
+{
+ ScTokenArray* pCode = pData->GetCode();
+ if (!pCode)
+ return MightReferenceSheet::NONE;
+
+ formula::FormulaTokenArrayPlainIterator aIter(*pCode);
+ for (const formula::FormulaToken* p = aIter.First(); p; p = aIter.Next())
+ {
+ if (p->GetOpCode() == ocName)
+ return MightReferenceSheet::NAME;
+ }
+
+ return pCode->ReferencesSheet( nRefTab, pData->GetPos().Tab()) ?
+ MightReferenceSheet::CODE : MightReferenceSheet::NONE;
+}
+
+ScRangeData* copyRangeName( const ScRangeData* pOldRangeData, ScDocument& rNewDoc, const ScDocument& rOldDoc,
+ const ScAddress& rNewPos, const ScAddress& rOldPos, bool bGlobalNamesToLocal,
+ SCTAB nOldSheet, const SCTAB nNewSheet, bool bSameDoc)
+{
+ ScAddress aRangePos( pOldRangeData->GetPos());
+ if (nNewSheet >= 0)
+ aRangePos.SetTab( nNewSheet);
+ ScRangeData* pRangeData = new ScRangeData(*pOldRangeData, &rNewDoc, &aRangePos);
+ pRangeData->SetIndex(0); // needed for insert to assign a new index
+ ScTokenArray* pRangeNameToken = pRangeData->GetCode();
+ if (bSameDoc && nNewSheet >= 0)
+ {
+ if (bGlobalNamesToLocal && nOldSheet < 0)
+ {
+ nOldSheet = rOldPos.Tab();
+ if (rNewPos.Tab() <= nOldSheet)
+ // Sheet was inserted before and references already updated.
+ ++nOldSheet;
+ }
+ pRangeNameToken->AdjustSheetLocalNameReferences( nOldSheet, nNewSheet);
+ }
+ if (!bSameDoc)
+ {
+ pRangeNameToken->ReadjustAbsolute3DReferences(rOldDoc, rNewDoc, pRangeData->GetPos(), true);
+ pRangeNameToken->AdjustAbsoluteRefs(rOldDoc, rOldPos, rNewPos, true);
+ }
+
+ bool bInserted;
+ if (nNewSheet < 0)
+ bInserted = rNewDoc.GetRangeName()->insert(pRangeData);
+ else
+ bInserted = rNewDoc.GetRangeName(nNewSheet)->insert(pRangeData);
+
+ return bInserted ? pRangeData : nullptr;
+}
+
+struct SheetIndex
+{
+ SCTAB mnSheet;
+ sal_uInt16 mnIndex;
+
+ SheetIndex( SCTAB nSheet, sal_uInt16 nIndex ) : mnSheet(nSheet < -1 ? -1 : nSheet), mnIndex(nIndex) {}
+ bool operator<( const SheetIndex& r ) const
+ {
+ // Ascending order sheet, index
+ if (mnSheet < r.mnSheet)
+ return true;
+ if (mnSheet == r.mnSheet)
+ return mnIndex < r.mnIndex;
+ return false;
+ }
+};
+typedef std::map< SheetIndex, SheetIndex > SheetIndexMap;
+
+ScRangeData* copyRangeNames( SheetIndexMap& rSheetIndexMap, std::vector<ScRangeData*>& rRangeDataVec,
+ const sc::UpdatedRangeNames& rReferencingNames, SCTAB nTab,
+ const ScRangeData* pOldRangeData, ScDocument& rNewDoc, const ScDocument& rOldDoc,
+ const ScAddress& rNewPos, const ScAddress& rOldPos, bool bGlobalNamesToLocal,
+ const SCTAB nOldSheet, const SCTAB nNewSheet, bool bSameDoc)
+{
+ ScRangeData* pRangeData = nullptr;
+ const ScRangeName* pOldRangeName = (nTab < 0 ? rOldDoc.GetRangeName() : rOldDoc.GetRangeName(nTab));
+ if (pOldRangeName)
+ {
+ const ScRangeName* pNewRangeName = (nNewSheet < 0 ? rNewDoc.GetRangeName() : rNewDoc.GetRangeName(nNewSheet));
+ sc::UpdatedRangeNames::NameIndicesType aSet( rReferencingNames.getUpdatedNames(nTab));
+ for (auto const & rIndex : aSet)
+ {
+ const ScRangeData* pCopyData = pOldRangeName->findByIndex(rIndex);
+ if (pCopyData)
+ {
+ // Match the original pOldRangeData to adapt the current
+ // token's values later. For that no check for an already
+ // copied name is needed as we only enter here if there was
+ // none.
+ if (pCopyData == pOldRangeData)
+ {
+ pRangeData = copyRangeName( pCopyData, rNewDoc, rOldDoc, rNewPos, rOldPos,
+ bGlobalNamesToLocal, nOldSheet, nNewSheet, bSameDoc);
+ if (pRangeData)
+ {
+ rRangeDataVec.push_back(pRangeData);
+ rSheetIndexMap.insert( std::make_pair( SheetIndex( nOldSheet, pCopyData->GetIndex()),
+ SheetIndex( nNewSheet, pRangeData->GetIndex())));
+ }
+ }
+ else
+ {
+ // First check if the name is already available as copy.
+ const ScRangeData* pFoundData = pNewRangeName->findByUpperName( pCopyData->GetUpperName());
+ if (pFoundData)
+ {
+ // Just add the resulting sheet/index mapping.
+ rSheetIndexMap.insert( std::make_pair( SheetIndex( nOldSheet, pCopyData->GetIndex()),
+ SheetIndex( nNewSheet, pFoundData->GetIndex())));
+ }
+ else
+ {
+ ScRangeData* pTmpData = copyRangeName( pCopyData, rNewDoc, rOldDoc, rNewPos, rOldPos,
+ bGlobalNamesToLocal, nOldSheet, nNewSheet, bSameDoc);
+ if (pTmpData)
+ {
+ rRangeDataVec.push_back(pTmpData);
+ rSheetIndexMap.insert( std::make_pair( SheetIndex( nOldSheet, pCopyData->GetIndex()),
+ SheetIndex( nNewSheet, pTmpData->GetIndex())));
+ }
+ }
+ }
+ }
+ }
+ }
+ return pRangeData;
+}
+
+} // namespace
+
+bool ScDocument::CopyAdjustRangeName( SCTAB& rSheet, sal_uInt16& rIndex, ScRangeData*& rpRangeData,
+ ScDocument& rNewDoc, const ScAddress& rNewPos, const ScAddress& rOldPos, const bool bGlobalNamesToLocal,
+ const bool bUsedByFormula ) const
+{
+ ScDocument* pThis = const_cast<ScDocument*>(this);
+ const bool bSameDoc = (rNewDoc.GetPool() == pThis->GetPool());
+ if (bSameDoc && ((rSheet < 0 && !bGlobalNamesToLocal) || (rSheet >= 0
+ && (rSheet != rOldPos.Tab() || (IsClipboard() && pThis->IsCutMode())))))
+ // Same doc and global name, if not copied to local name, or
+ // sheet-local name on other sheet stays the same. Sheet-local on
+ // same sheet also in a clipboard cut&paste / move operation.
+ return false;
+
+ // Ensure we don't fiddle with the references until exit.
+ const SCTAB nOldSheet = rSheet;
+ const sal_uInt16 nOldIndex = rIndex;
+
+ SAL_WARN_IF( !bSameDoc && nOldSheet >= 0 && nOldSheet != rOldPos.Tab(),
+ "sc.core", "adjustCopyRangeName - sheet-local name was on other sheet in other document");
+ /* TODO: can we do something about that? e.g. loop over sheets? */
+
+ OUString aRangeName;
+ ScRangeData* pOldRangeData = nullptr;
+
+ // XXX bGlobalNamesToLocal is also a synonym for copied sheet.
+ bool bInsertingBefore = (bGlobalNamesToLocal && bSameDoc && rNewPos.Tab() <= rOldPos.Tab());
+
+ // The Tab where an old local name is to be found or that a global name
+ // references. May differ below from nOldSheet if a sheet was inserted
+ // before the old position. Global names and local names other than on the
+ // old sheet or new sheet are already updated, local names on the old sheet
+ // or inserted sheet will be updated later. Confusing stuff. Watch out.
+ SCTAB nOldTab = (nOldSheet < 0 ? rOldPos.Tab() : nOldSheet);
+ if (bInsertingBefore)
+ // Sheet was already inserted before old position.
+ ++nOldTab;
+
+ // Search the name of the RangeName.
+ if (nOldSheet >= 0)
+ {
+ const ScRangeName* pNames = GetRangeName(nOldTab);
+ pOldRangeData = pNames ? pNames->findByIndex(nOldIndex) : nullptr;
+ if (!pOldRangeData)
+ return false; // might be an error in the formula array
+ aRangeName = pOldRangeData->GetUpperName();
+ }
+ else
+ {
+ pOldRangeData = GetRangeName()->findByIndex(nOldIndex);
+ if (!pOldRangeData)
+ return false; // might be an error in the formula array
+ aRangeName = pOldRangeData->GetUpperName();
+ }
+
+ // Find corresponding range name in new document.
+ // First search for local range name then global range names.
+ SCTAB nNewSheet = rNewPos.Tab();
+ ScRangeName* pNewNames = rNewDoc.GetRangeName(nNewSheet);
+ // Search local range names.
+ if (pNewNames)
+ {
+ rpRangeData = pNewNames->findByUpperName(aRangeName);
+ }
+ // Search global range names.
+ if (!rpRangeData && !bGlobalNamesToLocal)
+ {
+ nNewSheet = -1;
+ pNewNames = rNewDoc.GetRangeName();
+ if (pNewNames)
+ rpRangeData = pNewNames->findByUpperName(aRangeName);
+ }
+ // If no range name was found copy it.
+ if (!rpRangeData)
+ {
+ // Do not copy global name if it doesn't reference sheet or is not used
+ // by a formula copied to another document.
+ bool bEarlyBailOut = (nOldSheet < 0 && (bSameDoc || !bUsedByFormula));
+ MightReferenceSheet eMightReference = mightRangeNameReferenceSheet( pOldRangeData, nOldTab);
+ if (bEarlyBailOut && eMightReference == MightReferenceSheet::NONE)
+ return false;
+
+ if (eMightReference == MightReferenceSheet::NAME)
+ {
+ // Name these to clarify what is passed where.
+ const SCTAB nGlobalRefTab = nOldTab;
+ const SCTAB nLocalRefTab = (bInsertingBefore ? nOldTab-1 : nOldTab);
+ const SCTAB nOldTokenTab = (nOldSheet < 0 ? (bInsertingBefore ? nOldTab-1 : nOldTab) : nOldSheet);
+ const SCTAB nOldTokenTabReplacement = nOldTab;
+ sc::UpdatedRangeNames aReferencingNames;
+ FindRangeNamesReferencingSheet( aReferencingNames, nOldSheet, nOldIndex,
+ nGlobalRefTab, nLocalRefTab, nOldTokenTab, nOldTokenTabReplacement, bSameDoc, 0);
+ if (bEarlyBailOut && aReferencingNames.isEmpty(-1) && aReferencingNames.isEmpty(nOldTokenTabReplacement))
+ return false;
+
+ SheetIndexMap aSheetIndexMap;
+ std::vector<ScRangeData*> aRangeDataVec;
+ if (!aReferencingNames.isEmpty(nOldTokenTabReplacement))
+ {
+ const SCTAB nTmpOldSheet = (nOldSheet < 0 ? nOldTab : nOldSheet);
+ nNewSheet = rNewPos.Tab();
+ rpRangeData = copyRangeNames( aSheetIndexMap, aRangeDataVec, aReferencingNames, nOldTab,
+ pOldRangeData, rNewDoc, *this, rNewPos, rOldPos,
+ bGlobalNamesToLocal, nTmpOldSheet, nNewSheet, bSameDoc);
+ }
+ if ((bGlobalNamesToLocal || !bSameDoc) && !aReferencingNames.isEmpty(-1))
+ {
+ const SCTAB nTmpOldSheet = -1;
+ const SCTAB nTmpNewSheet = (bGlobalNamesToLocal ? rNewPos.Tab() : -1);
+ ScRangeData* pTmpData = copyRangeNames( aSheetIndexMap, aRangeDataVec, aReferencingNames, -1,
+ pOldRangeData, rNewDoc, *this, rNewPos, rOldPos,
+ bGlobalNamesToLocal, nTmpOldSheet, nTmpNewSheet, bSameDoc);
+ if (!rpRangeData)
+ {
+ rpRangeData = pTmpData;
+ nNewSheet = nTmpNewSheet;
+ }
+ }
+
+ // Adjust copied nested names to new sheet/index.
+ for (auto & iRD : aRangeDataVec)
+ {
+ ScTokenArray* pCode = iRD->GetCode();
+ if (pCode)
+ {
+ formula::FormulaTokenArrayPlainIterator aIter(*pCode);
+ for (formula::FormulaToken* p = aIter.First(); p; p = aIter.Next())
+ {
+ if (p->GetOpCode() == ocName)
+ {
+ auto it = aSheetIndexMap.find( SheetIndex( p->GetSheet(), p->GetIndex()));
+ if (it != aSheetIndexMap.end())
+ {
+ p->SetSheet( it->second.mnSheet);
+ p->SetIndex( it->second.mnIndex);
+ }
+ else if (!bSameDoc)
+ {
+ SAL_WARN("sc.core","adjustCopyRangeName - mapping to new name in other doc missing");
+ p->SetIndex(0); // #NAME? error instead of arbitrary name.
+ }
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ nNewSheet = ((nOldSheet < 0 && !bGlobalNamesToLocal) ? -1 : rNewPos.Tab());
+ rpRangeData = copyRangeName( pOldRangeData, rNewDoc, *this, rNewPos, rOldPos, bGlobalNamesToLocal,
+ nOldSheet, nNewSheet, bSameDoc);
+ }
+
+ if (rpRangeData && !rNewDoc.IsClipOrUndo())
+ {
+ ScDocShell* pDocSh = static_cast<ScDocShell*>(rNewDoc.GetDocumentShell());
+ if (pDocSh)
+ pDocSh->SetAreasChangedNeedBroadcast();
+ }
+ }
+
+ rSheet = nNewSheet;
+ rIndex = rpRangeData ? rpRangeData->GetIndex() : 0; // 0 means not inserted
+ return true;
+}
+
+bool ScDocument::IsEditActionAllowed(
+ sc::ColRowEditAction eAction, SCTAB nTab, SCCOLROW nStart, SCCOLROW nEnd ) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return false;
+
+ return pTab->IsEditActionAllowed(eAction, nStart, nEnd);
+}
+
+bool ScDocument::IsEditActionAllowed(
+ sc::ColRowEditAction eAction, const ScMarkData& rMark, SCCOLROW nStart, SCCOLROW nEnd ) const
+{
+ return std::all_of(rMark.begin(), rMark.end(),
+ [this, &eAction, &nStart, &nEnd](const SCTAB& rTab) { return IsEditActionAllowed(eAction, rTab, nStart, nEnd); });
+}
+
+std::optional<sc::ColumnIterator> ScDocument::GetColumnIterator( SCTAB nTab, SCCOL nCol, SCROW nRow1, SCROW nRow2 ) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return {};
+
+ return pTab->GetColumnIterator(nCol, nRow1, nRow2);
+}
+
+void ScDocument::CreateColumnIfNotExists( SCTAB nTab, SCCOL nCol )
+{
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ pTab->CreateColumnIfNotExists(nCol);
+}
+
+bool ScDocument::EnsureFormulaCellResults( const ScRange& rRange, bool bSkipRunning )
+{
+ bool bAnyDirty = false;
+ for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab)
+ {
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ continue;
+
+ bool bRet = pTab->EnsureFormulaCellResults(
+ rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row(), bSkipRunning);
+ bAnyDirty = bAnyDirty || bRet;
+ }
+
+ return bAnyDirty;
+}
+
+sc::ExternalDataMapper& ScDocument::GetExternalDataMapper()
+{
+ if (!mpDataMapper)
+ mpDataMapper.reset(new sc::ExternalDataMapper(*this));
+
+ return *mpDataMapper;
+}
+
+void ScDocument::StoreTabToCache(SCTAB nTab, SvStream& rStrm) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ pTab->StoreToCache(rStrm);
+}
+
+void ScDocument::RestoreTabFromCache(SCTAB nTab, SvStream& rStrm)
+{
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ pTab->RestoreFromCache(rStrm);
+}
+
+OString ScDocument::dumpSheetGeomData(SCTAB nTab, bool bColumns, SheetGeomType eGeomType)
+{
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return "";
+
+ return pTab->dumpSheetGeomData(bColumns, eGeomType);
+}
+
+SCCOL ScDocument::GetLOKFreezeCol(SCTAB nTab) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return -1;
+
+ return pTab->GetLOKFreezeCol();
+}
+SCROW ScDocument::GetLOKFreezeRow(SCTAB nTab) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return -1;
+
+ return pTab->GetLOKFreezeRow();
+}
+
+bool ScDocument::SetLOKFreezeCol(SCCOL nFreezeCol, SCTAB nTab)
+{
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return false;
+
+ return pTab->SetLOKFreezeCol(nFreezeCol);
+}
+
+bool ScDocument::SetLOKFreezeRow(SCROW nFreezeRow, SCTAB nTab)
+{
+ ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return false;
+
+ return pTab->SetLOKFreezeRow(nFreezeRow);
+}
+
+std::set<SCCOL> ScDocument::QueryColumnsWithFormulaCells( SCTAB nTab ) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return std::set<SCCOL>{};
+
+ return pTab->QueryColumnsWithFormulaCells();
+}
+
+void ScDocument::CheckIntegrity( SCTAB nTab ) const
+{
+ const ScTable* pTab = FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ pTab->CheckIntegrity();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/documentimport.cxx b/sc/source/core/data/documentimport.cxx
new file mode 100644
index 000000000..7ce2d0a00
--- /dev/null
+++ b/sc/source/core/data/documentimport.cxx
@@ -0,0 +1,859 @@
+/* -*- 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 <documentimport.hxx>
+#include <document.hxx>
+#include <table.hxx>
+#include <column.hxx>
+#include <formulacell.hxx>
+#include <docoptio.hxx>
+#include <mtvelements.hxx>
+#include <tokenarray.hxx>
+#include <stringutil.hxx>
+#include <compiler.hxx>
+#include <paramisc.hxx>
+#include <listenercontext.hxx>
+#include <attarray.hxx>
+#include <sharedformula.hxx>
+#include <bcaslot.hxx>
+#include <scopetools.hxx>
+#include <numformat.hxx>
+
+#include <o3tl/safeint.hxx>
+#include <svl/sharedstringpool.hxx>
+#include <svl/languageoptions.hxx>
+#include <unotools/configmgr.hxx>
+#include <unordered_map>
+
+namespace {
+
+struct ColAttr
+{
+ bool mbLatinNumFmtOnly;
+
+ ColAttr() : mbLatinNumFmtOnly(false) {}
+};
+
+struct TabAttr
+{
+ std::vector<ColAttr> maCols;
+};
+
+}
+
+struct ScDocumentImportImpl
+{
+ ScDocument& mrDoc;
+ sc::StartListeningContext maListenCxt;
+ std::vector<sc::TableColumnBlockPositionSet> maBlockPosSet;
+ SvtScriptType mnDefaultScriptNumeric;
+ bool mbFuzzing;
+ std::vector<TabAttr> maTabAttrs;
+ std::unordered_map<sal_uInt32, bool> maIsLatinScriptMap;
+
+ explicit ScDocumentImportImpl(ScDocument& rDoc) :
+ mrDoc(rDoc),
+ maListenCxt(rDoc),
+ mnDefaultScriptNumeric(SvtScriptType::UNKNOWN),
+ mbFuzzing(utl::ConfigManager::IsFuzzing())
+ {}
+
+ bool isValid( size_t nTab, size_t nCol )
+ {
+ return (nTab <= o3tl::make_unsigned(MAXTAB) && nCol <= o3tl::make_unsigned(mrDoc.MaxCol()));
+ }
+
+ ColAttr* getColAttr( size_t nTab, size_t nCol )
+ {
+ if (!isValid(nTab, nCol))
+ return nullptr;
+
+ if (nTab >= maTabAttrs.size())
+ maTabAttrs.resize(nTab+1);
+
+ TabAttr& rTab = maTabAttrs[nTab];
+ if (nCol >= rTab.maCols.size())
+ rTab.maCols.resize(nCol+1);
+
+ return &rTab.maCols[nCol];
+ }
+
+ sc::ColumnBlockPosition* getBlockPosition( SCTAB nTab, SCCOL nCol )
+ {
+ if (!isValid(nTab, nCol))
+ return nullptr;
+
+ if (o3tl::make_unsigned(nTab) >= maBlockPosSet.size())
+ {
+ for (SCTAB i = maBlockPosSet.size(); i <= nTab; ++i)
+ maBlockPosSet.emplace_back(mrDoc, i);
+ }
+
+ sc::TableColumnBlockPositionSet& rTab = maBlockPosSet[nTab];
+ return rTab.getBlockPosition(nCol);
+ }
+
+ void invalidateBlockPositionSet(SCTAB nTab)
+ {
+ if (o3tl::make_unsigned(nTab) >= maBlockPosSet.size())
+ return;
+
+ sc::TableColumnBlockPositionSet& rTab = maBlockPosSet[nTab];
+ rTab.invalidate();
+ }
+
+ void initForSheets()
+ {
+ size_t n = mrDoc.GetTableCount();
+ for (size_t i = maBlockPosSet.size(); i < n; ++i)
+ maBlockPosSet.emplace_back(mrDoc, i);
+
+ if (maTabAttrs.size() < n)
+ maTabAttrs.resize(n);
+ }
+};
+
+ScDocumentImport::Attrs::Attrs() : mbLatinNumFmtOnly(false) {}
+
+ScDocumentImport::Attrs::~Attrs() {}
+
+ScDocumentImport::ScDocumentImport(ScDocument& rDoc) : mpImpl(new ScDocumentImportImpl(rDoc)) {}
+
+ScDocumentImport::~ScDocumentImport()
+{
+}
+
+ScDocument& ScDocumentImport::getDoc()
+{
+ return mpImpl->mrDoc;
+}
+
+const ScDocument& ScDocumentImport::getDoc() const
+{
+ return mpImpl->mrDoc;
+}
+
+void ScDocumentImport::initForSheets()
+{
+ mpImpl->initForSheets();
+}
+
+void ScDocumentImport::setDefaultNumericScript(SvtScriptType nScript)
+{
+ mpImpl->mnDefaultScriptNumeric = nScript;
+}
+
+void ScDocumentImport::setCellStyleToSheet(SCTAB nTab, const ScStyleSheet& rStyle)
+{
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ pTab->ApplyStyleArea(0, 0, getDoc().MaxCol(), getDoc().MaxRow(), rStyle);
+}
+
+SCTAB ScDocumentImport::getSheetIndex(const OUString& rName) const
+{
+ SCTAB nTab = -1;
+ if (!mpImpl->mrDoc.GetTable(rName, nTab))
+ return -1;
+
+ return nTab;
+}
+
+SCTAB ScDocumentImport::getSheetCount() const
+{
+ return mpImpl->mrDoc.maTabs.size();
+}
+
+bool ScDocumentImport::appendSheet(const OUString& rName)
+{
+ SCTAB nTabCount = mpImpl->mrDoc.maTabs.size();
+ if (!ValidTab(nTabCount))
+ return false;
+
+ mpImpl->mrDoc.maTabs.emplace_back(new ScTable(mpImpl->mrDoc, nTabCount, rName));
+ return true;
+}
+
+void ScDocumentImport::setSheetName(SCTAB nTab, const OUString& rName)
+{
+ mpImpl->mrDoc.SetTabNameOnLoad(nTab, rName);
+}
+
+void ScDocumentImport::setOriginDate(sal_uInt16 nYear, sal_uInt16 nMonth, sal_uInt16 nDay)
+{
+ if (!mpImpl->mrDoc.pDocOptions)
+ mpImpl->mrDoc.pDocOptions.reset( new ScDocOptions );
+
+ mpImpl->mrDoc.pDocOptions->SetDate(nDay, nMonth, nYear);
+}
+
+void ScDocumentImport::invalidateBlockPositionSet(SCTAB nTab)
+{
+ mpImpl->invalidateBlockPositionSet(nTab);
+}
+
+void ScDocumentImport::setAutoInput(const ScAddress& rPos, const OUString& rStr, const ScSetStringParam* pStringParam)
+{
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(rPos.Tab(), rPos.Col());
+
+ if (!pBlockPos)
+ return;
+
+ // If ScSetStringParam was given, ScColumn::ParseString() shall take care
+ // of checking. Ensure caller said so.
+ assert(!pStringParam || pStringParam->mbCheckLinkFormula);
+
+ ScCellValue aCell;
+ pTab->aCol[rPos.Col()].ParseString(
+ aCell, rPos.Row(), rPos.Tab(), rStr, mpImpl->mrDoc.GetAddressConvention(), pStringParam);
+
+ sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells;
+ switch (aCell.meType)
+ {
+ case CELLTYPE_STRING:
+ // string is copied.
+ pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), *aCell.mpString);
+ break;
+ case CELLTYPE_EDIT:
+ // Cell takes the ownership of the text object.
+ pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), aCell.mpEditText);
+ aCell.mpEditText = nullptr;
+ break;
+ case CELLTYPE_VALUE:
+ pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), aCell.mfValue);
+ break;
+ case CELLTYPE_FORMULA:
+ if (!pStringParam)
+ mpImpl->mrDoc.CheckLinkFormulaNeedingCheck( *aCell.mpFormula->GetCode());
+ // This formula cell instance is directly placed in the document without copying.
+ pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), aCell.mpFormula);
+ aCell.mpFormula = nullptr;
+ break;
+ default:
+ pBlockPos->miCellPos = rCells.set_empty(pBlockPos->miCellPos, rPos.Row(), rPos.Row());
+ }
+}
+
+void ScDocumentImport::setNumericCell(const ScAddress& rPos, double fVal)
+{
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(rPos.Tab(), rPos.Col());
+
+ if (!pBlockPos)
+ return;
+
+ sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells;
+ pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), fVal);
+}
+
+void ScDocumentImport::setStringCell(const ScAddress& rPos, const OUString& rStr)
+{
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(rPos.Tab(), rPos.Col());
+
+ if (!pBlockPos)
+ return;
+
+ svl::SharedString aSS = mpImpl->mrDoc.GetSharedStringPool().intern(rStr);
+ if (!aSS.getData())
+ return;
+
+ sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells;
+ pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), aSS);
+}
+
+void ScDocumentImport::setEditCell(const ScAddress& rPos, std::unique_ptr<EditTextObject> pEditText)
+{
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(rPos.Tab(), rPos.Col());
+
+ if (!pBlockPos)
+ return;
+
+ pEditText->NormalizeString(mpImpl->mrDoc.GetSharedStringPool());
+ sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells;
+ pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), pEditText.release());
+}
+
+void ScDocumentImport::setFormulaCell(
+ const ScAddress& rPos, const OUString& rFormula, formula::FormulaGrammar::Grammar eGrammar,
+ const double* pResult )
+{
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(rPos.Tab(), rPos.Col());
+
+ if (!pBlockPos)
+ return;
+
+ std::unique_ptr<ScFormulaCell> pFC =
+ std::make_unique<ScFormulaCell>(mpImpl->mrDoc, rPos, rFormula, eGrammar);
+
+ mpImpl->mrDoc.CheckLinkFormulaNeedingCheck( *pFC->GetCode());
+
+ if (pResult)
+ {
+ // Set cached result to this formula cell.
+ pFC->SetResultDouble(*pResult);
+ }
+
+ sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells;
+ pBlockPos->miCellPos =
+ rCells.set(pBlockPos->miCellPos, rPos.Row(), pFC.release());
+}
+
+void ScDocumentImport::setFormulaCell(
+ const ScAddress& rPos, const OUString& rFormula, formula::FormulaGrammar::Grammar eGrammar,
+ const OUString& rResult )
+{
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(rPos.Tab(), rPos.Col());
+
+ if (!pBlockPos)
+ return;
+
+ std::unique_ptr<ScFormulaCell> pFC =
+ std::make_unique<ScFormulaCell>(mpImpl->mrDoc, rPos, rFormula, eGrammar);
+
+ mpImpl->mrDoc.CheckLinkFormulaNeedingCheck( *pFC->GetCode());
+
+ // Set cached result to this formula cell.
+ pFC->SetHybridString(mpImpl->mrDoc.GetSharedStringPool().intern(rResult));
+
+ sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells;
+ pBlockPos->miCellPos =
+ rCells.set(pBlockPos->miCellPos, rPos.Row(), pFC.release());
+}
+
+void ScDocumentImport::setFormulaCell(const ScAddress& rPos, std::unique_ptr<ScTokenArray> pArray)
+{
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(rPos.Tab(), rPos.Col());
+
+ if (!pBlockPos)
+ return;
+
+ std::unique_ptr<ScFormulaCell> pFC =
+ std::make_unique<ScFormulaCell>(mpImpl->mrDoc, rPos, std::move(pArray));
+
+ mpImpl->mrDoc.CheckLinkFormulaNeedingCheck( *pFC->GetCode());
+
+ sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells;
+ pBlockPos->miCellPos =
+ rCells.set(pBlockPos->miCellPos, rPos.Row(), pFC.release());
+}
+
+void ScDocumentImport::setFormulaCell(const ScAddress& rPos, ScFormulaCell* pCell)
+{
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(rPos.Tab(), rPos.Col());
+
+ if (!pBlockPos)
+ return;
+
+ if (pCell)
+ mpImpl->mrDoc.CheckLinkFormulaNeedingCheck( *pCell->GetCode());
+
+ sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells;
+
+ sc::CellStoreType::position_type aPos = rCells.position(rPos.Row());
+ if (aPos.first != rCells.end() && aPos.first->type == sc::element_type_formula)
+ {
+ ScFormulaCell* p = sc::formula_block::at(*aPos.first->data, aPos.second);
+ sc::SharedFormulaUtil::unshareFormulaCell(aPos, *p);
+ }
+
+ pBlockPos->miCellPos =
+ rCells.set(pBlockPos->miCellPos, rPos.Row(), pCell);
+}
+
+void ScDocumentImport::setMatrixCells(
+ const ScRange& rRange, const ScTokenArray& rArray, formula::FormulaGrammar::Grammar eGram)
+{
+ const ScAddress& rBasePos = rRange.aStart;
+
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(rBasePos.Tab());
+ if (!pTab)
+ return;
+
+ sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(rBasePos.Tab(), rBasePos.Col());
+
+ if (!pBlockPos)
+ return;
+
+ if (utl::ConfigManager::IsFuzzing()) //just too slow
+ return;
+
+ sc::CellStoreType& rCells = pTab->aCol[rBasePos.Col()].maCells;
+
+ // Set the master cell.
+ ScFormulaCell* pCell = new ScFormulaCell(mpImpl->mrDoc, rBasePos, rArray, eGram, ScMatrixMode::Formula);
+
+ mpImpl->mrDoc.CheckLinkFormulaNeedingCheck( *pCell->GetCode());
+
+ pBlockPos->miCellPos =
+ rCells.set(pBlockPos->miCellPos, rBasePos.Row(), pCell);
+
+ // Matrix formulas currently need re-calculation on import.
+ pCell->SetMatColsRows(
+ rRange.aEnd.Col()-rRange.aStart.Col()+1, rRange.aEnd.Row()-rRange.aStart.Row()+1);
+
+ // Set the reference cells.
+ ScSingleRefData aRefData;
+ aRefData.InitFlags();
+ aRefData.SetColRel(true);
+ aRefData.SetRowRel(true);
+ aRefData.SetTabRel(true);
+ aRefData.SetAddress(mpImpl->mrDoc.GetSheetLimits(), rBasePos, rBasePos);
+
+ ScTokenArray aArr(mpImpl->mrDoc); // consists only of one single reference token.
+ formula::FormulaToken* t = aArr.AddMatrixSingleReference(aRefData);
+
+ ScAddress aPos = rBasePos;
+ for (SCROW nRow = rRange.aStart.Row()+1; nRow <= rRange.aEnd.Row(); ++nRow)
+ {
+ // Token array must be cloned so that each formula cell receives its own copy.
+ aPos.SetRow(nRow);
+ // Reference in each cell must point to the origin cell relative to the current cell.
+ aRefData.SetAddress(mpImpl->mrDoc.GetSheetLimits(), rBasePos, aPos);
+ *t->GetSingleRef() = aRefData;
+ ScTokenArray aTokArr(aArr.CloneValue());
+ pCell = new ScFormulaCell(mpImpl->mrDoc, aPos, aTokArr, eGram, ScMatrixMode::Reference);
+ pBlockPos->miCellPos =
+ rCells.set(pBlockPos->miCellPos, aPos.Row(), pCell);
+ }
+
+ for (SCCOL nCol = rRange.aStart.Col()+1; nCol <= rRange.aEnd.Col(); ++nCol)
+ {
+ pBlockPos = mpImpl->getBlockPosition(rBasePos.Tab(), nCol);
+ if (!pBlockPos)
+ return;
+
+ sc::CellStoreType& rColCells = pTab->aCol[nCol].maCells;
+
+ aPos.SetCol(nCol);
+ for (SCROW nRow = rRange.aStart.Row(); nRow <= rRange.aEnd.Row(); ++nRow)
+ {
+ aPos.SetRow(nRow);
+ aRefData.SetAddress(mpImpl->mrDoc.GetSheetLimits(), rBasePos, aPos);
+ *t->GetSingleRef() = aRefData;
+ ScTokenArray aTokArr(aArr.CloneValue());
+ pCell = new ScFormulaCell(mpImpl->mrDoc, aPos, aTokArr, eGram, ScMatrixMode::Reference);
+ pBlockPos->miCellPos =
+ rColCells.set(pBlockPos->miCellPos, aPos.Row(), pCell);
+ }
+ }
+}
+
+void ScDocumentImport::setTableOpCells(const ScRange& rRange, const ScTabOpParam& rParam)
+{
+ SCTAB nTab = rRange.aStart.Tab();
+ SCCOL nCol1 = rRange.aStart.Col();
+ SCROW nRow1 = rRange.aStart.Row();
+ SCCOL nCol2 = rRange.aEnd.Col();
+ SCROW nRow2 = rRange.aEnd.Row();
+
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ ScDocument& rDoc = mpImpl->mrDoc;
+ ScRefAddress aRef;
+ OUStringBuffer aFormulaBuf;
+ aFormulaBuf.append('=');
+ aFormulaBuf.append(ScCompiler::GetNativeSymbol(ocTableOp));
+ aFormulaBuf.append(ScCompiler::GetNativeSymbol(ocOpen));
+
+ OUString aSep = ScCompiler::GetNativeSymbol(ocSep);
+ if (rParam.meMode == ScTabOpParam::Column) // column only
+ {
+ aRef.Set(rParam.aRefFormulaCell.GetAddress(), true, false, false);
+ aFormulaBuf.append(aRef.GetRefString(rDoc, nTab));
+ aFormulaBuf.append(aSep);
+ aFormulaBuf.append(rParam.aRefColCell.GetRefString(rDoc, nTab));
+ aFormulaBuf.append(aSep);
+ aRef.Set(nCol1, nRow1, nTab, false, true, true);
+ aFormulaBuf.append(aRef.GetRefString(rDoc, nTab));
+ nCol1++;
+ nCol2 = std::min( nCol2, static_cast<SCCOL>(rParam.aRefFormulaEnd.Col() -
+ rParam.aRefFormulaCell.Col() + nCol1 + 1));
+ }
+ else if (rParam.meMode == ScTabOpParam::Row) // row only
+ {
+ aRef.Set(rParam.aRefFormulaCell.GetAddress(), false, true, false);
+ aFormulaBuf.append(aRef.GetRefString(rDoc, nTab));
+ aFormulaBuf.append(aSep);
+ aFormulaBuf.append(rParam.aRefRowCell.GetRefString(rDoc, nTab));
+ aFormulaBuf.append(aSep);
+ aRef.Set(nCol1, nRow1, nTab, true, false, true);
+ aFormulaBuf.append(aRef.GetRefString(rDoc, nTab));
+ ++nRow1;
+ nRow2 = std::min(
+ nRow2, rParam.aRefFormulaEnd.Row() - rParam.aRefFormulaCell.Row() + nRow1 + 1);
+ }
+ else // both
+ {
+ aFormulaBuf.append(rParam.aRefFormulaCell.GetRefString(rDoc, nTab));
+ aFormulaBuf.append(aSep);
+ aFormulaBuf.append(rParam.aRefColCell.GetRefString(rDoc, nTab));
+ aFormulaBuf.append(aSep);
+ aRef.Set(nCol1, nRow1 + 1, nTab, false, true, true);
+ aFormulaBuf.append(aRef.GetRefString(rDoc, nTab));
+ aFormulaBuf.append(aSep);
+ aFormulaBuf.append(rParam.aRefRowCell.GetRefString(rDoc, nTab));
+ aFormulaBuf.append(aSep);
+ aRef.Set(nCol1 + 1, nRow1, nTab, true, false, true);
+ aFormulaBuf.append(aRef.GetRefString(rDoc, nTab));
+ ++nCol1;
+ ++nRow1;
+ }
+
+ aFormulaBuf.append(ScCompiler::GetNativeSymbol(ocClose));
+
+ ScFormulaCell aRefCell(
+ rDoc, ScAddress(nCol1, nRow1, nTab), aFormulaBuf.makeStringAndClear(),
+ formula::FormulaGrammar::GRAM_NATIVE, ScMatrixMode::NONE);
+
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ {
+ sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(nTab, nCol);
+
+ if (!pBlockPos)
+ // Something went horribly wrong.
+ return;
+
+ sc::CellStoreType& rColCells = pTab->aCol[nCol].maCells;
+
+ for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
+ {
+ ScAddress aPos(nCol, nRow, nTab);
+ ScFormulaCell* pCell = new ScFormulaCell(aRefCell, rDoc, aPos);
+ pBlockPos->miCellPos =
+ rColCells.set(pBlockPos->miCellPos, nRow, pCell);
+ }
+ }
+}
+
+void ScDocumentImport::fillDownCells(const ScAddress& rPos, SCROW nFillSize)
+{
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ sc::ColumnBlockPosition* pBlockPos = mpImpl->getBlockPosition(rPos.Tab(), rPos.Col());
+
+ if (!pBlockPos)
+ return;
+
+ sc::CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells;
+ ScRefCellValue aRefCell = pTab->aCol[rPos.Col()].GetCellValue(*pBlockPos, rPos.Row());
+
+ switch (aRefCell.meType)
+ {
+ case CELLTYPE_VALUE:
+ {
+ std::vector<double> aCopied(nFillSize, aRefCell.mfValue);
+ pBlockPos->miCellPos = rCells.set(
+ pBlockPos->miCellPos, rPos.Row()+1, aCopied.begin(), aCopied.end());
+ break;
+ }
+ case CELLTYPE_STRING:
+ {
+ std::vector<svl::SharedString> aCopied(nFillSize, *aRefCell.mpString);
+ pBlockPos->miCellPos = rCells.set(
+ pBlockPos->miCellPos, rPos.Row()+1, aCopied.begin(), aCopied.end());
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void ScDocumentImport::setAttrEntries( SCTAB nTab, SCCOL nColStart, SCCOL nColEnd, Attrs&& rAttrs )
+{
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ for(SCCOL nCol = nColStart; nCol <= nColEnd; ++nCol )
+ {
+ ColAttr* pColAttr = mpImpl->getColAttr(nTab, nCol);
+ if (pColAttr)
+ pColAttr->mbLatinNumFmtOnly = rAttrs.mbLatinNumFmtOnly;
+ }
+
+ pTab->SetAttrEntries( nColStart, nColEnd, std::move( rAttrs.mvData ));
+}
+
+void ScDocumentImport::setRowsVisible(SCTAB nTab, SCROW nRowStart, SCROW nRowEnd, bool bVisible)
+{
+ if (!bVisible)
+ {
+ getDoc().ShowRows(nRowStart, nRowEnd, nTab, false);
+ getDoc().SetDrawPageSize(nTab);
+ getDoc().UpdatePageBreaks( nTab );
+ }
+ else
+ {
+ assert(false);
+ }
+}
+
+void ScDocumentImport::setMergedCells(SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2)
+{
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(nTab);
+ if (!pTab)
+ return;
+
+ pTab->SetMergedCells(nCol1, nRow1, nCol2, nRow2);
+}
+
+namespace {
+
+class CellStoreInitializer
+{
+ // The pimpl pattern here is intentional.
+ //
+ // The problem with having the attributes in CellStoreInitializer
+ // directly is that, as a functor, it might be copied around. In
+ // that case miPos in _copied_ object points to maAttrs in the
+ // original object, not in the copy. So later, deep in mdds, we end
+ // up comparing iterators from different sequences.
+ //
+ // This could be solved by defining copy constructor and operator=,
+ // but given the limited usage of the class, I think it is simpler
+ // to let copies share the state.
+ struct Impl
+ {
+ sc::CellTextAttrStoreType maAttrs;
+ sc::CellTextAttrStoreType::iterator miPos;
+ SvtScriptType mnScriptNumeric;
+
+ explicit Impl(const ScSheetLimits& rSheetLimits, const SvtScriptType nScriptNumeric)
+ : maAttrs(rSheetLimits.GetMaxRowCount()), miPos(maAttrs.begin()), mnScriptNumeric(nScriptNumeric)
+ {}
+ };
+
+ ScDocumentImportImpl& mrDocImpl;
+ SCTAB mnTab;
+ SCCOL mnCol;
+
+public:
+ CellStoreInitializer( ScDocumentImportImpl& rDocImpl, SCTAB nTab, SCCOL nCol ) :
+ mrDocImpl(rDocImpl),
+ mnTab(nTab),
+ mnCol(nCol),
+ mpImpl(std::make_shared<Impl>(rDocImpl.mrDoc.GetSheetLimits(), mrDocImpl.mnDefaultScriptNumeric))
+ {}
+
+ std::shared_ptr<Impl> mpImpl;
+
+ void operator() (const sc::CellStoreType::value_type& node)
+ {
+ if (node.type == sc::element_type_empty)
+ return;
+
+ // Fill with default values for non-empty cell segments.
+ sc::CellTextAttr aDefault;
+ switch (node.type)
+ {
+ case sc::element_type_numeric:
+ {
+ aDefault.mnScriptType = mpImpl->mnScriptNumeric;
+ const ColAttr* p = mrDocImpl.getColAttr(mnTab, mnCol);
+ if (p && p->mbLatinNumFmtOnly)
+ aDefault.mnScriptType = SvtScriptType::LATIN;
+ }
+ break;
+ case sc::element_type_formula:
+ {
+ const ColAttr* p = mrDocImpl.getColAttr(mnTab, mnCol);
+ if (p && p->mbLatinNumFmtOnly)
+ {
+ // We can assume latin script type if the block only
+ // contains formula cells with numeric results.
+ ScFormulaCell** pp = &sc::formula_block::at(*node.data, 0);
+ ScFormulaCell** ppEnd = pp + node.size;
+ bool bNumResOnly = true;
+ for (; pp != ppEnd; ++pp)
+ {
+ const ScFormulaCell& rCell = **pp;
+ if (!rCell.IsValueNoError())
+ {
+ bNumResOnly = false;
+ break;
+ }
+ }
+
+ if (bNumResOnly)
+ aDefault.mnScriptType = SvtScriptType::LATIN;
+ }
+ }
+ break;
+ default:
+ ;
+ }
+
+ std::vector<sc::CellTextAttr> aDefaults(node.size, aDefault);
+ mpImpl->miPos = mpImpl->maAttrs.set(mpImpl->miPos, node.position, aDefaults.begin(), aDefaults.end());
+
+ if (node.type != sc::element_type_formula)
+ return;
+
+ if (mrDocImpl.mbFuzzing) // skip listening when fuzzing
+ return;
+
+ // Have all formula cells start listening to the document.
+ ScFormulaCell** pp = &sc::formula_block::at(*node.data, 0);
+ ScFormulaCell** ppEnd = pp + node.size;
+ for (; pp != ppEnd; ++pp)
+ {
+ ScFormulaCell& rFC = **pp;
+ if (rFC.IsSharedTop())
+ {
+ // Register formula cells as a group.
+ sc::SharedFormulaUtil::startListeningAsGroup(mrDocImpl.maListenCxt, pp);
+ pp += rFC.GetSharedLength() - 1; // Move to the last one in the group.
+ }
+ else
+ rFC.StartListeningTo(mrDocImpl.maListenCxt);
+ }
+ }
+
+ void swap(sc::CellTextAttrStoreType& rAttrs)
+ {
+ mpImpl->maAttrs.swap(rAttrs);
+ }
+};
+
+}
+
+void ScDocumentImport::finalize()
+{
+ // Populate the text width and script type arrays in all columns. Also
+ // activate all formula cells.
+ for (auto& rxTab : mpImpl->mrDoc.maTabs)
+ {
+ if (!rxTab)
+ continue;
+
+ ScTable& rTab = *rxTab;
+ SCCOL nNumCols = rTab.aCol.size();
+ for (SCCOL nColIdx = 0; nColIdx < nNumCols; ++nColIdx)
+ initColumn(rTab.aCol[nColIdx]);
+ }
+
+ mpImpl->mrDoc.finalizeOutlineImport();
+}
+
+void ScDocumentImport::initColumn(ScColumn& rCol)
+{
+ rCol.RegroupFormulaCells();
+
+ CellStoreInitializer aFunc(*mpImpl, rCol.nTab, rCol.nCol);
+ std::for_each(rCol.maCells.begin(), rCol.maCells.end(), aFunc);
+ aFunc.swap(rCol.maCellTextAttrs);
+
+ rCol.CellStorageModified();
+}
+
+namespace {
+
+class CellStoreAfterImportBroadcaster
+{
+public:
+
+ CellStoreAfterImportBroadcaster() {}
+
+ void operator() (const sc::CellStoreType::value_type& node)
+ {
+ if (node.type == sc::element_type_formula)
+ {
+ // Broadcast all formula cells marked for recalc.
+ ScFormulaCell** pp = &sc::formula_block::at(*node.data, 0);
+ ScFormulaCell** ppEnd = pp + node.size;
+ for (; pp != ppEnd; ++pp)
+ {
+ if ((*pp)->GetCode()->IsRecalcModeMustAfterImport())
+ (*pp)->SetDirty();
+ }
+ }
+ }
+};
+
+}
+
+void ScDocumentImport::broadcastRecalcAfterImport()
+{
+ sc::AutoCalcSwitch aACSwitch( mpImpl->mrDoc, false);
+ ScBulkBroadcast aBulkBroadcast( mpImpl->mrDoc.GetBASM(), SfxHintId::ScDataChanged);
+
+ for (auto& rxTab : mpImpl->mrDoc.maTabs)
+ {
+ if (!rxTab)
+ continue;
+
+ ScTable& rTab = *rxTab;
+ SCCOL nNumCols = rTab.aCol.size();
+ for (SCCOL nColIdx = 0; nColIdx < nNumCols; ++nColIdx)
+ broadcastRecalcAfterImportColumn(rTab.aCol[nColIdx]);
+ }
+}
+
+void ScDocumentImport::broadcastRecalcAfterImportColumn(ScColumn& rCol)
+{
+ CellStoreAfterImportBroadcaster aFunc;
+ std::for_each(rCol.maCells.begin(), rCol.maCells.end(), aFunc);
+}
+
+
+bool ScDocumentImport::isLatinScript(const ScPatternAttr& rPatAttr)
+{
+ SvNumberFormatter* pFormatter = mpImpl->mrDoc.GetFormatTable();
+ sal_uInt32 nKey = rPatAttr.GetNumberFormat(pFormatter);
+ return isLatinScript(nKey);
+}
+
+bool ScDocumentImport::isLatinScript(sal_uInt32 nFormat)
+{
+ auto it = mpImpl->maIsLatinScriptMap.find(nFormat);
+ if (it != mpImpl->maIsLatinScriptMap.end())
+ return it->second;
+ bool b = sc::NumFmtUtil::isLatinScript(nFormat, mpImpl->mrDoc);
+ mpImpl->maIsLatinScriptMap.emplace(nFormat, b);
+ return b;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/documentstreamaccess.cxx b/sc/source/core/data/documentstreamaccess.cxx
new file mode 100644
index 000000000..6b3581058
--- /dev/null
+++ b/sc/source/core/data/documentstreamaccess.cxx
@@ -0,0 +1,147 @@
+/* -*- 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 <documentstreamaccess.hxx>
+#include <document.hxx>
+#include <table.hxx>
+#include <column.hxx>
+#include <mtvelements.hxx>
+
+#include <svl/sharedstringpool.hxx>
+
+namespace sc {
+
+struct DocumentStreamAccessImpl
+{
+ ScDocument& mrDoc;
+ ColumnBlockPositionSet maBlockPosSet;
+
+ explicit DocumentStreamAccessImpl( ScDocument& rDoc ) :
+ mrDoc(rDoc),
+ maBlockPosSet(rDoc)
+ {}
+};
+
+DocumentStreamAccess::DocumentStreamAccess( ScDocument& rDoc ) :
+ mpImpl(new DocumentStreamAccessImpl(rDoc)) {}
+
+DocumentStreamAccess::~DocumentStreamAccess()
+{
+}
+
+void DocumentStreamAccess::setNumericCell( const ScAddress& rPos, double fVal )
+{
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ ColumnBlockPosition* pBlockPos =
+ mpImpl->maBlockPosSet.getBlockPosition(rPos.Tab(), rPos.Col());
+
+ if (!pBlockPos)
+ return;
+
+ // Set the numeric value.
+ CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells;
+ pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), fVal);
+
+ // Be sure to set the corresponding text attribute to the default value.
+ CellTextAttrStoreType& rAttrs = pTab->aCol[rPos.Col()].maCellTextAttrs;
+ pBlockPos->miCellTextAttrPos = rAttrs.set(pBlockPos->miCellTextAttrPos, rPos.Row(), CellTextAttr());
+}
+
+void DocumentStreamAccess::setStringCell( const ScAddress& rPos, const OUString& rStr )
+{
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(rPos.Tab());
+ if (!pTab)
+ return;
+
+ ColumnBlockPosition* pBlockPos =
+ mpImpl->maBlockPosSet.getBlockPosition(rPos.Tab(), rPos.Col());
+
+ if (!pBlockPos)
+ return;
+
+ svl::SharedString aSS = mpImpl->mrDoc.GetSharedStringPool().intern(rStr);
+ if (!aSS.getData())
+ return;
+
+ // Set the string.
+ CellStoreType& rCells = pTab->aCol[rPos.Col()].maCells;
+ pBlockPos->miCellPos = rCells.set(pBlockPos->miCellPos, rPos.Row(), aSS);
+
+ // Be sure to set the corresponding text attribute to the default value.
+ CellTextAttrStoreType& rAttrs = pTab->aCol[rPos.Col()].maCellTextAttrs;
+ pBlockPos->miCellTextAttrPos = rAttrs.set(pBlockPos->miCellTextAttrPos, rPos.Row(), CellTextAttr());
+}
+
+void DocumentStreamAccess::reset()
+{
+ mpImpl->maBlockPosSet.clear();
+}
+
+void DocumentStreamAccess::shiftRangeUp( const ScRange& rRange )
+{
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(rRange.aStart.Tab());
+ if (!pTab)
+ return;
+
+ SCROW nTopRow = rRange.aStart.Row();
+ SCROW nLastRow = rRange.aEnd.Row();
+
+ for (SCCOL nCol = rRange.aStart.Col(); nCol <= rRange.aEnd.Col(); ++nCol)
+ {
+ ColumnBlockPosition* pBlockPos =
+ mpImpl->maBlockPosSet.getBlockPosition(rRange.aStart.Tab(), nCol);
+
+ if (!pBlockPos)
+ return;
+
+ CellStoreType& rCells = pTab->aCol[nCol].maCells;
+ rCells.erase(nTopRow, nTopRow); // Erase the top, and shift the rest up.
+ pBlockPos->miCellPos = rCells.insert_empty(nLastRow, 1);
+
+ // Do the same for the text attribute storage.
+ CellTextAttrStoreType& rAttrs = pTab->aCol[nCol].maCellTextAttrs;
+ rAttrs.erase(nTopRow, nTopRow);
+ pBlockPos->miCellTextAttrPos = rAttrs.insert_empty(nLastRow, 1);
+ }
+}
+
+void DocumentStreamAccess::shiftRangeDown( const ScRange& rRange )
+{
+ ScTable* pTab = mpImpl->mrDoc.FetchTable(rRange.aStart.Tab());
+ if (!pTab)
+ return;
+
+ SCROW nTopRow = rRange.aStart.Row();
+ SCROW nLastRow = rRange.aEnd.Row();
+
+ for (SCCOL nCol = rRange.aStart.Col(); nCol <= rRange.aEnd.Col(); ++nCol)
+ {
+ ColumnBlockPosition* pBlockPos =
+ mpImpl->maBlockPosSet.getBlockPosition(rRange.aStart.Tab(), nCol);
+
+ if (!pBlockPos)
+ return;
+
+ CellStoreType& rCells = pTab->aCol[nCol].maCells;
+ rCells.erase(nLastRow, nLastRow); // Erase the bottom.
+ pBlockPos->miCellPos = rCells.insert_empty(nTopRow, 1); // insert at the top and shift everything down.
+
+ // Do the same for the text attribute storage.
+ CellTextAttrStoreType& rAttrs = pTab->aCol[nCol].maCellTextAttrs;
+ rAttrs.erase(nLastRow, nLastRow);
+ pBlockPos->miCellTextAttrPos = rAttrs.insert_empty(nTopRow, 1);
+ }
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dpcache.cxx b/sc/source/core/data/dpcache.cxx
new file mode 100644
index 000000000..52109c673
--- /dev/null
+++ b/sc/source/core/data/dpcache.cxx
@@ -0,0 +1,1522 @@
+/* -*- 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 <dpcache.hxx>
+
+#include <document.hxx>
+#include <queryentry.hxx>
+#include <queryparam.hxx>
+#include <dpobject.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <docoptio.hxx>
+#include <dpitemdata.hxx>
+#include <dputil.hxx>
+#include <dpnumgroupinfo.hxx>
+#include <columniterator.hxx>
+#include <cellvalue.hxx>
+
+#include <comphelper/parallelsort.hxx>
+#include <rtl/math.hxx>
+#include <unotools/charclass.hxx>
+#include <unotools/textsearch.hxx>
+#include <unotools/localedatawrapper.hxx>
+#include <unotools/collatorwrapper.hxx>
+#include <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+
+#if DUMP_PIVOT_TABLE
+#include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp>
+#endif
+
+// TODO : Threaded pivot cache operation is disabled until we can figure out
+// ways to make the edit engine and number formatter codes thread-safe in a
+// proper fashion.
+#define ENABLE_THREADED_PIVOT_CACHE 0
+
+#if ENABLE_THREADED_PIVOT_CACHE
+#include <thread>
+#include <future>
+#include <queue>
+#endif
+
+using namespace ::com::sun::star;
+
+using ::com::sun::star::uno::Exception;
+
+ScDPCache::GroupItems::GroupItems() : mnGroupType(0) {}
+
+ScDPCache::GroupItems::GroupItems(const ScDPNumGroupInfo& rInfo, sal_Int32 nGroupType) :
+ maInfo(rInfo), mnGroupType(nGroupType) {}
+
+ScDPCache::Field::Field() : mnNumFormat(0) {}
+
+ScDPCache::ScDPCache(ScDocument& rDoc) :
+ mrDoc( rDoc ),
+ mnColumnCount ( 0 ),
+ maEmptyRows(0, rDoc.GetMaxRowCount(), true),
+ mnDataSize(-1),
+ mnRowCount(0),
+ mbDisposing(false)
+{
+}
+
+namespace {
+
+struct ClearObjectSource
+{
+ void operator() (ScDPObject* p) const
+ {
+ p->ClearTableData();
+ }
+};
+
+}
+
+ScDPCache::~ScDPCache()
+{
+ // Make sure no live ScDPObject instances hold reference to this cache any
+ // more.
+ mbDisposing = true;
+ std::for_each(maRefObjects.begin(), maRefObjects.end(), ClearObjectSource());
+}
+
+namespace {
+
+/**
+ * While the macro interpret level is incremented, the formula cells are
+ * (semi-)guaranteed to be interpreted.
+ */
+class MacroInterpretIncrementer
+{
+public:
+ explicit MacroInterpretIncrementer(ScDocument& rDoc) :
+ mrDoc(rDoc)
+ {
+ mrDoc.IncMacroInterpretLevel();
+ }
+ ~MacroInterpretIncrementer()
+ {
+ mrDoc.DecMacroInterpretLevel();
+ }
+private:
+ ScDocument& mrDoc;
+};
+
+rtl_uString* internString( ScDPCache::StringSetType& rPool, const OUString& rStr )
+{
+ return rPool.insert(rStr).first->pData;
+}
+
+OUString createLabelString( const ScDocument& rDoc, SCCOL nCol, const ScRefCellValue& rCell )
+{
+ OUString aDocStr = rCell.getRawString(rDoc);
+
+ if (aDocStr.isEmpty())
+ {
+ // Replace an empty label string with column name.
+
+ ScAddress aColAddr(nCol, 0, 0);
+ aDocStr = ScResId(STR_COLUMN) + " " + aColAddr.Format(ScRefFlags::COL_VALID);
+ }
+ return aDocStr;
+}
+
+void initFromCell(
+ ScDPCache::StringSetType& rStrPool, const ScDocument& rDoc, const ScAddress& rPos,
+ const ScRefCellValue& rCell, ScDPItemData& rData, sal_uInt32& rNumFormat)
+{
+ OUString aDocStr = rCell.getRawString(rDoc);
+ rNumFormat = 0;
+
+ if (rCell.hasError())
+ {
+ rData.SetErrorStringInterned(internString(rStrPool, rDoc.GetString(rPos.Col(), rPos.Row(), rPos.Tab())));
+ }
+ else if (rCell.hasNumeric())
+ {
+ double fVal = rCell.getRawValue();
+ rNumFormat = rDoc.GetNumberFormat(rPos);
+ rData.SetValue(fVal);
+ }
+ else if (!rCell.isEmpty())
+ {
+ rData.SetStringInterned(internString(rStrPool, aDocStr));
+ }
+ else
+ rData.SetEmpty();
+}
+
+struct Bucket
+{
+ ScDPItemData maValue;
+ SCROW mnOrderIndex;
+ SCROW mnDataIndex;
+ Bucket() :
+ mnOrderIndex(0), mnDataIndex(0) {}
+ Bucket(const ScDPItemData& rValue, SCROW nData) :
+ maValue(rValue), mnOrderIndex(0), mnDataIndex(nData) {}
+};
+
+#if DEBUG_PIVOT_TABLE
+#include <iostream>
+using std::cout;
+using std::endl;
+
+struct PrintBucket
+{
+ void operator() (const Bucket& v) const
+ {
+ cout << "value: " << v.maValue.GetValue() << " order index: " << v.mnOrderIndex << " data index: " << v.mnDataIndex << endl;
+ }
+};
+
+#endif
+
+struct LessByValue
+{
+ bool operator() (const Bucket& left, const Bucket& right) const
+ {
+ return left.maValue < right.maValue;
+ }
+};
+
+struct LessByOrderIndex
+{
+ bool operator() (const Bucket& left, const Bucket& right) const
+ {
+ return left.mnOrderIndex < right.mnOrderIndex;
+ }
+};
+
+struct LessByDataIndex
+{
+ bool operator() (const Bucket& left, const Bucket& right) const
+ {
+ return left.mnDataIndex < right.mnDataIndex;
+ }
+};
+
+struct EqualByOrderIndex
+{
+ bool operator() (const Bucket& left, const Bucket& right) const
+ {
+ return left.mnOrderIndex == right.mnOrderIndex;
+ }
+};
+
+class PushBackValue
+{
+ ScDPCache::ScDPItemDataVec& mrItems;
+public:
+ explicit PushBackValue(ScDPCache::ScDPItemDataVec& _items) : mrItems(_items) {}
+ void operator() (const Bucket& v)
+ {
+ mrItems.push_back(v.maValue);
+ }
+};
+
+class PushBackOrderIndex
+{
+ ScDPCache::IndexArrayType& mrData;
+public:
+ explicit PushBackOrderIndex(ScDPCache::IndexArrayType& _items) : mrData(_items) {}
+ void operator() (const Bucket& v)
+ {
+ mrData.push_back(v.mnOrderIndex);
+ }
+};
+
+void processBuckets(std::vector<Bucket>& aBuckets, ScDPCache::Field& rField)
+{
+ if (aBuckets.empty())
+ return;
+
+ // Sort by the value.
+ comphelper::parallelSort(aBuckets.begin(), aBuckets.end(), LessByValue());
+
+ {
+ // Set order index such that unique values have identical index value.
+ SCROW nCurIndex = 0;
+ std::vector<Bucket>::iterator it = aBuckets.begin(), itEnd = aBuckets.end();
+ ScDPItemData aPrev = it->maValue;
+ it->mnOrderIndex = nCurIndex;
+ for (++it; it != itEnd; ++it)
+ {
+ if (!aPrev.IsCaseInsEqual(it->maValue))
+ ++nCurIndex;
+
+ it->mnOrderIndex = nCurIndex;
+ aPrev = it->maValue;
+ }
+ }
+
+ // Re-sort the bucket this time by the data index.
+ comphelper::parallelSort(aBuckets.begin(), aBuckets.end(), LessByDataIndex());
+
+ // Copy the order index series into the field object.
+ rField.maData.reserve(aBuckets.size());
+ std::for_each(aBuckets.begin(), aBuckets.end(), PushBackOrderIndex(rField.maData));
+
+ // Sort by the value again.
+ comphelper::parallelSort(aBuckets.begin(), aBuckets.end(), LessByOrderIndex());
+
+ // Unique by value.
+ std::vector<Bucket>::iterator itUniqueEnd =
+ std::unique(aBuckets.begin(), aBuckets.end(), EqualByOrderIndex());
+
+ // Copy the unique values into items.
+ std::vector<Bucket>::iterator itBeg = aBuckets.begin();
+ size_t nLen = distance(itBeg, itUniqueEnd);
+ rField.maItems.reserve(nLen);
+ std::for_each(itBeg, itUniqueEnd, PushBackValue(rField.maItems));
+}
+
+struct InitColumnData
+{
+ ScDPCache::EmptyRowsType maEmptyRows;
+ OUString maLabel;
+
+ ScDPCache::StringSetType* mpStrPool;
+ ScDPCache::Field* mpField;
+
+ SCCOL mnCol;
+
+ InitColumnData(ScSheetLimits const & rSheetLimits) :
+ maEmptyRows(0, rSheetLimits.GetMaxRowCount(), true),
+ mpStrPool(nullptr),
+ mpField(nullptr),
+ mnCol(-1) {}
+
+ void init( SCCOL nCol, ScDPCache::StringSetType* pStrPool, ScDPCache::Field* pField )
+ {
+ mpStrPool = pStrPool;
+ mpField = pField;
+ mnCol = nCol;
+ }
+};
+
+struct InitDocData
+{
+ ScDocument& mrDoc;
+ SCTAB mnDocTab;
+ SCROW mnStartRow;
+ SCROW mnEndRow;
+ bool mbTailEmptyRows;
+
+ InitDocData(ScDocument& rDoc) :
+ mrDoc(rDoc),
+ mnDocTab(-1),
+ mnStartRow(-1),
+ mnEndRow(-1),
+ mbTailEmptyRows(false) {}
+};
+
+typedef std::unordered_set<OUString> LabelSet;
+
+void normalizeAddLabel(const OUString& rLabel, std::vector<OUString>& rLabels, LabelSet& rExistingNames)
+{
+ const OUString aLabelLower = ScGlobal::getCharClass().lowercase(rLabel);
+ sal_Int32 nSuffix = 1;
+ OUString aNewLabel = rLabel;
+ OUString aNewLabelLower = aLabelLower;
+ while (true)
+ {
+ if (!rExistingNames.count(aNewLabelLower))
+ {
+ // this is a unique label.
+ rLabels.push_back(aNewLabel);
+ rExistingNames.insert(aNewLabelLower);
+ break;
+ }
+
+ // This name already exists.
+ aNewLabel = rLabel + OUString::number(++nSuffix);
+ aNewLabelLower = aLabelLower + OUString::number(nSuffix);
+ }
+}
+
+std::vector<OUString> normalizeLabels(const std::vector<InitColumnData>& rColData)
+{
+ std::vector<OUString> aLabels;
+ aLabels.reserve(rColData.size() + 1);
+
+ LabelSet aExistingNames;
+ normalizeAddLabel(ScResId(STR_PIVOT_DATA), aLabels, aExistingNames);
+
+ for (const InitColumnData& rCol : rColData)
+ normalizeAddLabel(rCol.maLabel, aLabels, aExistingNames);
+
+ return aLabels;
+}
+
+std::vector<OUString> normalizeLabels(const ScDPCache::DBConnector& rDB, const sal_Int32 nLabelCount)
+{
+ std::vector<OUString> aLabels;
+ aLabels.reserve(nLabelCount + 1);
+
+ LabelSet aExistingNames;
+ normalizeAddLabel(ScResId(STR_PIVOT_DATA), aLabels, aExistingNames);
+
+ for (sal_Int32 nCol = 0; nCol < nLabelCount; ++nCol)
+ {
+ OUString aColTitle = rDB.getColumnLabel(nCol);
+ normalizeAddLabel(aColTitle, aLabels, aExistingNames);
+ }
+
+ return aLabels;
+}
+
+void initColumnFromDoc( InitDocData& rDocData, InitColumnData &rColData )
+{
+ ScDPCache::Field& rField = *rColData.mpField;
+ ScDocument& rDoc = rDocData.mrDoc;
+ SCTAB nDocTab = rDocData.mnDocTab;
+ SCCOL nCol = rColData.mnCol;
+ SCROW nStartRow = rDocData.mnStartRow;
+ SCROW nEndRow = rDocData.mnEndRow;
+ bool bTailEmptyRows = rDocData.mbTailEmptyRows;
+
+ std::optional<sc::ColumnIterator> pIter =
+ rDoc.GetColumnIterator(nDocTab, nCol, nStartRow, nEndRow);
+ assert(pIter);
+ assert(pIter->hasCell());
+
+ ScDPItemData aData;
+
+ rColData.maLabel = createLabelString(rDoc, nCol, pIter->getCell());
+ pIter->next();
+
+ std::vector<Bucket> aBuckets;
+ aBuckets.reserve(nEndRow-nStartRow); // skip the topmost label cell.
+
+ // Push back all original values.
+ for (SCROW i = 0, n = nEndRow-nStartRow; i < n; ++i, pIter->next())
+ {
+ assert(pIter->hasCell());
+
+ sal_uInt32 nNumFormat = 0;
+ ScAddress aPos(nCol, pIter->getRow(), nDocTab);
+ initFromCell(*rColData.mpStrPool, rDoc, aPos, pIter->getCell(), aData, nNumFormat);
+
+ aBuckets.emplace_back(aData, i);
+
+ if (!aData.IsEmpty())
+ {
+ rColData.maEmptyRows.insert_back(i, i+1, false);
+ if (nNumFormat)
+ // Only take non-default number format.
+ rField.mnNumFormat = nNumFormat;
+ }
+ }
+
+ processBuckets(aBuckets, rField);
+
+ if (bTailEmptyRows)
+ {
+ // If the last item is not empty, append one. Note that the items
+ // are sorted, and empty item should come last when sorted.
+ if (rField.maItems.empty() || !rField.maItems.back().IsEmpty())
+ {
+ aData.SetEmpty();
+ rField.maItems.push_back(aData);
+ }
+ }
+}
+
+#if ENABLE_THREADED_PIVOT_CACHE
+
+class ThreadQueue
+{
+ using FutureType = std::future<void>;
+ std::queue<FutureType> maQueue;
+ std::mutex maMutex;
+ std::condition_variable maCond;
+
+ size_t mnMaxQueue;
+
+public:
+ ThreadQueue( size_t nMaxQueue ) : mnMaxQueue(nMaxQueue) {}
+
+ void push( std::function<void()> aFunc )
+ {
+ std::unique_lock<std::mutex> lock(maMutex);
+
+ while (maQueue.size() >= mnMaxQueue)
+ maCond.wait(lock);
+
+ FutureType f = std::async(std::launch::async, aFunc);
+ maQueue.push(std::move(f));
+ lock.unlock();
+
+ maCond.notify_one();
+ }
+
+ void waitForOne()
+ {
+ std::unique_lock<std::mutex> lock(maMutex);
+
+ while (maQueue.empty())
+ maCond.wait(lock);
+
+ FutureType ret = std::move(maQueue.front());
+ maQueue.pop();
+ lock.unlock();
+
+ ret.get(); // This may throw if an exception was thrown on the async thread.
+
+ maCond.notify_one();
+ }
+};
+
+class ThreadScopedGuard
+{
+ std::thread maThread;
+public:
+ ThreadScopedGuard(std::thread thread) : maThread(std::move(thread)) {}
+ ThreadScopedGuard(ThreadScopedGuard&& other) : maThread(std::move(other.maThread)) {}
+
+ ThreadScopedGuard(const ThreadScopedGuard&) = delete;
+ ThreadScopedGuard& operator= (const ThreadScopedGuard&) = delete;
+
+ ~ThreadScopedGuard()
+ {
+ maThread.join();
+ }
+};
+
+#endif
+
+}
+
+void ScDPCache::InitFromDoc(ScDocument& rDoc, const ScRange& rRange)
+{
+ Clear();
+
+ InitDocData aDocData(rDoc);
+
+ // Make sure the formula cells within the data range are interpreted
+ // during this call, for this method may be called from the interpretation
+ // of GETPIVOTDATA, which disables nested formula interpretation without
+ // increasing the macro level.
+ MacroInterpretIncrementer aMacroInc(rDoc);
+
+ aDocData.mnStartRow = rRange.aStart.Row(); // start of data
+ aDocData.mnEndRow = rRange.aEnd.Row();
+
+ // Sanity check
+ if (!GetDoc().ValidRow(aDocData.mnStartRow) || !GetDoc().ValidRow(aDocData.mnEndRow) || aDocData.mnEndRow <= aDocData.mnStartRow)
+ return;
+
+ SCCOL nStartCol = rRange.aStart.Col();
+ SCCOL nEndCol = rRange.aEnd.Col();
+ aDocData.mnDocTab = rRange.aStart.Tab();
+
+ mnColumnCount = nEndCol - nStartCol + 1;
+
+ // this row count must include the trailing empty rows.
+ mnRowCount = aDocData.mnEndRow - aDocData.mnStartRow; // skip the topmost label row.
+
+ // Skip trailing empty rows if exists.
+ SCCOL nCol1 = nStartCol, nCol2 = nEndCol;
+ SCROW nRow1 = aDocData.mnStartRow, nRow2 = aDocData.mnEndRow;
+ rDoc.ShrinkToDataArea(aDocData.mnDocTab, nCol1, nRow1, nCol2, nRow2);
+ aDocData.mbTailEmptyRows = aDocData.mnEndRow > nRow2; // Trailing empty rows exist.
+ aDocData.mnEndRow = nRow2;
+
+ if (aDocData.mnEndRow <= aDocData.mnStartRow)
+ {
+ // Check this again since the end row position has changed. It's
+ // possible that the new end row becomes lower than the start row
+ // after the shrinkage.
+ Clear();
+ return;
+ }
+
+ maStringPools.resize(mnColumnCount);
+ std::vector<InitColumnData> aColData(mnColumnCount, InitColumnData(rDoc.GetSheetLimits()));
+ maFields.reserve(mnColumnCount);
+ for (SCCOL i = 0; i < mnColumnCount; ++i)
+ maFields.push_back(std::make_unique<Field>());
+
+ maLabelNames.reserve(mnColumnCount+1);
+
+ // Ensure that none of the formula cells in the data range are dirty.
+ rDoc.EnsureFormulaCellResults(rRange);
+
+#if ENABLE_THREADED_PIVOT_CACHE
+ ThreadQueue aQueue(std::thread::hardware_concurrency());
+
+ auto aFuncLaunchFieldThreads = [&]()
+ {
+ for (sal_uInt16 nCol = nStartCol; nCol <= nEndCol; ++nCol)
+ {
+ size_t nDim = nCol - nStartCol;
+ InitColumnData& rColData = aColData[nDim];
+ rColData.init(nCol, &maStringPools[nDim], maFields[nDim].get());
+
+ auto func = [&aDocData,&rColData]()
+ {
+ initColumnFromDoc(aDocData, rColData);
+ };
+
+ aQueue.push(std::move(func));
+ }
+ };
+
+ {
+ // Launch a separate thread that in turn spawns async threads to populate the fields.
+ std::thread t(aFuncLaunchFieldThreads);
+ ThreadScopedGuard sg(std::move(t));
+
+ // Wait for all the async threads to complete on the main thread.
+ for (SCCOL i = 0; i < mnColumnCount; ++i)
+ aQueue.waitForOne();
+ }
+
+#else
+ for (sal_uInt16 nCol = nStartCol; nCol <= nEndCol; ++nCol)
+ {
+ size_t nDim = nCol - nStartCol;
+ InitColumnData& rColData = aColData[nDim];
+ rColData.init(nCol, &maStringPools[nDim], maFields[nDim].get());
+
+ initColumnFromDoc(aDocData, rColData);
+ }
+#endif
+
+ maLabelNames = normalizeLabels(aColData);
+
+ // Merge all non-empty rows data.
+ for (const InitColumnData& rCol : aColData)
+ {
+ EmptyRowsType::const_segment_iterator it = rCol.maEmptyRows.begin_segment();
+ EmptyRowsType::const_segment_iterator ite = rCol.maEmptyRows.end_segment();
+ EmptyRowsType::const_iterator pos = maEmptyRows.begin();
+
+ for (; it != ite; ++it)
+ {
+ if (!it->value)
+ // Non-empty segment found. Record it.
+ pos = maEmptyRows.insert(pos, it->start, it->end, false).first;
+ }
+ }
+
+ PostInit();
+}
+
+bool ScDPCache::InitFromDataBase(DBConnector& rDB)
+{
+ Clear();
+
+ try
+ {
+ mnColumnCount = rDB.getColumnCount();
+ maStringPools.resize(mnColumnCount);
+ maFields.clear();
+ maFields.reserve(mnColumnCount);
+ for (SCCOL i = 0; i < mnColumnCount; ++i)
+ maFields.push_back(std::make_unique<Field>());
+
+ // Get column titles and types.
+ maLabelNames = normalizeLabels(rDB, mnColumnCount);
+
+ std::vector<Bucket> aBuckets;
+ ScDPItemData aData;
+ for (sal_Int32 nCol = 0; nCol < mnColumnCount; ++nCol)
+ {
+ if (!rDB.first())
+ continue;
+
+ aBuckets.clear();
+ Field& rField = *maFields[nCol];
+ SCROW nRow = 0;
+ do
+ {
+ SvNumFormatType nFormatType = SvNumFormatType::UNDEFINED;
+ aData.SetEmpty();
+ rDB.getValue(nCol, aData, nFormatType);
+ aBuckets.emplace_back(aData, nRow);
+ if (!aData.IsEmpty())
+ {
+ maEmptyRows.insert_back(nRow, nRow+1, false);
+ SvNumberFormatter* pFormatter = mrDoc.GetFormatTable();
+ rField.mnNumFormat = pFormatter ? pFormatter->GetStandardFormat(nFormatType) : 0;
+ }
+
+ ++nRow;
+ }
+ while (rDB.next());
+
+ processBuckets(aBuckets, rField);
+ }
+
+ rDB.finish();
+
+ if (!maFields.empty())
+ mnRowCount = maFields[0]->maData.size();
+
+ PostInit();
+ return true;
+ }
+ catch (const Exception&)
+ {
+ return false;
+ }
+}
+
+bool ScDPCache::ValidQuery( SCROW nRow, const ScQueryParam &rParam) const
+{
+ if (!rParam.GetEntryCount())
+ return true;
+
+ if (!rParam.GetEntry(0).bDoQuery)
+ return true;
+
+ bool bMatchWholeCell = mrDoc.GetDocOptions().IsMatchWholeCell();
+
+ SCSIZE nEntryCount = rParam.GetEntryCount();
+ std::vector<bool> aPassed(nEntryCount, false);
+
+ tools::Long nPos = -1;
+ CollatorWrapper& rCollator = ScGlobal::GetCollator(rParam.bCaseSens);
+ ::utl::TransliterationWrapper& rTransliteration = ScGlobal::GetTransliteration(rParam.bCaseSens);
+
+ for (size_t i = 0; i < nEntryCount && rParam.GetEntry(i).bDoQuery; ++i)
+ {
+ const ScQueryEntry& rEntry = rParam.GetEntry(i);
+ const ScQueryEntry::Item& rItem = rEntry.GetQueryItem();
+ // we can only handle one single direct query
+ // #i115431# nField in QueryParam is the sheet column, not the field within the source range
+ SCCOL nQueryCol = static_cast<SCCOL>(rEntry.nField);
+ if ( nQueryCol < rParam.nCol1 )
+ nQueryCol = rParam.nCol1;
+ if ( nQueryCol > rParam.nCol2 )
+ nQueryCol = rParam.nCol2;
+ SCCOL nSourceField = nQueryCol - rParam.nCol1;
+ SCROW nId = GetItemDataId( nSourceField, nRow, false );
+ const ScDPItemData* pCellData = GetItemDataById( nSourceField, nId );
+
+ bool bOk = false;
+
+ if (rEntry.GetQueryItem().meType == ScQueryEntry::ByEmpty)
+ {
+ if (rEntry.IsQueryByEmpty())
+ bOk = pCellData->IsEmpty();
+ else
+ {
+ assert(rEntry.IsQueryByNonEmpty());
+ bOk = !pCellData->IsEmpty();
+ }
+ }
+ else if (rEntry.GetQueryItem().meType != ScQueryEntry::ByString && pCellData->IsValue())
+ { // by Value
+ double nCellVal = pCellData->GetValue();
+
+ switch (rEntry.eOp)
+ {
+ case SC_EQUAL :
+ bOk = ::rtl::math::approxEqual(nCellVal, rItem.mfVal);
+ break;
+ case SC_LESS :
+ bOk = (nCellVal < rItem.mfVal) && !::rtl::math::approxEqual(nCellVal, rItem.mfVal);
+ break;
+ case SC_GREATER :
+ bOk = (nCellVal > rItem.mfVal) && !::rtl::math::approxEqual(nCellVal, rItem.mfVal);
+ break;
+ case SC_LESS_EQUAL :
+ bOk = (nCellVal < rItem.mfVal) || ::rtl::math::approxEqual(nCellVal, rItem.mfVal);
+ break;
+ case SC_GREATER_EQUAL :
+ bOk = (nCellVal > rItem.mfVal) || ::rtl::math::approxEqual(nCellVal, rItem.mfVal);
+ break;
+ case SC_NOT_EQUAL :
+ bOk = !::rtl::math::approxEqual(nCellVal, rItem.mfVal);
+ break;
+ default:
+ bOk= false;
+ break;
+ }
+ }
+ else if ((rEntry.eOp == SC_EQUAL || rEntry.eOp == SC_NOT_EQUAL)
+ || (rEntry.GetQueryItem().meType == ScQueryEntry::ByString
+ && pCellData->HasStringData() )
+ )
+ { // by String
+ OUString aCellStr = pCellData->GetString();
+
+ bool bRealWildOrRegExp = (rParam.eSearchType != utl::SearchParam::SearchType::Normal &&
+ ((rEntry.eOp == SC_EQUAL) || (rEntry.eOp == SC_NOT_EQUAL)));
+ if (bRealWildOrRegExp)
+ {
+ sal_Int32 nStart = 0;
+ sal_Int32 nEnd = aCellStr.getLength();
+
+ bool bMatch = rEntry.GetSearchTextPtr( rParam.eSearchType, rParam.bCaseSens, bMatchWholeCell )
+ ->SearchForward( aCellStr, &nStart, &nEnd );
+ // from 614 on, nEnd is behind the found text
+ if (bMatch && bMatchWholeCell
+ && (nStart != 0 || nEnd != aCellStr.getLength()))
+ bMatch = false; // RegExp must match entire cell string
+
+ bOk = ((rEntry.eOp == SC_NOT_EQUAL) ? !bMatch : bMatch);
+ }
+ else
+ {
+ if (rEntry.eOp == SC_EQUAL || rEntry.eOp == SC_NOT_EQUAL)
+ {
+ if (bMatchWholeCell)
+ {
+ // TODO: Use shared string for fast equality check.
+ OUString aStr = rEntry.GetQueryItem().maString.getString();
+ bOk = rTransliteration.isEqual(aCellStr, aStr);
+ bool bHasStar = false;
+ sal_Int32 nIndex;
+ if (( nIndex = aStr.indexOf('*') ) != -1)
+ bHasStar = true;
+ if (bHasStar && (nIndex>0))
+ {
+ for (sal_Int32 j=0;(j<nIndex) && (j< aCellStr.getLength()) ; j++)
+ {
+ if (aCellStr[j] == aStr[j])
+ {
+ bOk=true;
+ }
+ else
+ {
+ bOk=false;
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ OUString aQueryStr = rEntry.GetQueryItem().maString.getString();
+ css::uno::Sequence< sal_Int32 > xOff;
+ const LanguageType nLang = ScGlobal::oSysLocale->GetLanguageTag().getLanguageType();
+ OUString aCell = rTransliteration.transliterate(
+ aCellStr, nLang, 0, aCellStr.getLength(), &xOff);
+ OUString aQuer = rTransliteration.transliterate(
+ aQueryStr, nLang, 0, aQueryStr.getLength(), &xOff);
+ bOk = (aCell.indexOf( aQuer ) != -1);
+ }
+ if (rEntry.eOp == SC_NOT_EQUAL)
+ bOk = !bOk;
+ }
+ else
+ { // use collator here because data was probably sorted
+ sal_Int32 nCompare = rCollator.compareString(
+ aCellStr, rEntry.GetQueryItem().maString.getString());
+ switch (rEntry.eOp)
+ {
+ case SC_LESS :
+ bOk = (nCompare < 0);
+ break;
+ case SC_GREATER :
+ bOk = (nCompare > 0);
+ break;
+ case SC_LESS_EQUAL :
+ bOk = (nCompare <= 0);
+ break;
+ case SC_GREATER_EQUAL :
+ bOk = (nCompare >= 0);
+ break;
+ case SC_NOT_EQUAL:
+ OSL_FAIL("SC_NOT_EQUAL");
+ break;
+ case SC_TOPVAL:
+ case SC_BOTVAL:
+ case SC_TOPPERC:
+ case SC_BOTPERC:
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ if (nPos == -1)
+ {
+ nPos++;
+ aPassed[nPos] = bOk;
+ }
+ else
+ {
+ if (rEntry.eConnect == SC_AND)
+ {
+ aPassed[nPos] = aPassed[nPos] && bOk;
+ }
+ else
+ {
+ nPos++;
+ aPassed[nPos] = bOk;
+ }
+ }
+ }
+
+ for (tools::Long j=1; j <= nPos; j++)
+ aPassed[0] = aPassed[0] || aPassed[j];
+
+ bool bRet = aPassed[0];
+ return bRet;
+}
+
+ScDocument& ScDPCache::GetDoc() const
+{
+ return mrDoc;
+}
+
+tools::Long ScDPCache::GetColumnCount() const
+{
+ return mnColumnCount;
+}
+
+bool ScDPCache::IsRowEmpty(SCROW nRow) const
+{
+ bool bEmpty = true;
+ maEmptyRows.search_tree(nRow, bEmpty);
+ return bEmpty;
+}
+
+const ScDPCache::GroupItems* ScDPCache::GetGroupItems(tools::Long nDim) const
+{
+ if (nDim < 0)
+ return nullptr;
+
+ tools::Long nSourceCount = static_cast<tools::Long>(maFields.size());
+ if (nDim < nSourceCount)
+ return maFields[nDim]->mpGroup.get();
+
+ nDim -= nSourceCount;
+ if (nDim < static_cast<tools::Long>(maGroupFields.size()))
+ return maGroupFields[nDim].get();
+
+ return nullptr;
+}
+
+OUString ScDPCache::GetDimensionName(std::vector<OUString>::size_type nDim) const
+{
+ OSL_ENSURE(nDim < maLabelNames.size()-1 , "ScDPTableDataCache::GetDimensionName");
+ OSL_ENSURE(maLabelNames.size() == static_cast <sal_uInt16> (mnColumnCount+1), "ScDPTableDataCache::GetDimensionName");
+
+ if ( nDim+1 < maLabelNames.size() )
+ {
+ return maLabelNames[nDim+1];
+ }
+ else
+ return OUString();
+}
+
+void ScDPCache::PostInit()
+{
+ OSL_ENSURE(!maFields.empty(), "Cache not initialized!");
+
+ maEmptyRows.build_tree();
+ auto it = maEmptyRows.rbegin();
+ OSL_ENSURE(it != maEmptyRows.rend(), "corrupt flat_segment_tree instance!");
+ mnDataSize = maFields[0]->maData.size();
+ ++it; // Skip the first position.
+ OSL_ENSURE(it != maEmptyRows.rend(), "buggy version of flat_segment_tree is used.");
+ if (it->second)
+ {
+ SCROW nLastNonEmpty = it->first - 1;
+ if (nLastNonEmpty+1 < mnDataSize)
+ mnDataSize = nLastNonEmpty+1;
+ }
+}
+
+void ScDPCache::Clear()
+{
+ mnColumnCount = 0;
+ mnRowCount = 0;
+ maFields.clear();
+ maLabelNames.clear();
+ maGroupFields.clear();
+ maEmptyRows.clear();
+ maStringPools.clear();
+}
+
+SCROW ScDPCache::GetItemDataId(sal_uInt16 nDim, SCROW nRow, bool bRepeatIfEmpty) const
+{
+ OSL_ENSURE(nDim < mnColumnCount, "ScDPTableDataCache::GetItemDataId ");
+
+ const Field& rField = *maFields[nDim];
+ if (o3tl::make_unsigned(nRow) >= rField.maData.size())
+ {
+ // nRow is in the trailing empty rows area.
+ if (bRepeatIfEmpty)
+ nRow = rField.maData.size()-1; // Move to the last non-empty row.
+ else
+ // Return the last item, which should always be empty if the
+ // initialization has skipped trailing empty rows.
+ return rField.maItems.size()-1;
+
+ }
+ else if (bRepeatIfEmpty)
+ {
+ while (nRow > 0 && rField.maItems[rField.maData[nRow]].IsEmpty())
+ --nRow;
+ }
+
+ return rField.maData[nRow];
+}
+
+const ScDPItemData* ScDPCache::GetItemDataById(tools::Long nDim, SCROW nId) const
+{
+ if (nDim < 0 || nId < 0)
+ return nullptr;
+
+ size_t nSourceCount = maFields.size();
+ size_t nDimPos = static_cast<size_t>(nDim);
+ size_t nItemId = static_cast<size_t>(nId);
+ if (nDimPos < nSourceCount)
+ {
+ // source field.
+ const Field& rField = *maFields[nDimPos];
+ if (nItemId < rField.maItems.size())
+ return &rField.maItems[nItemId];
+
+ if (!rField.mpGroup)
+ return nullptr;
+
+ nItemId -= rField.maItems.size();
+ const ScDPItemDataVec& rGI = rField.mpGroup->maItems;
+ if (nItemId >= rGI.size())
+ return nullptr;
+
+ return &rGI[nItemId];
+ }
+
+ // Try group fields.
+ nDimPos -= nSourceCount;
+ if (nDimPos >= maGroupFields.size())
+ return nullptr;
+
+ const ScDPItemDataVec& rGI = maGroupFields[nDimPos]->maItems;
+ if (nItemId >= rGI.size())
+ return nullptr;
+
+ return &rGI[nItemId];
+}
+
+size_t ScDPCache::GetFieldCount() const
+{
+ return maFields.size();
+}
+
+size_t ScDPCache::GetGroupFieldCount() const
+{
+ return maGroupFields.size();
+}
+
+SCROW ScDPCache::GetRowCount() const
+{
+ return mnRowCount;
+}
+
+SCROW ScDPCache::GetDataSize() const
+{
+ OSL_ENSURE(mnDataSize <= GetRowCount(), "Data size should never be larger than the row count.");
+ return mnDataSize >= 0 ? mnDataSize : 0;
+}
+
+const ScDPCache::IndexArrayType* ScDPCache::GetFieldIndexArray( size_t nDim ) const
+{
+ if (nDim >= maFields.size())
+ return nullptr;
+
+ return &maFields[nDim]->maData;
+}
+
+const ScDPCache::ScDPItemDataVec& ScDPCache::GetDimMemberValues(SCCOL nDim) const
+{
+ OSL_ENSURE( nDim>=0 && nDim < mnColumnCount ," nDim < mnColumnCount ");
+ return maFields.at(nDim)->maItems;
+}
+
+sal_uInt32 ScDPCache::GetNumberFormat( tools::Long nDim ) const
+{
+ if ( nDim >= mnColumnCount )
+ return 0;
+
+ // TODO: Find a way to determine the dominant number format in presence of
+ // multiple number formats in the same field.
+ return maFields[nDim]->mnNumFormat;
+}
+
+bool ScDPCache::IsDateDimension( tools::Long nDim ) const
+{
+ if (nDim >= mnColumnCount)
+ return false;
+
+ SvNumberFormatter* pFormatter = mrDoc.GetFormatTable();
+ if (!pFormatter)
+ return false;
+
+ SvNumFormatType eType = pFormatter->GetType(maFields[nDim]->mnNumFormat);
+ return (eType == SvNumFormatType::DATE) || (eType == SvNumFormatType::DATETIME);
+}
+
+tools::Long ScDPCache::GetDimMemberCount(tools::Long nDim) const
+{
+ OSL_ENSURE( nDim>=0 && nDim < mnColumnCount ," ScDPTableDataCache::GetDimMemberCount : out of bound ");
+ return maFields[nDim]->maItems.size();
+}
+
+SCCOL ScDPCache::GetDimensionIndex(std::u16string_view sName) const
+{
+ for (size_t i = 1; i < maLabelNames.size(); ++i)
+ {
+ if (maLabelNames[i] == sName)
+ return static_cast<SCCOL>(i-1);
+ }
+ return -1;
+}
+
+rtl_uString* ScDPCache::InternString( size_t nDim, const OUString& rStr )
+{
+ assert(nDim < maStringPools.size());
+ return internString(maStringPools[nDim], rStr);
+}
+
+void ScDPCache::AddReference(ScDPObject* pObj) const
+{
+ maRefObjects.insert(pObj);
+}
+
+void ScDPCache::RemoveReference(ScDPObject* pObj) const
+{
+ if (mbDisposing)
+ // Object being deleted.
+ return;
+
+ maRefObjects.erase(pObj);
+ if (maRefObjects.empty())
+ mrDoc.GetDPCollection()->RemoveCache(this);
+}
+
+const ScDPCache::ScDPObjectSet& ScDPCache::GetAllReferences() const
+{
+ return maRefObjects;
+}
+
+SCROW ScDPCache::GetIdByItemData(tools::Long nDim, const ScDPItemData& rItem) const
+{
+ if (nDim < 0)
+ return -1;
+
+ if (nDim < mnColumnCount)
+ {
+ // source field.
+ const ScDPItemDataVec& rItems = maFields[nDim]->maItems;
+ for (size_t i = 0, n = rItems.size(); i < n; ++i)
+ {
+ if (rItems[i] == rItem)
+ return i;
+ }
+
+ if (!maFields[nDim]->mpGroup)
+ return -1;
+
+ // grouped source field.
+ const ScDPItemDataVec& rGI = maFields[nDim]->mpGroup->maItems;
+ for (size_t i = 0, n = rGI.size(); i < n; ++i)
+ {
+ if (rGI[i] == rItem)
+ return rItems.size() + i;
+ }
+ return -1;
+ }
+
+ // group field.
+ nDim -= mnColumnCount;
+ if (o3tl::make_unsigned(nDim) < maGroupFields.size())
+ {
+ const ScDPItemDataVec& rGI = maGroupFields[nDim]->maItems;
+ for (size_t i = 0, n = rGI.size(); i < n; ++i)
+ {
+ if (rGI[i] == rItem)
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+// static
+sal_uInt32 ScDPCache::GetLocaleIndependentFormat( SvNumberFormatter& rFormatter, sal_uInt32 nNumFormat )
+{
+ // For a date or date+time format use ISO format so it works across locales
+ // and can be matched against string based item queries. For time use 24h
+ // format. All others use General format, no currency, percent, ...
+ // Use en-US locale for all.
+ switch (rFormatter.GetType( nNumFormat))
+ {
+ case SvNumFormatType::DATE:
+ return rFormatter.GetFormatIndex( NF_DATE_ISO_YYYYMMDD, LANGUAGE_ENGLISH_US);
+ case SvNumFormatType::TIME:
+ return rFormatter.GetFormatIndex( NF_TIME_HHMMSS, LANGUAGE_ENGLISH_US);
+ case SvNumFormatType::DATETIME:
+ return rFormatter.GetFormatIndex( NF_DATETIME_ISO_YYYYMMDD_HHMMSS, LANGUAGE_ENGLISH_US);
+ default:
+ return rFormatter.GetFormatIndex( NF_NUMBER_STANDARD, LANGUAGE_ENGLISH_US);
+ }
+}
+
+// static
+OUString ScDPCache::GetLocaleIndependentFormattedNumberString( double fValue )
+{
+ return rtl::math::doubleToUString( fValue, rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max, '.', true);
+}
+
+// static
+OUString ScDPCache::GetLocaleIndependentFormattedString( double fValue,
+ SvNumberFormatter& rFormatter, sal_uInt32 nNumFormat )
+{
+ nNumFormat = GetLocaleIndependentFormat( rFormatter, nNumFormat);
+ if ((nNumFormat % SV_COUNTRY_LANGUAGE_OFFSET) == 0)
+ return GetLocaleIndependentFormattedNumberString( fValue);
+
+ OUString aStr;
+ const Color* pColor = nullptr;
+ rFormatter.GetOutputString( fValue, nNumFormat, aStr, &pColor);
+ return aStr;
+}
+
+OUString ScDPCache::GetFormattedString(tools::Long nDim, const ScDPItemData& rItem, bool bLocaleIndependent) const
+{
+ if (nDim < 0)
+ return rItem.GetString();
+
+ ScDPItemData::Type eType = rItem.GetType();
+ if (eType == ScDPItemData::Value)
+ {
+ // Format value using the stored number format.
+ SvNumberFormatter* pFormatter = mrDoc.GetFormatTable();
+ if (pFormatter)
+ {
+ sal_uInt32 nNumFormat = GetNumberFormat(nDim);
+ if (bLocaleIndependent)
+ return GetLocaleIndependentFormattedString( rItem.GetValue(), *pFormatter, nNumFormat);
+
+ OUString aStr;
+ const Color* pColor = nullptr;
+ pFormatter->GetOutputString(rItem.GetValue(), nNumFormat, aStr, &pColor);
+ return aStr;
+ }
+
+ // Last resort...
+ return GetLocaleIndependentFormattedNumberString( rItem.GetValue());
+ }
+
+ if (eType == ScDPItemData::GroupValue)
+ {
+ ScDPItemData::GroupValueAttr aAttr = rItem.GetGroupValue();
+ double fStart = 0.0, fEnd = 0.0;
+ const GroupItems* p = GetGroupItems(nDim);
+ if (p)
+ {
+ fStart = p->maInfo.mfStart;
+ fEnd = p->maInfo.mfEnd;
+ }
+ return ScDPUtil::getDateGroupName(
+ aAttr.mnGroupType, aAttr.mnValue, mrDoc.GetFormatTable(), fStart, fEnd);
+ }
+
+ if (eType == ScDPItemData::RangeStart)
+ {
+ double fVal = rItem.GetValue();
+ const GroupItems* p = GetGroupItems(nDim);
+ if (!p)
+ return rItem.GetString();
+
+ sal_Unicode cDecSep = ScGlobal::getLocaleData().getNumDecimalSep()[0];
+ return ScDPUtil::getNumGroupName(fVal, p->maInfo, cDecSep, mrDoc.GetFormatTable());
+ }
+
+ return rItem.GetString();
+}
+
+SvNumberFormatter* ScDPCache::GetNumberFormatter() const
+{
+ return mrDoc.GetFormatTable();
+}
+
+tools::Long ScDPCache::AppendGroupField()
+{
+ maGroupFields.push_back(std::make_unique<GroupItems>());
+ return static_cast<tools::Long>(maFields.size() + maGroupFields.size() - 1);
+}
+
+void ScDPCache::ResetGroupItems(tools::Long nDim, const ScDPNumGroupInfo& rNumInfo, sal_Int32 nGroupType)
+{
+ if (nDim < 0)
+ return;
+
+ tools::Long nSourceCount = static_cast<tools::Long>(maFields.size());
+ if (nDim < nSourceCount)
+ {
+ maFields.at(nDim)->mpGroup.reset(new GroupItems(rNumInfo, nGroupType));
+ return;
+ }
+
+ nDim -= nSourceCount;
+ if (nDim < static_cast<tools::Long>(maGroupFields.size()))
+ {
+ GroupItems& rGI = *maGroupFields[nDim];
+ rGI.maItems.clear();
+ rGI.maInfo = rNumInfo;
+ rGI.mnGroupType = nGroupType;
+ }
+}
+
+SCROW ScDPCache::SetGroupItem(tools::Long nDim, const ScDPItemData& rData)
+{
+ if (nDim < 0)
+ return -1;
+
+ tools::Long nSourceCount = static_cast<tools::Long>(maFields.size());
+ if (nDim < nSourceCount)
+ {
+ GroupItems& rGI = *maFields.at(nDim)->mpGroup;
+ rGI.maItems.push_back(rData);
+ SCROW nId = maFields[nDim]->maItems.size() + rGI.maItems.size() - 1;
+ return nId;
+ }
+
+ nDim -= nSourceCount;
+ if (nDim < static_cast<tools::Long>(maGroupFields.size()))
+ {
+ ScDPItemDataVec& rItems = maGroupFields.at(nDim)->maItems;
+ rItems.push_back(rData);
+ return rItems.size()-1;
+ }
+
+ return -1;
+}
+
+void ScDPCache::GetGroupDimMemberIds(tools::Long nDim, std::vector<SCROW>& rIds) const
+{
+ if (nDim < 0)
+ return;
+
+ tools::Long nSourceCount = static_cast<tools::Long>(maFields.size());
+ if (nDim < nSourceCount)
+ {
+ if (!maFields.at(nDim)->mpGroup)
+ return;
+
+ size_t nOffset = maFields[nDim]->maItems.size();
+ const ScDPItemDataVec& rGI = maFields[nDim]->mpGroup->maItems;
+ for (size_t i = 0, n = rGI.size(); i < n; ++i)
+ rIds.push_back(static_cast<SCROW>(i + nOffset));
+
+ return;
+ }
+
+ nDim -= nSourceCount;
+ if (nDim < static_cast<tools::Long>(maGroupFields.size()))
+ {
+ const ScDPItemDataVec& rGI = maGroupFields.at(nDim)->maItems;
+ for (size_t i = 0, n = rGI.size(); i < n; ++i)
+ rIds.push_back(static_cast<SCROW>(i));
+ }
+}
+
+namespace {
+
+struct ClearGroupItems
+{
+ void operator() (const std::unique_ptr<ScDPCache::Field>& r) const
+ {
+ r->mpGroup.reset();
+ }
+};
+
+}
+
+void ScDPCache::ClearGroupFields()
+{
+ maGroupFields.clear();
+}
+
+void ScDPCache::ClearAllFields()
+{
+ ClearGroupFields();
+ std::for_each(maFields.begin(), maFields.end(), ClearGroupItems());
+}
+
+const ScDPNumGroupInfo* ScDPCache::GetNumGroupInfo(tools::Long nDim) const
+{
+ if (nDim < 0)
+ return nullptr;
+
+ tools::Long nSourceCount = static_cast<tools::Long>(maFields.size());
+ if (nDim < nSourceCount)
+ {
+ if (!maFields.at(nDim)->mpGroup)
+ return nullptr;
+
+ return &maFields[nDim]->mpGroup->maInfo;
+ }
+
+ nDim -= nSourceCount;
+ if (nDim < static_cast<tools::Long>(maGroupFields.size()))
+ return &maGroupFields.at(nDim)->maInfo;
+
+ return nullptr;
+}
+
+sal_Int32 ScDPCache::GetGroupType(tools::Long nDim) const
+{
+ if (nDim < 0)
+ return 0;
+
+ tools::Long nSourceCount = static_cast<tools::Long>(maFields.size());
+ if (nDim < nSourceCount)
+ {
+ if (!maFields.at(nDim)->mpGroup)
+ return 0;
+
+ return maFields[nDim]->mpGroup->mnGroupType;
+ }
+
+ nDim -= nSourceCount;
+ if (nDim < static_cast<tools::Long>(maGroupFields.size()))
+ return maGroupFields.at(nDim)->mnGroupType;
+
+ return 0;
+}
+
+#if DUMP_PIVOT_TABLE
+
+namespace {
+
+void dumpItems(const ScDPCache& rCache, tools::Long nDim, const ScDPCache::ScDPItemDataVec& rItems, size_t nOffset)
+{
+ for (size_t i = 0; i < rItems.size(); ++i)
+ cout << " " << (i+nOffset) << ": " << rCache.GetFormattedString(nDim, rItems[i], false) << endl;
+}
+
+void dumpSourceData(const ScDPCache& rCache, tools::Long nDim, const ScDPCache::ScDPItemDataVec& rItems, const ScDPCache::IndexArrayType& rArray)
+{
+ for (const auto& rIndex : rArray)
+ cout << " '" << rCache.GetFormattedString(nDim, rItems[rIndex], false) << "'" << endl;
+}
+
+const char* getGroupTypeName(sal_Int32 nType)
+{
+ static const char* pNames[] = {
+ "", "years", "quarters", "months", "days", "hours", "minutes", "seconds"
+ };
+
+ switch (nType)
+ {
+ case sheet::DataPilotFieldGroupBy::YEARS: return pNames[1];
+ case sheet::DataPilotFieldGroupBy::QUARTERS: return pNames[2];
+ case sheet::DataPilotFieldGroupBy::MONTHS: return pNames[3];
+ case sheet::DataPilotFieldGroupBy::DAYS: return pNames[4];
+ case sheet::DataPilotFieldGroupBy::HOURS: return pNames[5];
+ case sheet::DataPilotFieldGroupBy::MINUTES: return pNames[6];
+ case sheet::DataPilotFieldGroupBy::SECONDS: return pNames[7];
+ default:
+ ;
+ }
+
+ return pNames[0];
+}
+
+}
+
+void ScDPCache::Dump() const
+{
+ // Change these flags to fit your debugging needs.
+ bool bDumpItems = false;
+ bool bDumpSourceData = false;
+
+ cout << "--- pivot cache dump" << endl;
+ {
+ size_t i = 0;
+ for (const auto& rxField : maFields)
+ {
+ const Field& fld = *rxField;
+ cout << "* source dimension: " << GetDimensionName(i) << " (ID = " << i << ")" << endl;
+ cout << " item count: " << fld.maItems.size() << endl;
+ if (bDumpItems)
+ dumpItems(*this, i, fld.maItems, 0);
+ if (fld.mpGroup)
+ {
+ cout << " group item count: " << fld.mpGroup->maItems.size() << endl;
+ cout << " group type: " << getGroupTypeName(fld.mpGroup->mnGroupType) << endl;
+ if (bDumpItems)
+ dumpItems(*this, i, fld.mpGroup->maItems, fld.maItems.size());
+ }
+
+ if (bDumpSourceData)
+ {
+ cout << " source data (re-constructed):" << endl;
+ dumpSourceData(*this, i, fld.maItems, fld.maData);
+ }
+
+ ++i;
+ }
+ }
+
+ {
+ size_t i = maFields.size();
+ for (const auto& rxGroupField : maGroupFields)
+ {
+ const GroupItems& gi = *rxGroupField;
+ cout << "* group dimension: (unnamed) (ID = " << i << ")" << endl;
+ cout << " item count: " << gi.maItems.size() << endl;
+ cout << " group type: " << getGroupTypeName(gi.mnGroupType) << endl;
+ if (bDumpItems)
+ dumpItems(*this, i, gi.maItems, 0);
+ ++i;
+ }
+ }
+
+ {
+ struct { SCROW start; SCROW end; bool empty; } aRange;
+ cout << "* empty rows: " << endl;
+ mdds::flat_segment_tree<SCROW, bool>::const_iterator it = maEmptyRows.begin(), itEnd = maEmptyRows.end();
+ if (it != itEnd)
+ {
+ aRange.start = it->first;
+ aRange.empty = it->second;
+
+ for (++it; it != itEnd; ++it)
+ {
+ aRange.end = it->first-1;
+ cout << " rows " << aRange.start << "-" << aRange.end << ": " << (aRange.empty ? "empty" : "not-empty") << endl;
+ aRange.start = it->first;
+ aRange.empty = it->second;
+ }
+ }
+ }
+
+ cout << "---" << endl;
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dpdimsave.cxx b/sc/source/core/data/dpdimsave.cxx
new file mode 100644
index 000000000..07ba25f0a
--- /dev/null
+++ b/sc/source/core/data/dpdimsave.cxx
@@ -0,0 +1,791 @@
+/* -*- 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 <dpcache.hxx>
+#include <dpdimsave.hxx>
+#include <dpgroup.hxx>
+#include <dpobject.hxx>
+#include <dputil.hxx>
+#include <document.hxx>
+
+#include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp>
+
+#include <svl/numformat.hxx>
+#include <osl/diagnose.h>
+#include <rtl/math.hxx>
+#include <algorithm>
+
+#include <globstr.hrc>
+#include <scresid.hxx>
+
+using namespace com::sun::star;
+
+ScDPSaveGroupItem::ScDPSaveGroupItem( const OUString& rName ) :
+ aGroupName(rName) {}
+
+ScDPSaveGroupItem::~ScDPSaveGroupItem() {}
+
+void ScDPSaveGroupItem::AddElement( const OUString& rName )
+{
+ aElements.push_back(rName);
+}
+
+void ScDPSaveGroupItem::AddElementsFromGroup( const ScDPSaveGroupItem& rGroup )
+{
+ // add all elements of the other group (used for nested grouping)
+ aElements.insert( aElements.end(), rGroup.aElements.begin(), rGroup.aElements.end() );
+}
+
+bool ScDPSaveGroupItem::RemoveElement( const OUString& rName )
+{
+ auto it = std::find(aElements.begin(), aElements.end(), rName); //TODO: ignore case
+ if (it != aElements.end())
+ {
+ aElements.erase(it);
+ return true;
+ }
+ return false; // not found
+}
+
+bool ScDPSaveGroupItem::IsEmpty() const
+{
+ return aElements.empty();
+}
+
+size_t ScDPSaveGroupItem::GetElementCount() const
+{
+ return aElements.size();
+}
+
+const OUString* ScDPSaveGroupItem::GetElementByIndex(size_t nIndex) const
+{
+ return (nIndex < aElements.size()) ? &aElements[ nIndex ] : nullptr;
+}
+
+void ScDPSaveGroupItem::Rename( const OUString& rNewName )
+{
+ aGroupName = rNewName;
+}
+
+void ScDPSaveGroupItem::RemoveElementsFromGroups( ScDPSaveGroupDimension& rDimension ) const
+{
+ // remove this group's elements from their groups in rDimension
+ // (rDimension must be a different dimension from the one which contains this)
+
+ for ( const auto& rElement : aElements )
+ rDimension.RemoveFromGroups( rElement );
+}
+
+void ScDPSaveGroupItem::ConvertElementsToItems(SvNumberFormatter* pFormatter) const
+{
+ maItems.reserve(aElements.size());
+ for (const auto& rElement : aElements)
+ {
+ sal_uInt32 nFormat = 0;
+ double fValue;
+ ScDPItemData aData;
+ if (pFormatter->IsNumberFormat(rElement, nFormat, fValue))
+ aData.SetValue(fValue);
+ else
+ aData.SetString(rElement);
+
+ maItems.push_back(aData);
+ }
+}
+
+bool ScDPSaveGroupItem::HasInGroup(const ScDPItemData& rItem) const
+{
+ return std::find(maItems.begin(), maItems.end(), rItem) != maItems.end();
+}
+
+void ScDPSaveGroupItem::AddToData(ScDPGroupDimension& rDataDim) const
+{
+ ScDPGroupItem aGroup(aGroupName);
+ for (const auto& rItem : maItems)
+ aGroup.AddElement(rItem);
+
+ rDataDim.AddItem(aGroup);
+}
+
+ScDPSaveGroupDimension::ScDPSaveGroupDimension( const OUString& rSource, const OUString& rName ) :
+ aSourceDim( rSource ),
+ aGroupDimName( rName ),
+ nDatePart( 0 )
+{
+}
+
+ScDPSaveGroupDimension::ScDPSaveGroupDimension( const OUString& rSource, const OUString& rName, const ScDPNumGroupInfo& rDateInfo, sal_Int32 nPart ) :
+ aSourceDim( rSource ),
+ aGroupDimName( rName ),
+ aDateInfo( rDateInfo ),
+ nDatePart( nPart )
+{
+}
+
+void ScDPSaveGroupDimension::SetDateInfo( const ScDPNumGroupInfo& rInfo, sal_Int32 nPart )
+{
+ aDateInfo = rInfo;
+ nDatePart = nPart;
+}
+
+void ScDPSaveGroupDimension::AddGroupItem( const ScDPSaveGroupItem& rItem )
+{
+ aGroups.push_back( rItem );
+}
+
+OUString ScDPSaveGroupDimension::CreateGroupName(std::u16string_view rPrefix)
+{
+ // create a name for a new group, using "Group1", "Group2" etc. (translated prefix in rPrefix)
+
+ //TODO: look in all dimensions, to avoid clashes with automatic groups (=name of base element)?
+ //TODO: (only dimensions for the same base)
+
+ sal_Int32 nAdd = 1; // first try is "Group1"
+ const sal_Int32 nMaxAdd = nAdd + aGroups.size(); // limit the loop
+ while ( nAdd <= nMaxAdd )
+ {
+ OUString aGroupName = rPrefix + OUString::number( nAdd );
+
+ // look for existing groups
+ bool bExists = std::any_of(aGroups.begin(), aGroups.end(),
+ [&aGroupName](const ScDPSaveGroupItem& rGroup) {
+ return rGroup.GetGroupName() == aGroupName; //TODO: ignore case
+ });
+
+ if ( !bExists )
+ return aGroupName; // found a new name
+
+ ++nAdd; // continue with higher number
+ }
+
+ OSL_FAIL("CreateGroupName: no valid name found");
+ return OUString();
+}
+
+const ScDPSaveGroupItem* ScDPSaveGroupDimension::GetNamedGroup( const OUString& rGroupName ) const
+{
+ return const_cast< ScDPSaveGroupDimension* >( this )->GetNamedGroupAcc( rGroupName );
+}
+
+ScDPSaveGroupItem* ScDPSaveGroupDimension::GetNamedGroupAcc( const OUString& rGroupName )
+{
+ auto aIter = std::find_if(aGroups.begin(), aGroups.end(),
+ [&rGroupName](const ScDPSaveGroupItem& rGroup) {
+ return rGroup.GetGroupName() == rGroupName; //TODO: ignore case
+ });
+ if (aIter != aGroups.end())
+ return &*aIter;
+
+ return nullptr; // none found
+}
+
+tools::Long ScDPSaveGroupDimension::GetGroupCount() const
+{
+ return aGroups.size();
+}
+
+const ScDPSaveGroupItem& ScDPSaveGroupDimension::GetGroupByIndex( tools::Long nIndex ) const
+{
+ return aGroups[nIndex];
+}
+
+
+void ScDPSaveGroupDimension::RemoveFromGroups( const OUString& rItemName )
+{
+ // if the item is in any group, remove it from the group,
+ // also remove the group if it is empty afterwards
+
+ for ( ScDPSaveGroupItemVec::iterator aIter(aGroups.begin()); aIter != aGroups.end(); ++aIter )
+ if ( aIter->RemoveElement( rItemName ) )
+ {
+ if ( aIter->IsEmpty() ) // removed last item from the group?
+ aGroups.erase( aIter ); // then remove the group
+
+ return; // don't have to look further
+ }
+}
+
+void ScDPSaveGroupDimension::RemoveGroup(const OUString& rGroupName)
+{
+ auto aIter = std::find_if(aGroups.begin(), aGroups.end(),
+ [&rGroupName](const ScDPSaveGroupItem& rGroup) {
+ return rGroup.GetGroupName() == rGroupName; //TODO: ignore case
+ });
+ if (aIter != aGroups.end())
+ aGroups.erase( aIter );
+}
+
+bool ScDPSaveGroupDimension::IsEmpty() const
+{
+ return aGroups.empty();
+}
+
+bool ScDPSaveGroupDimension::HasOnlyHidden(const ScDPUniqueStringSet& rVisible)
+{
+ // check if there are only groups that don't appear in the list of visible names
+
+ return std::none_of(aGroups.begin(), aGroups.end(),
+ [&rVisible](const ScDPSaveGroupItem& rGroup) { return rVisible.count(rGroup.GetGroupName()) > 0; });
+}
+
+void ScDPSaveGroupDimension::Rename( const OUString& rNewName )
+{
+ aGroupDimName = rNewName;
+}
+
+bool ScDPSaveGroupDimension::IsInGroup(const ScDPItemData& rItem) const
+{
+ return std::any_of(aGroups.begin(), aGroups.end(),
+ [&rItem](const ScDPSaveGroupItem& rGroup) { return rGroup.HasInGroup(rItem); });
+}
+
+namespace {
+
+bool isInteger(double fValue)
+{
+ return rtl::math::approxEqual(fValue, rtl::math::approxFloor(fValue));
+}
+
+void fillDateGroupDimension(
+ ScDPCache& rCache, ScDPNumGroupInfo& rDateInfo, tools::Long nSourceDim, tools::Long nGroupDim,
+ sal_Int32 nDatePart, const SvNumberFormatter* pFormatter)
+{
+ // Auto min/max is only used for "Years" part, but the loop is always
+ // needed.
+ double fSourceMin = 0.0;
+ double fSourceMax = 0.0;
+ bool bFirst = true;
+
+ const ScDPCache::ScDPItemDataVec& rItems = rCache.GetDimMemberValues(nSourceDim);
+ for (const ScDPItemData& rItem : rItems)
+ {
+ if (rItem.GetType() != ScDPItemData::Value)
+ continue;
+
+ double fVal = rItem.GetValue();
+ if (bFirst)
+ {
+ fSourceMin = fSourceMax = fVal;
+ bFirst = false;
+ }
+ else
+ {
+ if (fVal < fSourceMin)
+ fSourceMin = fVal;
+ if ( fVal > fSourceMax )
+ fSourceMax = fVal;
+ }
+ }
+
+ // For the start/end values, use the same date rounding as in
+ // ScDPNumGroupDimension::GetNumEntries (but not for the list of
+ // available years).
+ if (rDateInfo.mbAutoStart)
+ rDateInfo.mfStart = rtl::math::approxFloor(fSourceMin);
+ if (rDateInfo.mbAutoEnd)
+ rDateInfo.mfEnd = rtl::math::approxFloor(fSourceMax) + 1;
+
+ //TODO: if not automatic, limit fSourceMin/fSourceMax for list of year values?
+
+ tools::Long nStart = 0, nEnd = 0; // end is inclusive
+
+ switch (nDatePart)
+ {
+ case sheet::DataPilotFieldGroupBy::YEARS:
+ nStart = ScDPUtil::getDatePartValue(
+ fSourceMin, nullptr, sheet::DataPilotFieldGroupBy::YEARS, pFormatter);
+ nEnd = ScDPUtil::getDatePartValue(fSourceMax, nullptr, sheet::DataPilotFieldGroupBy::YEARS, pFormatter);
+ break;
+ case sheet::DataPilotFieldGroupBy::QUARTERS: nStart = 1; nEnd = 4; break;
+ case sheet::DataPilotFieldGroupBy::MONTHS: nStart = 1; nEnd = 12; break;
+ case sheet::DataPilotFieldGroupBy::DAYS: nStart = 1; nEnd = 366; break;
+ case sheet::DataPilotFieldGroupBy::HOURS: nStart = 0; nEnd = 23; break;
+ case sheet::DataPilotFieldGroupBy::MINUTES: nStart = 0; nEnd = 59; break;
+ case sheet::DataPilotFieldGroupBy::SECONDS: nStart = 0; nEnd = 59; break;
+ default:
+ OSL_FAIL("invalid date part");
+ }
+
+ // Now, populate the group items in the cache.
+ rCache.ResetGroupItems(nGroupDim, rDateInfo, nDatePart);
+
+ for (tools::Long nValue = nStart; nValue <= nEnd; ++nValue)
+ rCache.SetGroupItem(nGroupDim, ScDPItemData(nDatePart, nValue));
+
+ // add first/last entry (min/max)
+ rCache.SetGroupItem(nGroupDim, ScDPItemData(nDatePart, ScDPItemData::DateFirst));
+ rCache.SetGroupItem(nGroupDim, ScDPItemData(nDatePart, ScDPItemData::DateLast));
+}
+
+}
+
+void ScDPSaveGroupDimension::AddToData( ScDPGroupTableData& rData ) const
+{
+ tools::Long nSourceIndex = rData.GetDimensionIndex( aSourceDim );
+ if ( nSourceIndex < 0 )
+ return;
+
+ ScDPGroupDimension aDim( nSourceIndex, aGroupDimName );
+ if ( nDatePart )
+ {
+ // date grouping
+
+ aDim.SetDateDimension();
+ }
+ else
+ {
+ // normal (manual) grouping
+
+ for (const auto& rGroup : aGroups)
+ rGroup.AddToData(aDim);
+ }
+
+ rData.AddGroupDimension( aDim );
+}
+
+void ScDPSaveGroupDimension::AddToCache(ScDPCache& rCache) const
+{
+ tools::Long nSourceDim = rCache.GetDimensionIndex(aSourceDim);
+ if (nSourceDim < 0)
+ return;
+
+ tools::Long nDim = rCache.AppendGroupField();
+ SvNumberFormatter* pFormatter = rCache.GetDoc().GetFormatTable();
+
+ if (nDatePart)
+ {
+ fillDateGroupDimension(rCache, aDateInfo, nSourceDim, nDim, nDatePart, pFormatter);
+ return;
+ }
+
+ rCache.ResetGroupItems(nDim, aDateInfo, 0);
+ for (const ScDPSaveGroupItem& rGI : aGroups)
+ {
+ rGI.ConvertElementsToItems(pFormatter);
+ rCache.SetGroupItem(nDim, ScDPItemData(rGI.GetGroupName()));
+ }
+
+ const ScDPCache::ScDPItemDataVec& rItems = rCache.GetDimMemberValues(nSourceDim);
+ for (const ScDPItemData& rItem : rItems)
+ {
+ if (!IsInGroup(rItem))
+ // Not in any group. Add as its own group.
+ rCache.SetGroupItem(nDim, rItem);
+ }
+}
+
+ScDPSaveNumGroupDimension::ScDPSaveNumGroupDimension( const OUString& rName, const ScDPNumGroupInfo& rInfo ) :
+ aDimensionName( rName ),
+ aGroupInfo( rInfo ),
+ nDatePart( 0 )
+{
+}
+
+ScDPSaveNumGroupDimension::ScDPSaveNumGroupDimension( const OUString& rName, const ScDPNumGroupInfo& rDateInfo, sal_Int32 nPart ) :
+ aDimensionName( rName ),
+ aDateInfo( rDateInfo ),
+ nDatePart( nPart )
+{
+}
+
+void ScDPSaveNumGroupDimension::AddToData( ScDPGroupTableData& rData ) const
+{
+ tools::Long nSource = rData.GetDimensionIndex( aDimensionName );
+ if ( nSource >= 0 )
+ {
+ ScDPNumGroupDimension aDim( aGroupInfo ); // aGroupInfo: value grouping
+ if ( nDatePart )
+ aDim.SetDateDimension();
+
+ rData.SetNumGroupDimension( nSource, aDim );
+ }
+}
+
+void ScDPSaveNumGroupDimension::AddToCache(ScDPCache& rCache) const
+{
+ tools::Long nDim = rCache.GetDimensionIndex(aDimensionName);
+ if (nDim < 0)
+ return;
+
+ if (aDateInfo.mbEnable)
+ {
+ // Date grouping
+ SvNumberFormatter* pFormatter = rCache.GetDoc().GetFormatTable();
+ fillDateGroupDimension(rCache, aDateInfo, nDim, nDim, nDatePart, pFormatter);
+ }
+ else if (aGroupInfo.mbEnable)
+ {
+ // Number-range grouping
+
+ // Look through the source entries for non-integer numbers, minimum
+ // and maximum.
+
+ // non-integer GroupInfo values count, too
+ aGroupInfo.mbIntegerOnly =
+ (aGroupInfo.mbAutoStart || isInteger(aGroupInfo.mfStart)) &&
+ (aGroupInfo.mbAutoEnd || isInteger(aGroupInfo.mfEnd)) &&
+ isInteger(aGroupInfo.mfStep);
+
+ double fSourceMin = 0.0;
+ double fSourceMax = 0.0;
+ bool bFirst = true;
+
+ const ScDPCache::ScDPItemDataVec& rItems = rCache.GetDimMemberValues(nDim);
+ for (const ScDPItemData& rItem : rItems)
+ {
+ if (rItem.GetType() != ScDPItemData::Value)
+ continue;
+
+ double fValue = rItem.GetValue();
+ if (bFirst)
+ {
+ fSourceMin = fSourceMax = fValue;
+ bFirst = false;
+ continue;
+ }
+
+ if (fValue < fSourceMin)
+ fSourceMin = fValue;
+ if (fValue > fSourceMax)
+ fSourceMax = fValue;
+
+ if (aGroupInfo.mbIntegerOnly && !isInteger(fValue))
+ {
+ // If any non-integer numbers are involved, the group labels
+ // are shown including their upper limit.
+ aGroupInfo.mbIntegerOnly = false;
+ }
+ }
+
+ if (aGroupInfo.mbDateValues)
+ {
+ // special handling for dates: always integer, round down limits
+ aGroupInfo.mbIntegerOnly = true;
+ fSourceMin = rtl::math::approxFloor(fSourceMin);
+ fSourceMax = rtl::math::approxFloor(fSourceMax) + 1;
+ }
+
+ if (aGroupInfo.mbAutoStart)
+ aGroupInfo.mfStart = fSourceMin;
+ if (aGroupInfo.mbAutoEnd)
+ aGroupInfo.mfEnd = fSourceMax;
+
+ //TODO: limit number of entries?
+
+ tools::Long nLoopCount = 0;
+ double fLoop = aGroupInfo.mfStart;
+
+ rCache.ResetGroupItems(nDim, aGroupInfo, 0);
+
+ // Use "less than" instead of "less or equal" for the loop - don't
+ // create a group that consists only of the end value. Instead, the
+ // end value is then included in the last group (last group is bigger
+ // than the others). The first group has to be created nonetheless.
+ // GetNumGroupForValue has corresponding logic.
+
+ bool bFirstGroup = true;
+ while (bFirstGroup || (fLoop < aGroupInfo.mfEnd && !rtl::math::approxEqual(fLoop, aGroupInfo.mfEnd)))
+ {
+ ScDPItemData aItem;
+ aItem.SetRangeStart(fLoop);
+ rCache.SetGroupItem(nDim, aItem);
+ ++nLoopCount;
+ fLoop = aGroupInfo.mfStart + nLoopCount * aGroupInfo.mfStep;
+ bFirstGroup = false;
+
+ // ScDPItemData values are compared with approxEqual
+ }
+
+ ScDPItemData aItem;
+ aItem.SetRangeFirst();
+ rCache.SetGroupItem(nDim, aItem);
+
+ aItem.SetRangeLast();
+ rCache.SetGroupItem(nDim, aItem);
+ }
+}
+
+void ScDPSaveNumGroupDimension::SetGroupInfo( const ScDPNumGroupInfo& rNew )
+{
+ aGroupInfo = rNew;
+}
+
+void ScDPSaveNumGroupDimension::SetDateInfo( const ScDPNumGroupInfo& rInfo, sal_Int32 nPart )
+{
+ aDateInfo = rInfo;
+ nDatePart = nPart;
+}
+
+namespace {
+
+struct ScDPSaveGroupDimNameFunc
+{
+ OUString maDimName;
+ explicit ScDPSaveGroupDimNameFunc( const OUString& rDimName ) : maDimName( rDimName ) {}
+ bool operator()( const ScDPSaveGroupDimension& rGroupDim ) const { return rGroupDim.GetGroupDimName() == maDimName; }
+};
+
+struct ScDPSaveGroupSourceNameFunc
+{
+ OUString maSrcDimName;
+ explicit ScDPSaveGroupSourceNameFunc( const OUString& rSrcDimName ) : maSrcDimName( rSrcDimName ) {}
+ bool operator()( const ScDPSaveGroupDimension& rGroupDim ) const { return rGroupDim.GetSourceDimName() == maSrcDimName; }
+};
+
+} // namespace
+
+ScDPDimensionSaveData::ScDPDimensionSaveData()
+{
+}
+
+bool ScDPDimensionSaveData::operator==( const ScDPDimensionSaveData& ) const
+{
+ return false;
+}
+
+void ScDPDimensionSaveData::AddGroupDimension( const ScDPSaveGroupDimension& rGroupDim )
+{
+ OSL_ENSURE( ::std::none_of( maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupDimNameFunc( rGroupDim.GetGroupDimName() ) ),
+ "ScDPDimensionSaveData::AddGroupDimension - group dimension exists already" );
+ // ReplaceGroupDimension() adds new or replaces existing
+ ReplaceGroupDimension( rGroupDim );
+}
+
+void ScDPDimensionSaveData::ReplaceGroupDimension( const ScDPSaveGroupDimension& rGroupDim )
+{
+ ScDPSaveGroupDimVec::iterator aIt = ::std::find_if(
+ maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupDimNameFunc( rGroupDim.GetGroupDimName() ) );
+ if( aIt == maGroupDims.end() )
+ maGroupDims.push_back( rGroupDim );
+ else
+ *aIt = rGroupDim;
+}
+
+void ScDPDimensionSaveData::RemoveGroupDimension( const OUString& rGroupDimName )
+{
+ ScDPSaveGroupDimVec::iterator aIt = ::std::find_if(
+ maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupDimNameFunc( rGroupDimName ) );
+ if( aIt != maGroupDims.end() )
+ maGroupDims.erase( aIt );
+}
+
+void ScDPDimensionSaveData::AddNumGroupDimension( const ScDPSaveNumGroupDimension& rGroupDim )
+{
+ OSL_ENSURE( maNumGroupDims.count( rGroupDim.GetDimensionName() ) == 0,
+ "ScDPDimensionSaveData::AddNumGroupDimension - numeric group dimension exists already" );
+ // ReplaceNumGroupDimension() adds new or replaces existing
+ ReplaceNumGroupDimension( rGroupDim );
+}
+
+void ScDPDimensionSaveData::ReplaceNumGroupDimension( const ScDPSaveNumGroupDimension& rGroupDim )
+{
+ ScDPSaveNumGroupDimMap::iterator aIt = maNumGroupDims.find( rGroupDim.GetDimensionName() );
+ if( aIt == maNumGroupDims.end() )
+ maNumGroupDims.emplace( rGroupDim.GetDimensionName(), rGroupDim );
+ else
+ aIt->second = rGroupDim;
+}
+
+void ScDPDimensionSaveData::RemoveNumGroupDimension( const OUString& rGroupDimName )
+{
+ maNumGroupDims.erase( rGroupDimName );
+}
+
+void ScDPDimensionSaveData::WriteToData( ScDPGroupTableData& rData ) const
+{
+ // rData is assumed to be empty
+ // AddToData also handles date grouping
+
+ for( const auto& rGroupDim : maGroupDims )
+ rGroupDim.AddToData( rData );
+
+ for( const auto& rEntry : maNumGroupDims )
+ rEntry.second.AddToData( rData );
+}
+
+void ScDPDimensionSaveData::WriteToCache(ScDPCache& rCache) const
+{
+ for (const auto& rEntry : maGroupDims)
+ rEntry.AddToCache(rCache);
+ for (const auto& rEntry : maNumGroupDims)
+ rEntry.second.AddToCache(rCache);
+}
+
+const ScDPSaveGroupDimension* ScDPDimensionSaveData::GetGroupDimForBase( const OUString& rBaseDimName ) const
+{
+ return const_cast< ScDPDimensionSaveData* >( this )->GetGroupDimAccForBase( rBaseDimName );
+}
+
+const ScDPSaveGroupDimension* ScDPDimensionSaveData::GetNamedGroupDim( const OUString& rGroupDimName ) const
+{
+ return const_cast< ScDPDimensionSaveData* >( this )->GetNamedGroupDimAcc( rGroupDimName );
+}
+
+const ScDPSaveGroupDimension* ScDPDimensionSaveData::GetFirstNamedGroupDim( const OUString& rBaseDimName ) const
+{
+ return const_cast< ScDPDimensionSaveData* >( this )->GetFirstNamedGroupDimAcc( rBaseDimName );
+}
+
+const ScDPSaveGroupDimension* ScDPDimensionSaveData::GetNextNamedGroupDim( const OUString& rGroupDimName ) const
+{
+ return const_cast< ScDPDimensionSaveData* >( this )->GetNextNamedGroupDimAcc( rGroupDimName );
+}
+
+const ScDPSaveNumGroupDimension* ScDPDimensionSaveData::GetNumGroupDim( const OUString& rGroupDimName ) const
+{
+ return const_cast< ScDPDimensionSaveData* >( this )->GetNumGroupDimAcc( rGroupDimName );
+}
+
+ScDPSaveGroupDimension* ScDPDimensionSaveData::GetGroupDimAccForBase( const OUString& rBaseDimName )
+{
+ ScDPSaveGroupDimension* pGroupDim = GetFirstNamedGroupDimAcc( rBaseDimName );
+ return pGroupDim ? pGroupDim : GetNextNamedGroupDimAcc( rBaseDimName );
+}
+
+ScDPSaveGroupDimension* ScDPDimensionSaveData::GetNamedGroupDimAcc( const OUString& rGroupDimName )
+{
+ ScDPSaveGroupDimVec::iterator aIt = ::std::find_if(
+ maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupDimNameFunc( rGroupDimName ) );
+ return (aIt == maGroupDims.end()) ? nullptr : &*aIt;
+}
+
+ScDPSaveGroupDimension* ScDPDimensionSaveData::GetFirstNamedGroupDimAcc( const OUString& rBaseDimName )
+{
+ ScDPSaveGroupDimVec::iterator aIt = ::std::find_if(
+ maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupSourceNameFunc( rBaseDimName ) );
+ return (aIt == maGroupDims.end()) ? nullptr : &*aIt;
+}
+
+ScDPSaveGroupDimension* ScDPDimensionSaveData::GetNextNamedGroupDimAcc( const OUString& rGroupDimName )
+{
+ // find the group dimension with the passed name
+ ScDPSaveGroupDimVec::iterator aIt = ::std::find_if(
+ maGroupDims.begin(), maGroupDims.end(), ScDPSaveGroupDimNameFunc( rGroupDimName ) );
+ // find next group dimension based on the same source dimension name
+ if( aIt != maGroupDims.end() )
+ aIt = ::std::find_if( aIt + 1, maGroupDims.end(), ScDPSaveGroupSourceNameFunc( aIt->GetSourceDimName() ) );
+ return (aIt == maGroupDims.end()) ? nullptr : &*aIt;
+}
+
+ScDPSaveNumGroupDimension* ScDPDimensionSaveData::GetNumGroupDimAcc( const OUString& rGroupDimName )
+{
+ ScDPSaveNumGroupDimMap::iterator aIt = maNumGroupDims.find( rGroupDimName );
+ return (aIt == maNumGroupDims.end()) ? nullptr : &aIt->second;
+}
+
+bool ScDPDimensionSaveData::HasGroupDimensions() const
+{
+ return !maGroupDims.empty() || !maNumGroupDims.empty();
+}
+
+sal_Int32 ScDPDimensionSaveData::CollectDateParts( const OUString& rBaseDimName ) const
+{
+ sal_Int32 nParts = 0;
+ // start with part of numeric group
+ if( const ScDPSaveNumGroupDimension* pNumDim = GetNumGroupDim( rBaseDimName ) )
+ nParts |= pNumDim->GetDatePart();
+ // collect parts from all matching group dimensions
+ for( const ScDPSaveGroupDimension* pGroupDim = GetFirstNamedGroupDim( rBaseDimName ); pGroupDim; pGroupDim = GetNextNamedGroupDim( pGroupDim->GetGroupDimName() ) )
+ nParts |= pGroupDim->GetDatePart();
+
+ return nParts;
+}
+
+OUString ScDPDimensionSaveData::CreateGroupDimName(
+ const OUString& rSourceName, const ScDPObject& rObject, bool bAllowSource,
+ const std::vector<OUString>* pDeletedNames )
+{
+ // create a name for the new dimension by appending a number to the original
+ // dimension's name
+
+ bool bUseSource = bAllowSource; // if set, try the unchanged original name first
+
+ sal_Int32 nAdd = 2; // first try is "Name2"
+ const sal_Int32 nMaxAdd = 1000; // limit the loop
+ while ( nAdd <= nMaxAdd )
+ {
+ OUString aDimName( rSourceName );
+ if ( !bUseSource )
+ aDimName += OUString::number(nAdd);
+
+ // look for existing group dimensions
+ bool bExists = std::any_of(maGroupDims.begin(), maGroupDims.end(),
+ [&aDimName](const ScDPSaveGroupDimension& rDim) {
+ return rDim.GetGroupDimName() == aDimName; //TODO: ignore case
+ });
+
+ // look for base dimensions that happen to have that name
+ if ( !bExists && rObject.IsDimNameInUse( aDimName ) )
+ {
+ if ( pDeletedNames &&
+ std::find( pDeletedNames->begin(), pDeletedNames->end(), aDimName ) != pDeletedNames->end() )
+ {
+ // allow the name anyway if the name is in pDeletedNames
+ }
+ else
+ bExists = true;
+ }
+
+ if ( !bExists )
+ return aDimName; // found a new name
+
+ if ( bUseSource )
+ bUseSource = false;
+ else
+ ++nAdd; // continue with higher number
+ }
+ OSL_FAIL("CreateGroupDimName: no valid name found");
+ return OUString();
+}
+
+namespace
+{
+ const TranslateId aDatePartIds[] =
+ {
+ STR_DPFIELD_GROUP_BY_SECONDS,
+ STR_DPFIELD_GROUP_BY_MINUTES,
+ STR_DPFIELD_GROUP_BY_HOURS,
+ STR_DPFIELD_GROUP_BY_DAYS,
+ STR_DPFIELD_GROUP_BY_MONTHS,
+ STR_DPFIELD_GROUP_BY_QUARTERS,
+ STR_DPFIELD_GROUP_BY_YEARS
+ };
+}
+
+OUString ScDPDimensionSaveData::CreateDateGroupDimName(
+ sal_Int32 nDatePart, const ScDPObject& rObject, bool bAllowSource,
+ const std::vector<OUString>* pDeletedNames )
+{
+ using namespace css::sheet::DataPilotFieldGroupBy;
+ OUString aPartName;
+ switch( nDatePart )
+ {
+ case SECONDS: aPartName = ScResId(aDatePartIds[0]); break;
+ case MINUTES: aPartName = ScResId(aDatePartIds[1]); break;
+ case HOURS: aPartName = ScResId(aDatePartIds[2]); break;
+ case DAYS: aPartName = ScResId(aDatePartIds[3]); break;
+ case MONTHS: aPartName = ScResId(aDatePartIds[4]); break;
+ case QUARTERS: aPartName = ScResId(aDatePartIds[5]); break;
+ case YEARS: aPartName = ScResId(aDatePartIds[6]); break;
+ }
+ OSL_ENSURE(!aPartName.isEmpty(), "ScDPDimensionSaveData::CreateDateGroupDimName - invalid date part");
+ return CreateGroupDimName( aPartName, rObject, bAllowSource, pDeletedNames );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dpfilteredcache.cxx b/sc/source/core/data/dpfilteredcache.cxx
new file mode 100644
index 000000000..b47fc43ae
--- /dev/null
+++ b/sc/source/core/data/dpfilteredcache.cxx
@@ -0,0 +1,433 @@
+/* -*- 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 <dpcache.hxx>
+#include <dpfilteredcache.hxx>
+#include <address.hxx>
+#include <queryparam.hxx>
+#include <dpitemdata.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+#include <algorithm>
+
+using ::std::vector;
+using ::com::sun::star::uno::Sequence;
+using ::com::sun::star::uno::Any;
+
+ScDPFilteredCache::SingleFilter::SingleFilter(const ScDPItemData& rItem) :
+ maItem(rItem) {}
+
+bool ScDPFilteredCache::SingleFilter::match(const ScDPItemData& rCellData) const
+{
+ return maItem == rCellData;
+}
+
+std::vector<ScDPItemData> ScDPFilteredCache::SingleFilter::getMatchValues() const
+{
+ return { maItem };
+}
+
+ScDPFilteredCache::GroupFilter::GroupFilter()
+{
+}
+
+bool ScDPFilteredCache::GroupFilter::match(const ScDPItemData& rCellData) const
+{
+ return std::find(maItems.begin(), maItems.end(), rCellData) != maItems.end();
+}
+
+std::vector<ScDPItemData> ScDPFilteredCache::GroupFilter::getMatchValues() const
+{
+ return maItems;
+}
+
+void ScDPFilteredCache::GroupFilter::addMatchItem(const ScDPItemData& rItem)
+{
+ maItems.push_back(rItem);
+}
+
+size_t ScDPFilteredCache::GroupFilter::getMatchItemCount() const
+{
+ return maItems.size();
+}
+
+ScDPFilteredCache::Criterion::Criterion() :
+ mnFieldIndex(-1)
+{
+}
+
+ScDPFilteredCache::ScDPFilteredCache(const ScDPCache& rCache) :
+ maShowByFilter(0, MAXROW+1, false), maShowByPage(0, MAXROW+1, true), mrCache(rCache)
+{
+}
+
+ScDPFilteredCache::~ScDPFilteredCache()
+{
+}
+
+sal_Int32 ScDPFilteredCache::getRowSize() const
+{
+ return mrCache.GetRowCount();
+}
+
+sal_Int32 ScDPFilteredCache::getColSize() const
+{
+ return mrCache.GetColumnCount();
+}
+
+void ScDPFilteredCache::fillTable(
+ const ScQueryParam& rQuery, bool bIgnoreEmptyRows, bool bRepeatIfEmpty)
+{
+ SCROW nRowCount = getRowSize();
+ SCROW nDataSize = mrCache.GetDataSize();
+ SCCOL nColCount = getColSize();
+ if (nRowCount <= 0 || nColCount <= 0)
+ return;
+
+ maShowByFilter.clear();
+ maShowByPage.clear();
+ maShowByPage.build_tree();
+
+ // Process the non-empty data rows.
+ for (SCROW nRow = 0; nRow < nDataSize; ++nRow)
+ {
+ if (!getCache().ValidQuery(nRow, rQuery))
+ continue;
+
+ if (bIgnoreEmptyRows && getCache().IsRowEmpty(nRow))
+ continue;
+
+ maShowByFilter.insert_back(nRow, nRow+1, true);
+ }
+
+ // Process the trailing empty rows.
+ if (!bIgnoreEmptyRows)
+ maShowByFilter.insert_back(nDataSize, nRowCount, true);
+
+ maShowByFilter.build_tree();
+
+ // Initialize field entries container.
+ maFieldEntries.clear();
+ maFieldEntries.reserve(nColCount);
+
+ // Build unique field entries.
+ for (SCCOL nCol = 0; nCol < nColCount; ++nCol)
+ {
+ maFieldEntries.emplace_back( );
+ SCROW nMemCount = getCache().GetDimMemberCount( nCol );
+ if (!nMemCount)
+ continue;
+
+ std::vector<SCROW> aAdded(nMemCount, -1);
+ bool bShow = false;
+ SCROW nEndSegment = -1;
+ for (SCROW nRow = 0; nRow < nRowCount; ++nRow)
+ {
+ if (nRow > nEndSegment)
+ {
+ if (!maShowByFilter.search_tree(nRow, bShow, nullptr, &nEndSegment).second)
+ {
+ OSL_FAIL("Tree search failed!");
+ continue;
+ }
+ --nEndSegment; // End position is not inclusive. Move back one.
+ }
+
+ if (!bShow)
+ {
+ nRow = nEndSegment;
+ continue;
+ }
+
+ SCROW nIndex = getCache().GetItemDataId(nCol, nRow, bRepeatIfEmpty);
+ aAdded[nIndex] = nIndex;
+
+ // tdf#96588 - large numbers of trailing identical empty
+ // rows generate the same nIndex & nOrder.
+ if (nRow == nDataSize)
+ break;
+ }
+ for (SCROW nRow = 0; nRow < nMemCount; ++nRow)
+ {
+ if (aAdded[nRow] != -1)
+ maFieldEntries.back().push_back(aAdded[nRow]);
+ }
+ }
+}
+
+void ScDPFilteredCache::fillTable()
+{
+ SCROW nRowCount = getRowSize();
+ SCCOL nColCount = getColSize();
+ if (nRowCount <= 0 || nColCount <= 0)
+ return;
+
+ maShowByPage.clear();
+ maShowByPage.build_tree();
+
+ maShowByFilter.clear();
+ maShowByFilter.insert_front(0, nRowCount, true);
+ maShowByFilter.build_tree();
+
+ // Initialize field entries container.
+ maFieldEntries.clear();
+ maFieldEntries.reserve(nColCount);
+
+ // Data rows
+ for (SCCOL nCol = 0; nCol < nColCount; ++nCol)
+ {
+ maFieldEntries.emplace_back( );
+ SCROW nMemCount = getCache().GetDimMemberCount( nCol );
+ if (!nMemCount)
+ continue;
+
+ std::vector<SCROW> aAdded(nMemCount, -1);
+
+ for (SCROW nRow = 0; nRow < nRowCount; ++nRow)
+ {
+ SCROW nIndex = getCache().GetItemDataId(nCol, nRow, false);
+ aAdded[nIndex] = nIndex;
+ }
+ for (SCROW nRow = 0; nRow < nMemCount; ++nRow)
+ {
+ if (aAdded[nRow] != -1)
+ maFieldEntries.back().push_back(aAdded[nRow]);
+ }
+ }
+}
+
+bool ScDPFilteredCache::isRowActive(sal_Int32 nRow, sal_Int32* pLastRow) const
+{
+ bool bFilter = false, bPage = true;
+ SCROW nLastRowFilter = MAXROW, nLastRowPage = MAXROW;
+ maShowByFilter.search_tree(nRow, bFilter, nullptr, &nLastRowFilter);
+ maShowByPage.search_tree(nRow, bPage, nullptr, &nLastRowPage);
+ if (pLastRow)
+ {
+ // Return the last row of current segment.
+ *pLastRow = std::min(nLastRowFilter, nLastRowPage);
+ *pLastRow -= 1; // End position is not inclusive. Move back one.
+ }
+
+ return bFilter && bPage;
+}
+
+void ScDPFilteredCache::filterByPageDimension(const vector<Criterion>& rCriteria, const std::unordered_set<sal_Int32>& rRepeatIfEmptyDims)
+{
+ SCROW nRowSize = getRowSize();
+ SCROW nDataSize = mrCache.GetDataSize();
+
+ maShowByPage.clear();
+
+ for (SCROW nRow = 0; nRow < nDataSize; ++nRow)
+ {
+ bool bShow = isRowQualified(nRow, rCriteria, rRepeatIfEmptyDims);
+ maShowByPage.insert_back(nRow, nRow+1, bShow);
+ }
+
+ // tdf#96588 - rapidly extend for blank rows with identical data
+ if (nDataSize < nRowSize)
+ {
+ bool bBlankShow = isRowQualified(nDataSize, rCriteria, rRepeatIfEmptyDims);
+ maShowByPage.insert_back(nDataSize, nRowSize, bBlankShow);
+ }
+
+ maShowByPage.build_tree();
+}
+
+const ScDPItemData* ScDPFilteredCache::getCell(SCCOL nCol, SCROW nRow, bool bRepeatIfEmpty) const
+{
+ SCROW nId= mrCache.GetItemDataId(nCol, nRow, bRepeatIfEmpty);
+ return mrCache.GetItemDataById( nCol, nId );
+}
+
+void ScDPFilteredCache::getValue( ScDPValue& rVal, SCCOL nCol, SCROW nRow) const
+{
+ const ScDPItemData* pData = getCell( nCol, nRow, false/*bRepeatIfEmpty*/ );
+
+ if (pData)
+ {
+ rVal.mfValue = pData->IsValue() ? pData->GetValue() : 0.0;
+ rVal.meType = pData->GetCellType();
+ }
+ else
+ rVal.Set(0.0, ScDPValue::Empty);
+}
+
+OUString ScDPFilteredCache::getFieldName(SCCOL nIndex) const
+{
+ return mrCache.GetDimensionName(nIndex);
+}
+
+const ::std::vector<SCROW>& ScDPFilteredCache::getFieldEntries( sal_Int32 nColumn ) const
+{
+ if (nColumn < 0 || o3tl::make_unsigned(nColumn) >= maFieldEntries.size())
+ {
+ // index out of bound. Hopefully this code will never be reached.
+ static const ::std::vector<SCROW> emptyEntries{};
+ return emptyEntries;
+ }
+ return maFieldEntries[nColumn];
+}
+
+void ScDPFilteredCache::filterTable(const vector<Criterion>& rCriteria, Sequence< Sequence<Any> >& rTabData,
+ const std::unordered_set<sal_Int32>& rRepeatIfEmptyDims)
+{
+ sal_Int32 nRowSize = getRowSize();
+ SCCOL nColSize = getColSize();
+
+ if (!nRowSize)
+ // no data to filter.
+ return;
+
+ // Row first, then column.
+ vector< Sequence<Any> > tableData;
+ tableData.reserve(nRowSize+1);
+
+ // Header first.
+ Sequence<Any> headerRow(nColSize);
+ auto pRow = headerRow.getArray();
+ for (SCCOL nCol = 0; nCol < nColSize; ++nCol)
+ {
+ OUString str = getFieldName( nCol);
+ Any any;
+ any <<= str;
+ pRow[nCol] = any;
+ }
+ tableData.push_back(headerRow);
+
+ for (sal_Int32 nRow = 0; nRow < nRowSize; ++nRow)
+ {
+ sal_Int32 nLastRow;
+ if (!isRowActive(nRow, &nLastRow))
+ {
+ // This row is filtered out.
+ nRow = nLastRow;
+ continue;
+ }
+
+ if (!isRowQualified(nRow, rCriteria, rRepeatIfEmptyDims))
+ continue;
+
+ // Insert this row into table.
+
+ Sequence<Any> row(nColSize);
+ pRow = row.getArray();
+ for (SCCOL nCol = 0; nCol < nColSize; ++nCol)
+ {
+ Any any;
+ bool bRepeatIfEmpty = rRepeatIfEmptyDims.count(nCol) > 0;
+ const ScDPItemData* pData= getCell(nCol, nRow, bRepeatIfEmpty);
+ if ( pData->IsValue() )
+ any <<= pData->GetValue();
+ else
+ {
+ OUString string (pData->GetString() );
+ any <<= string;
+ }
+ pRow[nCol] = any;
+ }
+ tableData.push_back(row);
+ }
+
+ // convert vector to Sequence
+ sal_Int32 nTabSize = static_cast<sal_Int32>(tableData.size());
+ rTabData.realloc(nTabSize);
+ auto pTabData = rTabData.getArray();
+ for (sal_Int32 i = 0; i < nTabSize; ++i)
+ pTabData[i] = tableData[i];
+}
+
+void ScDPFilteredCache::clear()
+{
+ maFieldEntries.clear();
+ maShowByFilter.clear();
+ maShowByPage.clear();
+}
+
+bool ScDPFilteredCache::empty() const
+{
+ return maFieldEntries.empty();
+}
+
+bool ScDPFilteredCache::isRowQualified(sal_Int32 nRow, const vector<Criterion>& rCriteria,
+ const std::unordered_set<sal_Int32>& rRepeatIfEmptyDims) const
+{
+ sal_Int32 nColSize = getColSize();
+ for (const auto& rCriterion : rCriteria)
+ {
+ if (rCriterion.mnFieldIndex >= nColSize)
+ // specified field is outside the source data columns. Don't
+ // use this criterion.
+ continue;
+
+ // Check if the 'repeat if empty' flag is set for this field.
+ bool bRepeatIfEmpty = rRepeatIfEmptyDims.count(rCriterion.mnFieldIndex) > 0;
+ const ScDPItemData* pCellData = getCell(static_cast<SCCOL>(rCriterion.mnFieldIndex), nRow, bRepeatIfEmpty);
+ if (!rCriterion.mpFilter->match(*pCellData))
+ return false;
+ }
+ return true;
+}
+
+#if DUMP_PIVOT_TABLE
+
+void ScDPFilteredCache::dumpRowFlag( const RowFlagType& rFlag )
+{
+ RowFlagType::const_iterator it = rFlag.begin(), itEnd = rFlag.end();
+ bool bShow = it->second;
+ SCROW nRow1 = it->first;
+ for (++it; it != itEnd; ++it)
+ {
+ SCROW nRow2 = it->first;
+ cout << " * range " << nRow1 << "-" << nRow2 << ": " << (bShow ? "on" : "off") << endl;
+ bShow = it->second;
+ nRow1 = nRow2;
+ }
+}
+
+void ScDPFilteredCache::dump() const
+{
+ cout << "--- pivot filtered cache dump" << endl;
+
+ cout << endl;
+ cout << "* show by filter" << endl;
+ dumpRowFlag(maShowByFilter);
+
+ cout << endl;
+ cout << "* show by page dimensions" << endl;
+ dumpRowFlag(maShowByPage);
+
+ cout << endl;
+ cout << "* field entries" << endl;
+ size_t nFieldCount = maFieldEntries.size();
+ for (size_t i = 0; i < nFieldCount; ++i)
+ {
+ const vector<SCROW>& rField = maFieldEntries[i];
+ cout << " * field " << i << endl;
+ for (size_t j = 0, n = rField.size(); j < n; ++j)
+ cout << " ID: " << rField[j] << endl;
+ }
+ cout << "---" << endl;
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dpglobal.cxx b/sc/source/core/data/dpglobal.cxx
new file mode 100644
index 000000000..ac72054e7
--- /dev/null
+++ b/sc/source/core/data/dpglobal.cxx
@@ -0,0 +1,20 @@
+/* -*- 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 <dpglobal.hxx>
+
+ScDPValue::ScDPValue() : mfValue(0.0), meType(String) {}
+
+void ScDPValue::Set( double fV, Type eT )
+{
+ mfValue = fV;
+ meType = eT;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dpgroup.cxx b/sc/source/core/data/dpgroup.cxx
new file mode 100644
index 000000000..52bc6476f
--- /dev/null
+++ b/sc/source/core/data/dpgroup.cxx
@@ -0,0 +1,1030 @@
+/* -*- 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 <dpgroup.hxx>
+
+#include <dpcache.hxx>
+#include <document.hxx>
+#include <dpfilteredcache.hxx>
+#include <dputil.hxx>
+
+#include <osl/diagnose.h>
+#include <rtl/math.hxx>
+#include <svl/numformat.hxx>
+
+#include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp>
+
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+#include <algorithm>
+
+using namespace ::com::sun::star;
+using ::com::sun::star::uno::Any;
+using ::com::sun::star::uno::Sequence;
+
+using ::std::vector;
+using ::std::shared_ptr;
+
+const sal_uInt16 SC_DP_LEAPYEAR = 1648; // arbitrary leap year for date calculations
+
+namespace {
+
+class ScDPGroupNumFilter : public ScDPFilteredCache::FilterBase
+{
+public:
+ ScDPGroupNumFilter(std::vector<ScDPItemData>&& rValues, const ScDPNumGroupInfo& rInfo);
+
+ virtual bool match(const ScDPItemData &rCellData) const override;
+ virtual std::vector<ScDPItemData> getMatchValues() const override;
+private:
+ std::vector<ScDPItemData> maValues;
+ ScDPNumGroupInfo maNumInfo;
+};
+
+}
+
+ScDPGroupNumFilter::ScDPGroupNumFilter( std::vector<ScDPItemData>&& rValues, const ScDPNumGroupInfo& rInfo) :
+ maValues(std::move(rValues)), maNumInfo(rInfo) {}
+
+bool ScDPGroupNumFilter::match(const ScDPItemData& rCellData) const
+{
+ if (rCellData.GetType() != ScDPItemData::Value)
+ return false;
+
+ for (const auto& rValue : maValues)
+ {
+ double fVal = rValue.GetValue();
+ if (std::isinf(fVal))
+ {
+ if (std::signbit(fVal))
+ {
+ // Less than the min value.
+ if (rCellData.GetValue() < maNumInfo.mfStart)
+ return true;
+ }
+
+ // Greater than the max value.
+ if (maNumInfo.mfEnd < rCellData.GetValue())
+ return true;
+
+ continue;
+ }
+
+ double low = fVal;
+ double high = low + maNumInfo.mfStep;
+ if (maNumInfo.mbIntegerOnly)
+ high += 1.0;
+
+ if (low <= rCellData.GetValue() && rCellData.GetValue() < high)
+ return true;
+ }
+
+ return false;
+}
+
+std::vector<ScDPItemData> ScDPGroupNumFilter::getMatchValues() const
+{
+ return std::vector<ScDPItemData>();
+}
+
+namespace {
+
+class ScDPGroupDateFilter : public ScDPFilteredCache::FilterBase
+{
+public:
+ ScDPGroupDateFilter(
+ std::vector<ScDPItemData>&& rValues, const Date& rNullDate, const ScDPNumGroupInfo& rNumInfo);
+
+ virtual bool match(const ScDPItemData & rCellData) const override;
+ virtual std::vector<ScDPItemData> getMatchValues() const override;
+
+private:
+ std::vector<ScDPItemData> maValues;
+ Date maNullDate;
+ ScDPNumGroupInfo maNumInfo;
+};
+
+}
+
+ScDPGroupDateFilter::ScDPGroupDateFilter(
+ std::vector<ScDPItemData>&& rValues, const Date& rNullDate, const ScDPNumGroupInfo& rNumInfo) :
+ maValues(std::move(rValues)),
+ maNullDate(rNullDate),
+ maNumInfo(rNumInfo)
+{
+}
+
+bool ScDPGroupDateFilter::match( const ScDPItemData & rCellData ) const
+{
+ using namespace ::com::sun::star::sheet;
+ using ::rtl::math::approxFloor;
+ using ::rtl::math::approxEqual;
+
+ if ( !rCellData.IsValue() )
+ return false;
+
+ for (const ScDPItemData& rValue : maValues)
+ {
+ if (rValue.GetType() != ScDPItemData::GroupValue)
+ continue;
+
+ sal_Int32 nGroupType = rValue.GetGroupValue().mnGroupType;
+ sal_Int32 nValue = rValue.GetGroupValue().mnValue;
+
+ // Start and end dates are inclusive. (An end date without a time value
+ // is included, while an end date with a time value is not.)
+
+ if (rCellData.GetValue() < maNumInfo.mfStart && !approxEqual(rCellData.GetValue(), maNumInfo.mfStart))
+ {
+ if (nValue == ScDPItemData::DateFirst)
+ return true;
+ continue;
+ }
+
+ if (rCellData.GetValue() > maNumInfo.mfEnd && !approxEqual(rCellData.GetValue(), maNumInfo.mfEnd))
+ {
+ if (nValue == ScDPItemData::DateLast)
+ return true;
+ continue;
+ }
+
+ if (nGroupType == DataPilotFieldGroupBy::HOURS || nGroupType == DataPilotFieldGroupBy::MINUTES ||
+ nGroupType == DataPilotFieldGroupBy::SECONDS)
+ {
+ // handle time
+ // (do as in the cell functions, ScInterpreter::ScGetHour() etc.)
+
+ sal_uInt16 nHour, nMinute, nSecond;
+ double fFractionOfSecond;
+ tools::Time::GetClock( rCellData.GetValue(), nHour, nMinute, nSecond, fFractionOfSecond, 0);
+
+ switch (nGroupType)
+ {
+ case DataPilotFieldGroupBy::HOURS:
+ {
+ if (nHour == nValue)
+ return true;
+ }
+ break;
+ case DataPilotFieldGroupBy::MINUTES:
+ {
+ if (nMinute == nValue)
+ return true;
+ }
+ break;
+ case DataPilotFieldGroupBy::SECONDS:
+ {
+ if (nSecond == nValue)
+ return true;
+ }
+ break;
+ default:
+ OSL_FAIL("invalid time part");
+ }
+
+ continue;
+ }
+
+ Date date = maNullDate + static_cast<sal_Int32>(approxFloor(rCellData.GetValue()));
+ switch (nGroupType)
+ {
+ case DataPilotFieldGroupBy::YEARS:
+ {
+ sal_Int32 year = static_cast<sal_Int32>(date.GetYear());
+ if (year == nValue)
+ return true;
+ }
+ break;
+ case DataPilotFieldGroupBy::QUARTERS:
+ {
+ sal_Int32 qtr = 1 + (static_cast<sal_Int32>(date.GetMonth()) - 1) / 3;
+ if (qtr == nValue)
+ return true;
+ }
+ break;
+ case DataPilotFieldGroupBy::MONTHS:
+ {
+ sal_Int32 month = static_cast<sal_Int32>(date.GetMonth());
+ if (month == nValue)
+ return true;
+ }
+ break;
+ case DataPilotFieldGroupBy::DAYS:
+ {
+ Date yearStart(1, 1, date.GetYear());
+ sal_Int32 days = (date - yearStart) + 1; // Jan 01 has value 1
+ if (days >= 60 && !date.IsLeapYear())
+ {
+ // This is not a leap year. Adjust the value accordingly.
+ ++days;
+ }
+ if (days == nValue)
+ return true;
+ }
+ break;
+ default:
+ OSL_FAIL("invalid date part");
+ }
+ }
+
+ return false;
+}
+
+std::vector<ScDPItemData> ScDPGroupDateFilter::getMatchValues() const
+{
+ return std::vector<ScDPItemData>();
+}
+
+namespace {
+
+bool isDateInGroup(const ScDPItemData& rGroupItem, const ScDPItemData& rChildItem)
+{
+ if (rGroupItem.GetType() != ScDPItemData::GroupValue || rChildItem.GetType() != ScDPItemData::GroupValue)
+ return false;
+
+ sal_Int32 nGroupPart = rGroupItem.GetGroupValue().mnGroupType;
+ sal_Int32 nGroupValue = rGroupItem.GetGroupValue().mnValue;
+ sal_Int32 nChildPart = rChildItem.GetGroupValue().mnGroupType;
+ sal_Int32 nChildValue = rChildItem.GetGroupValue().mnValue;
+
+ if (nGroupValue == ScDPItemData::DateFirst || nGroupValue == ScDPItemData::DateLast ||
+ nChildValue == ScDPItemData::DateFirst || nChildValue == ScDPItemData::DateLast)
+ {
+ // first/last entry matches only itself
+ return nGroupValue == nChildValue;
+ }
+
+ switch (nChildPart) // inner part
+ {
+ case css::sheet::DataPilotFieldGroupBy::MONTHS:
+ // a month is only contained in its quarter
+ if (nGroupPart == css::sheet::DataPilotFieldGroupBy::QUARTERS)
+ // months and quarters are both 1-based
+ return (nGroupValue - 1 == (nChildValue - 1) / 3);
+ break;
+ case css::sheet::DataPilotFieldGroupBy::DAYS:
+ // a day is only contained in its quarter or month
+ if (nGroupPart == css::sheet::DataPilotFieldGroupBy::MONTHS ||
+ nGroupPart == css::sheet::DataPilotFieldGroupBy::QUARTERS)
+ {
+ Date aDate(1, 1, SC_DP_LEAPYEAR);
+ aDate.AddDays(nChildValue - 1); // days are 1-based
+ sal_Int32 nCompare = aDate.GetMonth();
+ if (nGroupPart == css::sheet::DataPilotFieldGroupBy::QUARTERS)
+ nCompare = ( ( nCompare - 1 ) / 3 ) + 1; // get quarter from date
+
+ return nGroupValue == nCompare;
+ }
+ break;
+ default:
+ ;
+ }
+
+ return true;
+}
+
+}
+
+ScDPGroupItem::ScDPGroupItem( const ScDPItemData& rName ) :
+ aGroupName( rName )
+{
+}
+
+void ScDPGroupItem::AddElement( const ScDPItemData& rName )
+{
+ aElements.push_back( rName );
+}
+
+bool ScDPGroupItem::HasElement( const ScDPItemData& rData ) const
+{
+ return std::any_of(aElements.begin(), aElements.end(),
+ [&rData](const ScDPItemData& rElement) { return rElement.IsCaseInsEqual(rData); });
+}
+
+bool ScDPGroupItem::HasCommonElement( const ScDPGroupItem& rOther ) const
+{
+ return std::any_of(aElements.begin(), aElements.end(),
+ [&rOther](const ScDPItemData& rElement) { return rOther.HasElement(rElement); });
+}
+
+void ScDPGroupItem::FillGroupFilter( ScDPFilteredCache::GroupFilter& rFilter ) const
+{
+ for (const auto& rElement : aElements)
+ rFilter.addMatchItem(rElement);
+}
+
+ScDPGroupDimension::ScDPGroupDimension( tools::Long nSource, const OUString& rNewName ) :
+ nSourceDim( nSource ),
+ nGroupDim( -1 ),
+ aGroupName( rNewName ),
+ mbDateDimension(false)
+{
+}
+
+ScDPGroupDimension::~ScDPGroupDimension()
+{
+ maMemberEntries.clear();
+}
+
+ScDPGroupDimension::ScDPGroupDimension( const ScDPGroupDimension& rOther ) :
+ nSourceDim( rOther.nSourceDim ),
+ nGroupDim( rOther.nGroupDim ),
+ aGroupName( rOther.aGroupName ),
+ aItems( rOther.aItems ),
+ mbDateDimension(rOther.mbDateDimension)
+{
+}
+
+ScDPGroupDimension& ScDPGroupDimension::operator=( const ScDPGroupDimension& rOther )
+{
+ nSourceDim = rOther.nSourceDim;
+ nGroupDim = rOther.nGroupDim;
+ aGroupName = rOther.aGroupName;
+ aItems = rOther.aItems;
+ mbDateDimension = rOther.mbDateDimension;
+ return *this;
+}
+
+void ScDPGroupDimension::AddItem( const ScDPGroupItem& rItem )
+{
+ aItems.push_back( rItem );
+}
+
+void ScDPGroupDimension::SetGroupDim( tools::Long nDim )
+{
+ nGroupDim = nDim;
+}
+
+const std::vector<SCROW>& ScDPGroupDimension::GetColumnEntries(
+ const ScDPFilteredCache& rCacheTable) const
+{
+ if (!maMemberEntries.empty())
+ return maMemberEntries;
+
+ rCacheTable.getCache().GetGroupDimMemberIds(nGroupDim, maMemberEntries);
+ return maMemberEntries;
+}
+
+const ScDPGroupItem* ScDPGroupDimension::GetGroupForData( const ScDPItemData& rData ) const
+{
+ auto aIter = std::find_if(aItems.begin(), aItems.end(),
+ [&rData](const ScDPGroupItem& rItem) { return rItem.HasElement(rData); });
+ if (aIter != aItems.end())
+ return &*aIter;
+
+ return nullptr;
+}
+
+const ScDPGroupItem* ScDPGroupDimension::GetGroupForName( const ScDPItemData& rName ) const
+{
+ auto aIter = std::find_if(aItems.begin(), aItems.end(),
+ [&rName](const ScDPGroupItem& rItem) { return rItem.GetName().IsCaseInsEqual(rName); });
+ if (aIter != aItems.end())
+ return &*aIter;
+
+ return nullptr;
+}
+
+const ScDPGroupItem* ScDPGroupDimension::GetGroupByIndex( size_t nIndex ) const
+{
+ if (nIndex >= aItems.size())
+ return nullptr;
+
+ return &aItems[nIndex];
+}
+
+void ScDPGroupDimension::DisposeData()
+{
+ maMemberEntries.clear();
+}
+
+void ScDPGroupDimension::SetDateDimension()
+{
+ mbDateDimension = true;
+}
+
+ScDPNumGroupDimension::ScDPNumGroupDimension() : mbDateDimension(false) {}
+
+ScDPNumGroupDimension::ScDPNumGroupDimension( const ScDPNumGroupInfo& rInfo ) :
+ aGroupInfo(rInfo), mbDateDimension(false) {}
+
+ScDPNumGroupDimension::ScDPNumGroupDimension( const ScDPNumGroupDimension& rOther ) :
+ aGroupInfo(rOther.aGroupInfo), mbDateDimension(rOther.mbDateDimension) {}
+
+ScDPNumGroupDimension& ScDPNumGroupDimension::operator=( const ScDPNumGroupDimension& rOther )
+{
+ aGroupInfo = rOther.aGroupInfo;
+ mbDateDimension = rOther.mbDateDimension;
+ return *this;
+}
+
+void ScDPNumGroupDimension::DisposeData()
+{
+ aGroupInfo = ScDPNumGroupInfo();
+ maMemberEntries.clear();
+}
+
+ScDPNumGroupDimension::~ScDPNumGroupDimension()
+{
+}
+
+void ScDPNumGroupDimension::SetDateDimension()
+{
+ aGroupInfo.mbEnable = true; //TODO: or query both?
+ mbDateDimension = true;
+}
+
+const std::vector<SCROW>& ScDPNumGroupDimension::GetNumEntries(
+ SCCOL nSourceDim, const ScDPCache* pCache) const
+{
+ if (!maMemberEntries.empty())
+ return maMemberEntries;
+
+ pCache->GetGroupDimMemberIds(nSourceDim, maMemberEntries);
+ return maMemberEntries;
+}
+
+ScDPGroupTableData::ScDPGroupTableData( const shared_ptr<ScDPTableData>& pSource, ScDocument* pDocument ) :
+ ScDPTableData(pDocument),
+ pSourceData( pSource ),
+ pDoc( pDocument )
+{
+ OSL_ENSURE( pSource, "ScDPGroupTableData: pSource can't be NULL" );
+
+ CreateCacheTable();
+ nSourceCount = pSource->GetColumnCount(); // real columns, excluding data layout
+ pNumGroups.reset( new ScDPNumGroupDimension[nSourceCount] );
+}
+
+ScDPGroupTableData::~ScDPGroupTableData()
+{
+}
+
+void ScDPGroupTableData::AddGroupDimension( const ScDPGroupDimension& rGroup )
+{
+ ScDPGroupDimension aNewGroup( rGroup );
+ aNewGroup.SetGroupDim( GetColumnCount() ); // new dimension will be at the end
+ aGroups.push_back( aNewGroup );
+}
+
+void ScDPGroupTableData::SetNumGroupDimension( sal_Int32 nIndex, const ScDPNumGroupDimension& rGroup )
+{
+ if ( nIndex < nSourceCount )
+ {
+ pNumGroups[nIndex] = rGroup;
+
+ // automatic minimum / maximum is handled in GetNumEntries
+ }
+}
+
+sal_Int32 ScDPGroupTableData::GetDimensionIndex( std::u16string_view rName )
+{
+ for (tools::Long i = 0; i < nSourceCount; ++i) // nSourceCount excludes data layout
+ if (pSourceData->getDimensionName(i) == rName) //TODO: ignore case?
+ return i;
+ return -1; // none
+}
+
+sal_Int32 ScDPGroupTableData::GetColumnCount()
+{
+ return nSourceCount + aGroups.size();
+}
+
+bool ScDPGroupTableData::IsNumGroupDimension( tools::Long nDimension ) const
+{
+ return ( nDimension < nSourceCount && pNumGroups[nDimension].GetInfo().mbEnable );
+}
+
+void ScDPGroupTableData::GetNumGroupInfo(tools::Long nDimension, ScDPNumGroupInfo& rInfo)
+{
+ if ( nDimension < nSourceCount )
+ rInfo = pNumGroups[nDimension].GetInfo();
+}
+sal_Int32 ScDPGroupTableData::GetMembersCount( sal_Int32 nDim )
+{
+ const std::vector< SCROW >& members = GetColumnEntries( nDim );
+ return members.size();
+}
+const std::vector< SCROW >& ScDPGroupTableData::GetColumnEntries( sal_Int32 nColumn )
+{
+ if ( nColumn >= nSourceCount )
+ {
+ if ( getIsDataLayoutDimension( nColumn) ) // data layout dimension?
+ nColumn = nSourceCount; // index of data layout in source data
+ else
+ {
+ const ScDPGroupDimension& rGroupDim = aGroups[nColumn - nSourceCount];
+ return rGroupDim.GetColumnEntries( GetCacheTable() );
+ }
+ }
+
+ if ( IsNumGroupDimension( nColumn ) )
+ {
+ // dimension number is unchanged for numerical groups
+ return pNumGroups[nColumn].GetNumEntries(
+ static_cast<SCCOL>(nColumn), &GetCacheTable().getCache());
+ }
+
+ return pSourceData->GetColumnEntries( nColumn );
+}
+
+const ScDPItemData* ScDPGroupTableData::GetMemberById( sal_Int32 nDim, sal_Int32 nId )
+{
+ return pSourceData->GetMemberById( nDim, nId );
+}
+
+OUString ScDPGroupTableData::getDimensionName(sal_Int32 nColumn)
+{
+ if ( nColumn >= nSourceCount )
+ {
+ if ( nColumn == sal::static_int_cast<tools::Long>( nSourceCount + aGroups.size() ) ) // data layout dimension?
+ nColumn = nSourceCount; // index of data layout in source data
+ else
+ return aGroups[nColumn - nSourceCount].GetName();
+ }
+
+ return pSourceData->getDimensionName( nColumn );
+}
+
+bool ScDPGroupTableData::getIsDataLayoutDimension(sal_Int32 nColumn)
+{
+ // position of data layout dimension is moved from source data
+ return ( nColumn == sal::static_int_cast<tools::Long>( nSourceCount + aGroups.size() ) ); // data layout dimension?
+}
+
+bool ScDPGroupTableData::IsDateDimension(sal_Int32 nDim)
+{
+ if ( nDim >= nSourceCount )
+ {
+ if ( nDim == sal::static_int_cast<tools::Long>( nSourceCount + aGroups.size() ) ) // data layout dimension?
+ nDim = nSourceCount; // index of data layout in source data
+ else
+ nDim = aGroups[nDim - nSourceCount].GetSourceDim(); // look at original dimension
+ }
+
+ return pSourceData->IsDateDimension( nDim );
+}
+
+sal_uInt32 ScDPGroupTableData::GetNumberFormat(sal_Int32 nDim)
+{
+ if ( nDim >= nSourceCount )
+ {
+ if ( nDim == sal::static_int_cast<tools::Long>( nSourceCount + aGroups.size() ) ) // data layout dimension?
+ nDim = nSourceCount; // index of data layout in source data
+ else
+ nDim = aGroups[nDim - nSourceCount].GetSourceDim(); // look at original dimension
+ }
+
+ return pSourceData->GetNumberFormat( nDim );
+}
+
+void ScDPGroupTableData::DisposeData()
+{
+ for ( auto& rGroup : aGroups )
+ rGroup.DisposeData();
+
+ for ( tools::Long i=0; i<nSourceCount; i++ )
+ pNumGroups[i].DisposeData();
+
+ pSourceData->DisposeData();
+}
+
+void ScDPGroupTableData::SetEmptyFlags( bool bIgnoreEmptyRows, bool bRepeatIfEmpty )
+{
+ pSourceData->SetEmptyFlags( bIgnoreEmptyRows, bRepeatIfEmpty );
+}
+
+bool ScDPGroupTableData::IsRepeatIfEmpty()
+{
+ return pSourceData->IsRepeatIfEmpty();
+}
+
+void ScDPGroupTableData::CreateCacheTable()
+{
+ pSourceData->CreateCacheTable();
+}
+
+namespace {
+
+class FindCaseInsensitive
+{
+ ScDPItemData maValue;
+public:
+ explicit FindCaseInsensitive(const ScDPItemData& rVal) : maValue(rVal) {}
+
+ bool operator() (const ScDPItemData& rItem) const
+ {
+ return maValue.IsCaseInsEqual(rItem);
+ }
+};
+
+}
+
+void ScDPGroupTableData::ModifyFilterCriteria(vector<ScDPFilteredCache::Criterion>& rCriteria)
+{
+ // Build dimension ID to object map for group dimensions.
+ typedef std::unordered_map<tools::Long, const ScDPGroupDimension*> GroupFieldMapType;
+ GroupFieldMapType aGroupFieldIds;
+
+ for (const auto& rGroup : aGroups)
+ {
+ aGroupFieldIds.emplace(rGroup.GetGroupDim(), &rGroup);
+ }
+
+ vector<ScDPFilteredCache::Criterion> aNewCriteria;
+ aNewCriteria.reserve(rCriteria.size() + aGroups.size());
+
+ // Go through all the filtered field names and process them appropriately.
+
+ const ScDPCache& rCache = GetCacheTable().getCache();
+ GroupFieldMapType::const_iterator itrGrpEnd = aGroupFieldIds.end();
+ for (const auto& rCriterion : rCriteria)
+ {
+ std::vector<ScDPItemData> aMatchValues = rCriterion.mpFilter->getMatchValues();
+
+ GroupFieldMapType::const_iterator itrGrp = aGroupFieldIds.find(rCriterion.mnFieldIndex);
+ if (itrGrp == itrGrpEnd)
+ {
+ if (IsNumGroupDimension(rCriterion.mnFieldIndex))
+ {
+ // internal number group field
+ const ScDPNumGroupInfo* pNumInfo = rCache.GetNumGroupInfo(rCriterion.mnFieldIndex);
+ if (!pNumInfo)
+ // Number group dimension without num info? Something is wrong...
+ continue;
+
+ ScDPFilteredCache::Criterion aCri;
+ aCri.mnFieldIndex = rCriterion.mnFieldIndex;
+ const ScDPNumGroupDimension& rNumGrpDim = pNumGroups[rCriterion.mnFieldIndex];
+
+ if (rNumGrpDim.IsDateDimension())
+ {
+ // grouped by dates.
+ aCri.mpFilter =
+ std::make_shared<ScDPGroupDateFilter>(
+ std::move(aMatchValues), pDoc->GetFormatTable()->GetNullDate(), *pNumInfo);
+ }
+ else
+ {
+ // This dimension is grouped by numeric ranges.
+ aCri.mpFilter =
+ std::make_shared<ScDPGroupNumFilter>(std::move(aMatchValues), *pNumInfo);
+ }
+
+ aNewCriteria.push_back(aCri);
+ }
+ else
+ {
+ // This is a regular source field.
+ aNewCriteria.push_back(rCriterion);
+ }
+ }
+ else
+ {
+ // This is an ordinary group field or external number group field.
+
+ const ScDPGroupDimension* pGrpDim = itrGrp->second;
+ tools::Long nSrcDim = pGrpDim->GetSourceDim();
+ tools::Long nGrpDim = pGrpDim->GetGroupDim();
+ const ScDPNumGroupInfo* pNumInfo = rCache.GetNumGroupInfo(nGrpDim);
+
+ if (pGrpDim->IsDateDimension() && pNumInfo)
+ {
+ // external number group
+ ScDPFilteredCache::Criterion aCri;
+ aCri.mnFieldIndex = nSrcDim; // use the source dimension, not the group dimension.
+ aCri.mpFilter =
+ std::make_shared<ScDPGroupDateFilter>(
+ std::move(aMatchValues), pDoc->GetFormatTable()->GetNullDate(), *pNumInfo);
+
+ aNewCriteria.push_back(aCri);
+ }
+ else
+ {
+ // normal group
+
+ ScDPFilteredCache::Criterion aCri;
+ aCri.mnFieldIndex = nSrcDim;
+ aCri.mpFilter = std::make_shared<ScDPFilteredCache::GroupFilter>();
+ ScDPFilteredCache::GroupFilter* pGrpFilter =
+ static_cast<ScDPFilteredCache::GroupFilter*>(aCri.mpFilter.get());
+
+ size_t nGroupItemCount = pGrpDim->GetItemCount();
+ for (size_t i = 0; i < nGroupItemCount; ++i)
+ {
+ const ScDPGroupItem* pGrpItem = pGrpDim->GetGroupByIndex(i);
+ if (!pGrpItem)
+ continue;
+
+ // Make sure this group name equals one of the match values.
+ if (std::none_of(aMatchValues.begin(), aMatchValues.end(), FindCaseInsensitive(pGrpItem->GetName())))
+ continue;
+
+ pGrpItem->FillGroupFilter(*pGrpFilter);
+ }
+
+ aNewCriteria.push_back(aCri);
+ }
+ }
+ }
+ rCriteria.swap(aNewCriteria);
+}
+
+void ScDPGroupTableData::FilterCacheTable(std::vector<ScDPFilteredCache::Criterion>&& rCriteria, std::unordered_set<sal_Int32>&& rCatDims)
+{
+ ModifyFilterCriteria(rCriteria);
+ pSourceData->FilterCacheTable(std::move(rCriteria), std::move(rCatDims));
+}
+
+void ScDPGroupTableData::GetDrillDownData(std::vector<ScDPFilteredCache::Criterion>&& rCriteria, std::unordered_set<sal_Int32>&&rCatDims, Sequence< Sequence<Any> >& rData)
+{
+ ModifyFilterCriteria(rCriteria);
+ pSourceData->GetDrillDownData(std::move(rCriteria), std::move(rCatDims), rData);
+}
+
+void ScDPGroupTableData::CalcResults(CalcInfo& rInfo, bool bAutoShow)
+{
+ // #i111435# Inside FillRowDataFromCacheTable/GetItemData, virtual methods
+ // getIsDataLayoutDimension and GetSourceDim are used, so it has to be called
+ // with original rInfo, containing dimension indexes of the grouped data.
+
+ const ScDPFilteredCache& rCacheTable = pSourceData->GetCacheTable();
+ sal_Int32 nRowSize = rCacheTable.getRowSize();
+ for (sal_Int32 nRow = 0; nRow < nRowSize; ++nRow)
+ {
+ sal_Int32 nLastRow;
+ if (!rCacheTable.isRowActive(nRow, &nLastRow))
+ {
+ nRow = nLastRow;
+ continue;
+ }
+
+ CalcRowData aData;
+ FillRowDataFromCacheTable(nRow, rCacheTable, rInfo, aData);
+
+ if ( !rInfo.aColLevelDims.empty() )
+ FillGroupValues(aData.aColData, rInfo.aColLevelDims);
+ if ( !rInfo.aRowLevelDims.empty() )
+ FillGroupValues(aData.aRowData, rInfo.aRowLevelDims);
+ if ( !rInfo.aPageDims.empty() )
+ FillGroupValues(aData.aPageData, rInfo.aPageDims);
+
+ ProcessRowData(rInfo, aData, bAutoShow);
+ }
+}
+
+const ScDPFilteredCache& ScDPGroupTableData::GetCacheTable() const
+{
+ return pSourceData->GetCacheTable();
+}
+
+void ScDPGroupTableData::ReloadCacheTable()
+{
+ pSourceData->ReloadCacheTable();
+}
+
+void ScDPGroupTableData::FillGroupValues(vector<SCROW>& rItems, const vector<sal_Int32>& rDims)
+{
+ sal_Int32 nGroupedColumns = aGroups.size();
+
+ const ScDPCache& rCache = GetCacheTable().getCache();
+ size_t i = 0;
+ for (tools::Long nColumn : rDims)
+ {
+ bool bDateDim = false;
+
+ sal_Int32 nSourceDim = nColumn;
+ if ( nColumn >= nSourceCount && nColumn < nSourceCount + nGroupedColumns )
+ {
+ const ScDPGroupDimension& rGroupDim = aGroups[nColumn - nSourceCount];
+ nSourceDim= rGroupDim.GetSourceDim();
+ bDateDim = rGroupDim.IsDateDimension();
+ if (!bDateDim) // date is handled below
+ {
+ const ScDPItemData& rItem = *GetMemberById(nSourceDim, rItems[i]);
+ const ScDPGroupItem* pGroupItem = rGroupDim.GetGroupForData(rItem);
+ if (pGroupItem)
+ {
+ rItems[i] =
+ rCache.GetIdByItemData(nColumn, pGroupItem->GetName());
+ }
+ else
+ rItems[i] = rCache.GetIdByItemData(nColumn, rItem);
+ }
+ }
+ else if ( IsNumGroupDimension( nColumn ) )
+ {
+ bDateDim = pNumGroups[nColumn].IsDateDimension();
+ if (!bDateDim) // date is handled below
+ {
+ const ScDPItemData* pData = rCache.GetItemDataById(nSourceDim, rItems[i]);
+ if (pData->GetType() == ScDPItemData::Value)
+ {
+ ScDPNumGroupInfo aNumInfo;
+ GetNumGroupInfo(nColumn, aNumInfo);
+ double fGroupValue = ScDPUtil::getNumGroupStartValue(pData->GetValue(), aNumInfo);
+ ScDPItemData aItemData;
+ aItemData.SetRangeStart(fGroupValue);
+ rItems[i] = rCache.GetIdByItemData(nSourceDim, aItemData);
+ }
+ // else (textual) keep original value
+ }
+ }
+
+ const ScDPNumGroupInfo* pNumInfo = rCache.GetNumGroupInfo(nColumn);
+
+ if (bDateDim && pNumInfo)
+ {
+ // This is a date group dimension.
+ sal_Int32 nDatePart = rCache.GetGroupType(nColumn);
+ const ScDPItemData* pData = rCache.GetItemDataById(nSourceDim, rItems[i]);
+ if (pData->GetType() == ScDPItemData::Value)
+ {
+ SvNumberFormatter* pFormatter = pDoc->GetFormatTable();
+ sal_Int32 nPartValue = ScDPUtil::getDatePartValue(
+ pData->GetValue(), pNumInfo, nDatePart, pFormatter);
+
+ ScDPItemData aItem(nDatePart, nPartValue);
+ rItems[i] = rCache.GetIdByItemData(nColumn, aItem);
+ }
+ }
+
+ ++i;
+ }
+}
+
+bool ScDPGroupTableData::IsBaseForGroup(sal_Int32 nDim) const
+{
+ return std::any_of(aGroups.begin(), aGroups.end(),
+ [&nDim](const ScDPGroupDimension& rDim) { return rDim.GetSourceDim() == nDim; });
+}
+
+sal_Int32 ScDPGroupTableData::GetGroupBase(sal_Int32 nGroupDim) const
+{
+ auto aIter = std::find_if(aGroups.begin(), aGroups.end(),
+ [&nGroupDim](const ScDPGroupDimension& rDim) { return rDim.GetGroupDim() == nGroupDim; });
+ if (aIter != aGroups.end())
+ return aIter->GetSourceDim();
+
+ return -1; // none
+}
+
+bool ScDPGroupTableData::IsNumOrDateGroup(sal_Int32 nDimension) const
+{
+ // Virtual method from ScDPTableData, used in result data to force text labels.
+
+ if ( nDimension < nSourceCount )
+ {
+ return pNumGroups[nDimension].GetInfo().mbEnable ||
+ pNumGroups[nDimension].IsDateDimension();
+ }
+
+ auto aIter = std::find_if(aGroups.begin(), aGroups.end(),
+ [&nDimension](const ScDPGroupDimension& rDim) { return rDim.GetGroupDim() == nDimension; });
+ if (aIter != aGroups.end())
+ return aIter->IsDateDimension();
+
+ return false;
+}
+
+bool ScDPGroupTableData::IsInGroup( const ScDPItemData& rGroupData, sal_Int32 nGroupIndex,
+ const ScDPItemData& rBaseData, sal_Int32 nBaseIndex ) const
+{
+ auto aIter = std::find_if(aGroups.begin(), aGroups.end(),
+ [&nGroupIndex, &nBaseIndex](const ScDPGroupDimension& rDim) {
+ return rDim.GetGroupDim() == nGroupIndex && rDim.GetSourceDim() == nBaseIndex; });
+ if (aIter != aGroups.end())
+ {
+ const ScDPGroupDimension& rDim = *aIter;
+ if (rDim.IsDateDimension())
+ {
+ return isDateInGroup(rGroupData, rBaseData);
+ }
+ else
+ {
+ // If the item is in a group, only that group is valid.
+ // If the item is not in any group, its own name is valid.
+
+ const ScDPGroupItem* pGroup = rDim.GetGroupForData( rBaseData );
+ return pGroup ? pGroup->GetName().IsCaseInsEqual( rGroupData ) :
+ rGroupData.IsCaseInsEqual( rBaseData );
+ }
+ }
+
+ OSL_FAIL("IsInGroup: no group dimension found");
+ return true;
+}
+
+bool ScDPGroupTableData::HasCommonElement( const ScDPItemData& rFirstData, sal_Int32 nFirstIndex,
+ const ScDPItemData& rSecondData, sal_Int32 nSecondIndex ) const
+{
+ const ScDPGroupDimension* pFirstDim = nullptr;
+ const ScDPGroupDimension* pSecondDim = nullptr;
+ for ( const auto& rDim : aGroups )
+ {
+ const ScDPGroupDimension* pDim = &rDim;
+ if ( pDim->GetGroupDim() == nFirstIndex )
+ pFirstDim = pDim;
+ else if ( pDim->GetGroupDim() == nSecondIndex )
+ pSecondDim = pDim;
+ }
+ if ( pFirstDim && pSecondDim )
+ {
+ bool bFirstDate = pFirstDim->IsDateDimension();
+ bool bSecondDate = pSecondDim->IsDateDimension();
+ if (bFirstDate || bSecondDate)
+ {
+ // If one is a date group dimension, the other one must be, too.
+ if (!bFirstDate || !bSecondDate)
+ {
+ OSL_FAIL( "mix of date and non-date groups" );
+ return true;
+ }
+
+ return isDateInGroup(rFirstData, rSecondData);
+ }
+
+ const ScDPGroupItem* pFirstItem = pFirstDim->GetGroupForName( rFirstData );
+ const ScDPGroupItem* pSecondItem = pSecondDim->GetGroupForName( rSecondData );
+ if ( pFirstItem && pSecondItem )
+ {
+ // two existing groups -> sal_True if they have a common element
+ return pFirstItem->HasCommonElement( *pSecondItem );
+ }
+ else if ( pFirstItem )
+ {
+ // "automatic" group contains only its own name
+ return pFirstItem->HasElement( rSecondData );
+ }
+ else if ( pSecondItem )
+ {
+ // "automatic" group contains only its own name
+ return pSecondItem->HasElement( rFirstData );
+ }
+ else
+ {
+ // no groups -> sal_True if equal
+ return rFirstData.IsCaseInsEqual( rSecondData );
+ }
+ }
+
+ OSL_FAIL("HasCommonElement: no group dimension found");
+ return true;
+}
+
+sal_Int32 ScDPGroupTableData::GetSourceDim( sal_Int32 nDim )
+{
+ if ( getIsDataLayoutDimension( nDim ) )
+ return nSourceCount;
+ if ( nDim >= nSourceCount && nDim < nSourceCount +static_cast<tools::Long>(aGroups.size()) )
+ {
+ const ScDPGroupDimension& rGroupDim = aGroups[nDim - nSourceCount];
+ return rGroupDim.GetSourceDim();
+ }
+ return nDim;
+}
+
+sal_Int32 ScDPGroupTableData::Compare(sal_Int32 nDim, sal_Int32 nDataId1, sal_Int32 nDataId2)
+{
+ if ( getIsDataLayoutDimension(nDim) )
+ return 0;
+ const ScDPItemData* rItem1 = GetMemberById(nDim, nDataId1);
+ const ScDPItemData* rItem2 = GetMemberById(nDim, nDataId2);
+ if (rItem1 == nullptr || rItem2 == nullptr)
+ return 0;
+ return ScDPItemData::Compare( *rItem1,*rItem2);
+}
+
+#if DUMP_PIVOT_TABLE
+
+void ScDPGroupTableData::Dump() const
+{
+ cout << "--- ScDPGroupTableData" << endl;
+ for (tools::Long i = 0; i < nSourceCount; ++i)
+ {
+ cout << "* dimension: " << i << endl;
+ const ScDPNumGroupDimension& rGrp = pNumGroups[i];
+ rGrp.GetInfo().Dump();
+ }
+ cout << "---" << endl;
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dpitemdata.cxx b/sc/source/core/data/dpitemdata.cxx
new file mode 100644
index 000000000..e4efe75ee
--- /dev/null
+++ b/sc/source/core/data/dpitemdata.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 <dpitemdata.hxx>
+#include <global.hxx>
+
+#include <unotools/collatorwrapper.hxx>
+#include <unotools/transliterationwrapper.hxx>
+#include <rtl/math.hxx>
+
+const sal_Int32 ScDPItemData::DateFirst = -1;
+const sal_Int32 ScDPItemData::DateLast = 10000;
+
+sal_Int32 ScDPItemData::Compare(const ScDPItemData& rA, const ScDPItemData& rB)
+{
+ if (rA.meType != rB.meType)
+ {
+ // group value, value and string in this order. Ensure that the empty
+ // type comes last.
+ return rA.meType < rB.meType ? -1 : 1;
+ }
+
+ switch (rA.meType)
+ {
+ case GroupValue:
+ {
+ if (rA.maGroupValue.mnGroupType == rB.maGroupValue.mnGroupType)
+ {
+ if (rA.maGroupValue.mnValue == rB.maGroupValue.mnValue)
+ return 0;
+
+ return rA.maGroupValue.mnValue < rB.maGroupValue.mnValue ? -1 : 1;
+ }
+
+ return rA.maGroupValue.mnGroupType < rB.maGroupValue.mnGroupType ? -1 : 1;
+ }
+ case Value:
+ case RangeStart:
+ {
+ if (rA.mfValue == rB.mfValue)
+ return 0;
+
+ return rA.mfValue < rB.mfValue ? -1 : 1;
+ }
+ case String:
+ case Error:
+ if (rA.mpString == rB.mpString)
+ // strings may be interned.
+ return 0;
+
+ return ScGlobal::GetCollator().compareString(rA.GetString(), rB.GetString());
+ default:
+ ;
+ }
+ return 0;
+}
+
+ScDPItemData::ScDPItemData() :
+ mfValue(0.0), meType(Empty), mbStringInterned(false) {}
+
+ScDPItemData::ScDPItemData(const ScDPItemData& r) :
+ meType(r.meType), mbStringInterned(r.mbStringInterned)
+{
+ switch (r.meType)
+ {
+ case String:
+ case Error:
+ mpString = r.mpString;
+ if (!mbStringInterned)
+ rtl_uString_acquire(mpString);
+ break;
+ case Value:
+ case RangeStart:
+ mfValue = r.mfValue;
+ break;
+ case GroupValue:
+ maGroupValue.mnGroupType = r.maGroupValue.mnGroupType;
+ maGroupValue.mnValue = r.maGroupValue.mnValue;
+ break;
+ case Empty:
+ default:
+ mfValue = 0.0;
+ }
+}
+
+void ScDPItemData::DisposeString()
+{
+ if (!mbStringInterned)
+ {
+ if (meType == String || meType == Error)
+ rtl_uString_release(mpString);
+ }
+
+ mbStringInterned = false;
+}
+
+ScDPItemData::ScDPItemData(const OUString& rStr) :
+ mpString(rStr.pData), meType(String), mbStringInterned(false)
+{
+ rtl_uString_acquire(mpString);
+}
+
+ScDPItemData::ScDPItemData(sal_Int32 nGroupType, sal_Int32 nValue) :
+ meType(GroupValue), mbStringInterned(false)
+{
+ maGroupValue.mnGroupType = nGroupType;
+ maGroupValue.mnValue = nValue;
+}
+
+ScDPItemData::~ScDPItemData()
+{
+ DisposeString();
+}
+
+void ScDPItemData::SetEmpty()
+{
+ DisposeString();
+ meType = Empty;
+}
+
+void ScDPItemData::SetString(const OUString& rS)
+{
+ DisposeString();
+ mpString = rS.pData;
+ rtl_uString_acquire(mpString);
+ meType = String;
+}
+
+void ScDPItemData::SetStringInterned( rtl_uString* pS )
+{
+ DisposeString();
+ mpString = pS;
+ meType = String;
+ mbStringInterned = true;
+}
+
+void ScDPItemData::SetValue(double fVal)
+{
+ DisposeString();
+ mfValue = fVal;
+ meType = Value;
+}
+
+void ScDPItemData::SetRangeStart(double fVal)
+{
+ DisposeString();
+ mfValue = fVal;
+ meType = RangeStart;
+}
+
+void ScDPItemData::SetRangeFirst()
+{
+ DisposeString();
+ mfValue = -std::numeric_limits<double>::infinity();
+ meType = RangeStart;
+}
+
+void ScDPItemData::SetRangeLast()
+{
+ DisposeString();
+ mfValue = std::numeric_limits<double>::infinity();
+ meType = RangeStart;
+}
+
+void ScDPItemData::SetErrorStringInterned( rtl_uString* pS )
+{
+ SetStringInterned(pS);
+ meType = Error;
+}
+
+bool ScDPItemData::IsCaseInsEqual(const ScDPItemData& r) const
+{
+ if (meType != r.meType)
+ return false;
+
+ switch (meType)
+ {
+ case Value:
+ case RangeStart:
+ return rtl::math::approxEqual(mfValue, r.mfValue);
+ case GroupValue:
+ return maGroupValue.mnGroupType == r.maGroupValue.mnGroupType &&
+ maGroupValue.mnValue == r.maGroupValue.mnValue;
+ default:
+ ;
+ }
+
+ if (mpString == r.mpString)
+ // Fast equality check for interned strings.
+ return true;
+
+ return ScGlobal::GetTransliteration().isEqual(GetString(), r.GetString());
+}
+
+bool ScDPItemData::operator== (const ScDPItemData& r) const
+{
+ if (meType != r.meType)
+ return false;
+
+ switch (meType)
+ {
+ case Value:
+ case RangeStart:
+ return rtl::math::approxEqual(mfValue, r.mfValue);
+ case GroupValue:
+ return maGroupValue.mnGroupType == r.maGroupValue.mnGroupType &&
+ maGroupValue.mnValue == r.maGroupValue.mnValue;
+ default:
+ ;
+ }
+
+ // need exact equality until we have a safe case insensitive string hash
+ return GetString() == r.GetString();
+}
+
+bool ScDPItemData::operator< (const ScDPItemData& r) const
+{
+ return Compare(*this, r) == -1;
+}
+
+ScDPItemData& ScDPItemData::operator= (const ScDPItemData& r)
+{
+ DisposeString();
+ meType = r.meType;
+ switch (r.meType)
+ {
+ case String:
+ case Error:
+ mbStringInterned = r.mbStringInterned;
+ mpString = r.mpString;
+ if (!mbStringInterned)
+ rtl_uString_acquire(mpString);
+ break;
+ case Value:
+ case RangeStart:
+ mfValue = r.mfValue;
+ break;
+ case GroupValue:
+ maGroupValue.mnGroupType = r.maGroupValue.mnGroupType;
+ maGroupValue.mnValue = r.maGroupValue.mnValue;
+ break;
+ case Empty:
+ default:
+ mfValue = 0.0;
+ }
+ return *this;
+}
+
+ScDPValue::Type ScDPItemData::GetCellType() const
+{
+ switch (meType)
+ {
+ case Error:
+ return ScDPValue::Error;
+ case Empty:
+ return ScDPValue::Empty;
+ case Value:
+ return ScDPValue::Value;
+ default:
+ ;
+ }
+
+ return ScDPValue::String;
+}
+
+#if DEBUG_PIVOT_TABLE
+
+void ScDPItemData::Dump(const char* msg) const
+{
+ printf("--- (%s)\n", msg);
+ switch (meType)
+ {
+ case Empty:
+ printf("empty\n");
+ break;
+ case Error:
+ printf("error: %s\n",
+ OUStringToOString(OUString(mpString), RTL_TEXTENCODING_UTF8).getStr());
+ break;
+ case GroupValue:
+ printf("group value: group type = %d value = %d\n",
+ maGroupValue.mnGroupType, maGroupValue.mnValue);
+ break;
+ case String:
+ printf("string: %s\n",
+ OUStringToOString(OUString(mpString), RTL_TEXTENCODING_UTF8).getStr());
+ break;
+ case Value:
+ printf("value: %g\n", mfValue);
+ break;
+ case RangeStart:
+ printf("range start: %g\n", mfValue);
+ break;
+ default:
+ printf("unknown type\n");
+ }
+ printf("---\n");
+}
+#endif
+
+bool ScDPItemData::IsEmpty() const
+{
+ return meType == Empty;
+}
+
+bool ScDPItemData::IsValue() const
+{
+ return meType == Value;
+}
+
+OUString ScDPItemData::GetString() const
+{
+ switch (meType)
+ {
+ case String:
+ case Error:
+ return OUString(mpString);
+ case Value:
+ case RangeStart:
+ return OUString::number(mfValue);
+ case GroupValue:
+ return OUString::number(maGroupValue.mnValue);
+ case Empty:
+ default:
+ ;
+ }
+
+ return OUString();
+}
+
+double ScDPItemData::GetValue() const
+{
+ if (meType == Value || meType == RangeStart)
+ return mfValue;
+
+ return 0.0;
+}
+
+ScDPItemData::GroupValueAttr ScDPItemData::GetGroupValue() const
+{
+ if (meType == GroupValue)
+ return maGroupValue;
+
+ GroupValueAttr aGV;
+ aGV.mnGroupType = -1;
+ aGV.mnValue = -1;
+ return aGV;
+}
+
+bool ScDPItemData::HasStringData() const
+{
+ return meType == String || meType == Error;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dpnumgroupinfo.cxx b/sc/source/core/data/dpnumgroupinfo.cxx
new file mode 100644
index 000000000..f57822e16
--- /dev/null
+++ b/sc/source/core/data/dpnumgroupinfo.cxx
@@ -0,0 +1,35 @@
+/* -*- 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 <dpnumgroupinfo.hxx>
+
+ScDPNumGroupInfo::ScDPNumGroupInfo() :
+ mbEnable(false),
+ mbDateValues(false),
+ mbAutoStart(false),
+ mbAutoEnd(false),
+ mbIntegerOnly(true),
+ mfStart(0.0), mfEnd(0.0), mfStep(0.0) {}
+
+#if DUMP_PIVOT_TABLE
+
+void ScDPNumGroupInfo::Dump() const
+{
+ cout << "--- ScDPNumGroupInfo" << endl;
+ cout << " enabled: " << mbEnable << endl;
+ cout << " auto start: " << mbAutoStart << endl;
+ cout << " auto end: " << mbAutoEnd << endl;
+ cout << " start: " << mfStart << endl;
+ cout << " end: " << mfEnd << endl;
+ cout << " step: " << mfStep << endl;
+ cout << "---" << endl;
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dpobject.cxx b/sc/source/core/data/dpobject.cxx
new file mode 100644
index 000000000..fc31af3c6
--- /dev/null
+++ b/sc/source/core/data/dpobject.cxx
@@ -0,0 +1,3952 @@
+/* -*- 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 <docsh.hxx>
+#include <dpcache.hxx>
+#include <dpobject.hxx>
+#include <dptabsrc.hxx>
+#include <dpsave.hxx>
+#include <dpdimsave.hxx>
+#include <dpoutput.hxx>
+#include <dpshttab.hxx>
+#include <dpsdbtab.hxx>
+#include <dpgroup.hxx>
+#include <document.hxx>
+#include <pivot.hxx>
+#include <dapiuno.hxx>
+#include <miscuno.hxx>
+#include <refupdat.hxx>
+#include <attrib.hxx>
+#include <scitems.hxx>
+#include <unonames.hxx>
+#include <dpglobal.hxx>
+#include <globstr.hrc>
+#include <queryentry.hxx>
+#include <dputil.hxx>
+
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/sdb/XCompletedExecution.hpp>
+#include <com/sun/star/sdbc/DataType.hpp>
+#include <com/sun/star/sdbc/SQLException.hpp>
+#include <com/sun/star/sdbc/XResultSetMetaData.hpp>
+#include <com/sun/star/sdbc/XResultSetMetaDataSupplier.hpp>
+#include <com/sun/star/sdbc/XRow.hpp>
+#include <com/sun/star/sdbc/XRowSet.hpp>
+#include <com/sun/star/sheet/GeneralFunction2.hpp>
+#include <com/sun/star/sheet/DataPilotFieldFilter.hpp>
+#include <com/sun/star/sheet/DataPilotFieldOrientation.hpp>
+#include <com/sun/star/sheet/DataPilotFieldReferenceType.hpp>
+#include <com/sun/star/sheet/DataPilotTableHeaderData.hpp>
+#include <com/sun/star/sheet/DataPilotTablePositionData.hpp>
+#include <com/sun/star/sheet/DataPilotTablePositionType.hpp>
+#include <com/sun/star/sheet/DimensionFlags.hpp>
+#include <com/sun/star/task/InteractionHandler.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/lang/XSingleServiceFactory.hpp>
+#include <com/sun/star/lang/XSingleComponentFactory.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/container/XContentEnumerationAccess.hpp>
+#include <com/sun/star/sheet/XDrillDownDataSupplier.hpp>
+
+#include <unotools/charclass.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/string.hxx>
+#include <comphelper/types.hxx>
+#include <o3tl/safeint.hxx>
+#include <sal/macros.h>
+#include <svl/numformat.hxx>
+#include <tools/diagnose_ex.h>
+#include <svl/zforlist.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/weld.hxx>
+
+#include <utility>
+#include <vector>
+#include <memory>
+#include <algorithm>
+
+using namespace com::sun::star;
+using ::std::vector;
+using ::std::shared_ptr;
+using ::com::sun::star::uno::Sequence;
+using ::com::sun::star::uno::Reference;
+using ::com::sun::star::uno::UNO_QUERY;
+using ::com::sun::star::uno::Any;
+using ::com::sun::star::uno::Exception;
+using ::com::sun::star::lang::XComponent;
+using ::com::sun::star::sheet::DataPilotTableHeaderData;
+using ::com::sun::star::sheet::DataPilotTablePositionData;
+using ::com::sun::star::sheet::XDimensionsSupplier;
+using ::com::sun::star::beans::XPropertySet;
+
+constexpr OUStringLiteral SC_SERVICE_ROWSET = u"com.sun.star.sdb.RowSet";
+
+constexpr OUStringLiteral SC_DBPROP_DATASOURCENAME = u"DataSourceName";
+constexpr OUStringLiteral SC_DBPROP_COMMAND = u"Command";
+constexpr OUStringLiteral SC_DBPROP_COMMANDTYPE = u"CommandType";
+
+constexpr OUStringLiteral SCDPSOURCE_SERVICE = u"com.sun.star.sheet.DataPilotSource";
+
+namespace {
+
+/**
+ * Database connection implementation for UNO database API. Note that in
+ * the UNO database API, column index is 1-based, whereas the interface
+ * requires that column index be 0-based.
+ */
+class DBConnector : public ScDPCache::DBConnector
+{
+ ScDPCache& mrCache;
+
+ uno::Reference<sdbc::XRowSet> mxRowSet;
+ uno::Reference<sdbc::XRow> mxRow;
+ uno::Reference<sdbc::XResultSetMetaData> mxMetaData;
+ Date maNullDate;
+
+public:
+ DBConnector(ScDPCache& rCache, const uno::Reference<sdbc::XRowSet>& xRowSet, const Date& rNullDate);
+
+ bool isValid() const;
+
+ virtual void getValue(tools::Long nCol, ScDPItemData &rData, SvNumFormatType& rNumType) const override;
+ virtual OUString getColumnLabel(tools::Long nCol) const override;
+ virtual tools::Long getColumnCount() const override;
+ virtual bool first() override;
+ virtual bool next() override;
+ virtual void finish() override;
+};
+
+DBConnector::DBConnector(ScDPCache& rCache, const uno::Reference<sdbc::XRowSet>& xRowSet, const Date& rNullDate) :
+ mrCache(rCache), mxRowSet(xRowSet), maNullDate(rNullDate)
+{
+ Reference<sdbc::XResultSetMetaDataSupplier> xMetaSupp(mxRowSet, UNO_QUERY);
+ if (xMetaSupp.is())
+ mxMetaData = xMetaSupp->getMetaData();
+
+ mxRow.set(mxRowSet, UNO_QUERY);
+}
+
+bool DBConnector::isValid() const
+{
+ return mxRowSet.is() && mxRow.is() && mxMetaData.is();
+}
+
+bool DBConnector::first()
+{
+ return mxRowSet->first();
+}
+
+bool DBConnector::next()
+{
+ return mxRowSet->next();
+}
+
+void DBConnector::finish()
+{
+ mxRowSet->beforeFirst();
+}
+
+tools::Long DBConnector::getColumnCount() const
+{
+ return mxMetaData->getColumnCount();
+}
+
+OUString DBConnector::getColumnLabel(tools::Long nCol) const
+{
+ return mxMetaData->getColumnLabel(nCol+1);
+}
+
+void DBConnector::getValue(tools::Long nCol, ScDPItemData &rData, SvNumFormatType& rNumType) const
+{
+ rNumType = SvNumFormatType::NUMBER;
+ sal_Int32 nType = mxMetaData->getColumnType(nCol+1);
+
+ try
+ {
+ double fValue = 0.0;
+ switch (nType)
+ {
+ case sdbc::DataType::BIT:
+ case sdbc::DataType::BOOLEAN:
+ {
+ rNumType = SvNumFormatType::LOGICAL;
+ fValue = mxRow->getBoolean(nCol+1) ? 1 : 0;
+ rData.SetValue(fValue);
+ break;
+ }
+ case sdbc::DataType::TINYINT:
+ case sdbc::DataType::SMALLINT:
+ case sdbc::DataType::INTEGER:
+ case sdbc::DataType::BIGINT:
+ case sdbc::DataType::FLOAT:
+ case sdbc::DataType::REAL:
+ case sdbc::DataType::DOUBLE:
+ case sdbc::DataType::NUMERIC:
+ case sdbc::DataType::DECIMAL:
+ {
+ //TODO: do the conversion here?
+ fValue = mxRow->getDouble(nCol+1);
+ rData.SetValue(fValue);
+ break;
+ }
+ case sdbc::DataType::DATE:
+ {
+ rNumType = SvNumFormatType::DATE;
+
+ util::Date aDate = mxRow->getDate(nCol+1);
+ fValue = Date(aDate.Day, aDate.Month, aDate.Year) - maNullDate;
+ rData.SetValue(fValue);
+ break;
+ }
+ case sdbc::DataType::TIME:
+ {
+ rNumType = SvNumFormatType::TIME;
+
+ util::Time aTime = mxRow->getTime(nCol+1);
+ fValue = aTime.Hours / static_cast<double>(::tools::Time::hourPerDay) +
+ aTime.Minutes / static_cast<double>(::tools::Time::minutePerDay) +
+ aTime.Seconds / static_cast<double>(::tools::Time::secondPerDay) +
+ aTime.NanoSeconds / static_cast<double>(::tools::Time::nanoSecPerDay);
+ rData.SetValue(fValue);
+ break;
+ }
+ case sdbc::DataType::TIMESTAMP:
+ {
+ rNumType = SvNumFormatType::DATETIME;
+
+ util::DateTime aStamp = mxRow->getTimestamp(nCol+1);
+ fValue = ( Date( aStamp.Day, aStamp.Month, aStamp.Year ) - maNullDate ) +
+ aStamp.Hours / static_cast<double>(::tools::Time::hourPerDay) +
+ aStamp.Minutes / static_cast<double>(::tools::Time::minutePerDay) +
+ aStamp.Seconds / static_cast<double>(::tools::Time::secondPerDay) +
+ aStamp.NanoSeconds / static_cast<double>(::tools::Time::nanoSecPerDay);
+ rData.SetValue(fValue);
+ break;
+ }
+ case sdbc::DataType::CHAR:
+ case sdbc::DataType::VARCHAR:
+ case sdbc::DataType::LONGVARCHAR:
+ case sdbc::DataType::SQLNULL:
+ case sdbc::DataType::BINARY:
+ case sdbc::DataType::VARBINARY:
+ case sdbc::DataType::LONGVARBINARY:
+ default:
+ // nCol is 0-based, and the left-most column always has nCol == 0.
+ rData.SetStringInterned(
+ mrCache.InternString(nCol, mxRow->getString(nCol+1)));
+ }
+ }
+ catch (uno::Exception&)
+ {
+ rData.SetEmpty();
+ }
+}
+
+}
+
+static sheet::DataPilotFieldOrientation lcl_GetDataGetOrientation( const uno::Reference<sheet::XDimensionsSupplier>& xSource )
+{
+ sheet::DataPilotFieldOrientation nRet = sheet::DataPilotFieldOrientation_HIDDEN;
+ if ( xSource.is() )
+ {
+ uno::Reference<container::XNameAccess> xDimNameAccess = xSource->getDimensions();
+ const uno::Sequence<OUString> aDimNames = xDimNameAccess->getElementNames();
+ for (const OUString& rDimName : aDimNames)
+ {
+ uno::Reference<beans::XPropertySet> xDimProp(xDimNameAccess->getByName(rDimName),
+ uno::UNO_QUERY);
+ if ( xDimProp.is() )
+ {
+ const bool bFound = ScUnoHelpFunctions::GetBoolProperty( xDimProp,
+ SC_UNO_DP_ISDATALAYOUT );
+ //TODO: error checking -- is "IsDataLayoutDimension" property required??
+ if (bFound)
+ {
+ nRet = ScUnoHelpFunctions::GetEnumProperty(
+ xDimProp, SC_UNO_DP_ORIENTATION,
+ sheet::DataPilotFieldOrientation_HIDDEN );
+ break;
+ }
+ }
+ }
+ }
+ return nRet;
+}
+
+ScDPServiceDesc::ScDPServiceDesc(
+ const OUString& rServ, const OUString& rSrc, const OUString& rNam,
+ const OUString& rUser, const OUString& rPass ) :
+ aServiceName( rServ ),
+ aParSource( rSrc ),
+ aParName( rNam ),
+ aParUser( rUser ),
+ aParPass( rPass ) {}
+
+bool ScDPServiceDesc::operator== ( const ScDPServiceDesc& rOther ) const
+{
+ return aServiceName == rOther.aServiceName &&
+ aParSource == rOther.aParSource &&
+ aParName == rOther.aParName &&
+ aParUser == rOther.aParUser &&
+ aParPass == rOther.aParPass;
+}
+
+ScDPObject::ScDPObject( ScDocument* pD ) :
+ pDoc( pD ),
+ nHeaderRows( 0 ),
+ mbHeaderLayout(false),
+ bAllowMove(false),
+ bSettingsChanged(false),
+ mbEnableGetPivotData(true)
+{
+}
+
+ScDPObject::ScDPObject(const ScDPObject& r) :
+ pDoc( r.pDoc ),
+ aTableName( r.aTableName ),
+ aTableTag( r.aTableTag ),
+ aOutRange( r.aOutRange ),
+ maInteropGrabBag(r.maInteropGrabBag),
+ nHeaderRows( r.nHeaderRows ),
+ mbHeaderLayout( r.mbHeaderLayout ),
+ bAllowMove(false),
+ bSettingsChanged(false),
+ mbEnableGetPivotData(r.mbEnableGetPivotData)
+{
+ if (r.pSaveData)
+ pSaveData.reset( new ScDPSaveData(*r.pSaveData) );
+ if (r.pSheetDesc)
+ pSheetDesc.reset( new ScSheetSourceDesc(*r.pSheetDesc) );
+ if (r.pImpDesc)
+ pImpDesc.reset( new ScImportSourceDesc(*r.pImpDesc) );
+ if (r.pServDesc)
+ pServDesc.reset( new ScDPServiceDesc(*r.pServDesc) );
+ // xSource (and pOutput) is not copied
+}
+
+ScDPObject::~ScDPObject()
+{
+ Clear();
+}
+
+ScDPObject& ScDPObject::operator= (const ScDPObject& r)
+{
+ if (this != &r)
+ {
+ Clear();
+
+ pDoc = r.pDoc;
+ aTableName = r.aTableName;
+ aTableTag = r.aTableTag;
+ aOutRange = r.aOutRange;
+ maInteropGrabBag = r.maInteropGrabBag;
+ nHeaderRows = r.nHeaderRows;
+ mbHeaderLayout = r.mbHeaderLayout;
+ bAllowMove = false;
+ bSettingsChanged = false;
+ mbEnableGetPivotData = r.mbEnableGetPivotData;
+
+ if (r.pSaveData)
+ pSaveData.reset( new ScDPSaveData(*r.pSaveData) );
+ if (r.pSheetDesc)
+ pSheetDesc.reset( new ScSheetSourceDesc(*r.pSheetDesc) );
+ if (r.pImpDesc)
+ pImpDesc.reset( new ScImportSourceDesc(*r.pImpDesc) );
+ if (r.pServDesc)
+ pServDesc.reset( new ScDPServiceDesc(*r.pServDesc) );
+ }
+ return *this;
+}
+
+void ScDPObject::EnableGetPivotData(bool b)
+{
+ mbEnableGetPivotData = b;
+}
+
+void ScDPObject::SetAllowMove(bool bSet)
+{
+ bAllowMove = bSet;
+}
+
+void ScDPObject::SetSaveData(const ScDPSaveData& rData)
+{
+ if ( pSaveData.get() != &rData ) // API implementation modifies the original SaveData object
+ {
+ pSaveData.reset( new ScDPSaveData( rData ) );
+ }
+
+ InvalidateData(); // re-init source from SaveData
+}
+
+void ScDPObject::SetHeaderLayout (bool bUseGrid)
+{
+ mbHeaderLayout = bUseGrid;
+}
+
+void ScDPObject::SetOutRange(const ScRange& rRange)
+{
+ aOutRange = rRange;
+
+ if ( pOutput )
+ pOutput->SetPosition( rRange.aStart );
+}
+
+const ScRange& ScDPObject::GetOutRange() const
+{
+ return aOutRange;
+}
+
+void ScDPObject::SetSheetDesc(const ScSheetSourceDesc& rDesc)
+{
+ if ( pSheetDesc && rDesc == *pSheetDesc )
+ return; // nothing to do
+
+ pImpDesc.reset();
+ pServDesc.reset();
+
+ pSheetDesc.reset( new ScSheetSourceDesc(rDesc) );
+
+ // make valid QueryParam
+
+ const ScRange& rSrcRange = pSheetDesc->GetSourceRange();
+ ScQueryParam aParam = pSheetDesc->GetQueryParam();
+ aParam.nCol1 = rSrcRange.aStart.Col();
+ aParam.nRow1 = rSrcRange.aStart.Row();
+ aParam.nCol2 = rSrcRange.aEnd.Col();
+ aParam.nRow2 = rSrcRange.aEnd.Row();
+ aParam.bHasHeader = true;
+ pSheetDesc->SetQueryParam(aParam);
+
+ ClearTableData(); // new source must be created
+}
+
+void ScDPObject::SetImportDesc(const ScImportSourceDesc& rDesc)
+{
+ if ( pImpDesc && rDesc == *pImpDesc )
+ return; // nothing to do
+
+ pSheetDesc.reset();
+ pServDesc.reset();
+
+ pImpDesc.reset( new ScImportSourceDesc(rDesc) );
+
+ ClearTableData(); // new source must be created
+}
+
+void ScDPObject::SetServiceData(const ScDPServiceDesc& rDesc)
+{
+ if ( pServDesc && rDesc == *pServDesc )
+ return; // nothing to do
+
+ pSheetDesc.reset();
+ pImpDesc.reset();
+
+ pServDesc.reset( new ScDPServiceDesc(rDesc) );
+
+ ClearTableData(); // new source must be created
+}
+
+void ScDPObject::WriteSourceDataTo( ScDPObject& rDest ) const
+{
+ if ( pSheetDesc )
+ rDest.SetSheetDesc( *pSheetDesc );
+ else if ( pImpDesc )
+ rDest.SetImportDesc( *pImpDesc );
+ else if ( pServDesc )
+ rDest.SetServiceData( *pServDesc );
+
+ // name/tag are not source data, but needed along with source data
+
+ rDest.aTableName = aTableName;
+ rDest.aTableTag = aTableTag;
+}
+
+void ScDPObject::WriteTempDataTo( ScDPObject& rDest ) const
+{
+ rDest.nHeaderRows = nHeaderRows;
+}
+
+bool ScDPObject::IsSheetData() const
+{
+ return ( pSheetDesc != nullptr );
+}
+
+void ScDPObject::SetName(const OUString& rNew)
+{
+ aTableName = rNew;
+}
+
+void ScDPObject::SetTag(const OUString& rNew)
+{
+ aTableTag = rNew;
+}
+
+bool ScDPObject::IsDataDescriptionCell(const ScAddress& rPos)
+{
+ if (!pSaveData)
+ return false;
+
+ tools::Long nDataDimCount = pSaveData->GetDataDimensionCount();
+ if (nDataDimCount != 1)
+ // There has to be exactly one data dimension for the description to
+ // appear at top-left corner.
+ return false;
+
+ CreateOutput();
+ ScRange aTabRange = pOutput->GetOutputRange(sheet::DataPilotOutputRangeType::TABLE);
+ return (rPos == aTabRange.aStart);
+}
+
+uno::Reference<sheet::XDimensionsSupplier> const & ScDPObject::GetSource()
+{
+ CreateObjects();
+ return xSource;
+}
+
+void ScDPObject::CreateOutput()
+{
+ CreateObjects();
+ if (pOutput)
+ return;
+
+ bool bFilterButton = IsSheetData() && pSaveData && pSaveData->GetFilterButton();
+ pOutput.reset( new ScDPOutput( pDoc, xSource, aOutRange.aStart, bFilterButton ) );
+ pOutput->SetHeaderLayout ( mbHeaderLayout );
+
+ sal_Int32 nOldRows = nHeaderRows;
+ nHeaderRows = pOutput->GetHeaderRows();
+
+ if ( !(bAllowMove && nHeaderRows != nOldRows) )
+ return;
+
+ sal_Int32 nDiff = nOldRows - nHeaderRows;
+ if ( nOldRows == 0 )
+ --nDiff;
+ if ( nHeaderRows == 0 )
+ ++nDiff;
+
+ sal_Int32 nNewRow = aOutRange.aStart.Row() + nDiff;
+ if ( nNewRow < 0 )
+ nNewRow = 0;
+
+ ScAddress aStart( aOutRange.aStart );
+ aStart.SetRow(nNewRow);
+ pOutput->SetPosition( aStart );
+
+ //TODO: modify aOutRange?
+
+ bAllowMove = false; // use only once
+}
+
+namespace {
+
+class DisableGetPivotData
+{
+ ScDPObject& mrDPObj;
+ bool mbOldState;
+public:
+ DisableGetPivotData(ScDPObject& rObj, bool bOld) : mrDPObj(rObj), mbOldState(bOld)
+ {
+ mrDPObj.EnableGetPivotData(false);
+ }
+
+ ~DisableGetPivotData()
+ {
+ mrDPObj.EnableGetPivotData(mbOldState);
+ }
+};
+
+class FindIntersectingTable
+{
+ ScRange maRange;
+public:
+ explicit FindIntersectingTable(const ScRange& rRange) : maRange(rRange) {}
+
+ bool operator() (const std::unique_ptr<ScDPObject>& rObj) const
+ {
+ return maRange.Intersects(rObj->GetOutRange());
+ }
+};
+
+class FindIntersectingTableByColumns
+{
+ SCCOL mnCol1;
+ SCCOL mnCol2;
+ SCROW mnRow;
+ SCTAB mnTab;
+public:
+ FindIntersectingTableByColumns(SCCOL nCol1, SCCOL nCol2, SCROW nRow, SCTAB nTab) :
+ mnCol1(nCol1), mnCol2(nCol2), mnRow(nRow), mnTab(nTab) {}
+
+ bool operator() (const std::unique_ptr<ScDPObject>& rObj) const
+ {
+ const ScRange& rRange = rObj->GetOutRange();
+ if (mnTab != rRange.aStart.Tab())
+ // Not on this sheet.
+ return false;
+
+ if (rRange.aEnd.Row() < mnRow)
+ // This table is above the row. It's safe.
+ return false;
+
+ if (mnCol1 <= rRange.aStart.Col() && rRange.aEnd.Col() <= mnCol2)
+ // This table is fully enclosed in this column range.
+ return false;
+
+ if (rRange.aEnd.Col() < mnCol1 || mnCol2 < rRange.aStart.Col())
+ // This table is entirely outside this column range.
+ return false;
+
+ // This table must be intersected by this column range.
+ return true;
+ }
+};
+
+class FindIntersectingTableByRows
+{
+ SCCOL mnCol;
+ SCROW mnRow1;
+ SCROW mnRow2;
+ SCTAB mnTab;
+public:
+ FindIntersectingTableByRows(SCCOL nCol, SCROW nRow1, SCROW nRow2, SCTAB nTab) :
+ mnCol(nCol), mnRow1(nRow1), mnRow2(nRow2), mnTab(nTab) {}
+
+ bool operator() (const std::unique_ptr<ScDPObject>& rObj) const
+ {
+ const ScRange& rRange = rObj->GetOutRange();
+ if (mnTab != rRange.aStart.Tab())
+ // Not on this sheet.
+ return false;
+
+ if (rRange.aEnd.Col() < mnCol)
+ // This table is to the left of the column. It's safe.
+ return false;
+
+ if (mnRow1 <= rRange.aStart.Row() && rRange.aEnd.Row() <= mnRow2)
+ // This table is fully enclosed in this row range.
+ return false;
+
+ if (rRange.aEnd.Row() < mnRow1 || mnRow2 < rRange.aStart.Row())
+ // This table is entirely outside this row range.
+ return false;
+
+ // This table must be intersected by this row range.
+ return true;
+ }
+};
+
+class AccumulateOutputRanges
+{
+ ScRangeList maRanges;
+ SCTAB mnTab;
+public:
+ explicit AccumulateOutputRanges(SCTAB nTab) : mnTab(nTab) {}
+ AccumulateOutputRanges(const AccumulateOutputRanges& r) : maRanges(r.maRanges), mnTab(r.mnTab) {}
+
+ void operator() (const std::unique_ptr<ScDPObject>& rObj)
+ {
+ const ScRange& rRange = rObj->GetOutRange();
+ if (mnTab != rRange.aStart.Tab())
+ // Not on this sheet.
+ return;
+
+ maRanges.Join(rRange);
+ }
+
+ const ScRangeList& getRanges() const { return maRanges; }
+};
+
+}
+
+ScDPTableData* ScDPObject::GetTableData()
+{
+ if (!mpTableData)
+ {
+ shared_ptr<ScDPTableData> pData;
+ const ScDPDimensionSaveData* pDimData = pSaveData ? pSaveData->GetExistingDimensionData() : nullptr;
+
+ if ( pImpDesc )
+ {
+ // database data
+ const ScDPCache* pCache = pImpDesc->CreateCache(pDimData);
+ if (pCache)
+ {
+ pCache->AddReference(this);
+ pData = std::make_shared<ScDatabaseDPData>(pDoc, *pCache);
+ }
+ }
+ else
+ {
+ // cell data
+ if (!pSheetDesc)
+ {
+ OSL_FAIL("no source descriptor");
+ pSheetDesc.reset( new ScSheetSourceDesc(pDoc) ); // dummy defaults
+ }
+
+ {
+ // Temporarily disable GETPIVOTDATA to avoid having
+ // GETPIVOTDATA called onto itself from within the source
+ // range.
+ DisableGetPivotData aSwitch(*this, mbEnableGetPivotData);
+ const ScDPCache* pCache = pSheetDesc->CreateCache(pDimData);
+ if (pCache)
+ {
+ pCache->AddReference(this);
+ pData = std::make_shared<ScSheetDPData>(pDoc, *pSheetDesc, *pCache);
+ }
+ }
+ }
+
+ // grouping (for cell or database data)
+ if (pData && pDimData)
+ {
+ auto pGroupData = std::make_shared<ScDPGroupTableData>(pData, pDoc);
+ pDimData->WriteToData(*pGroupData);
+ pData = pGroupData;
+ }
+
+ mpTableData = pData; // after SetCacheId
+ }
+
+ return mpTableData.get();
+}
+
+void ScDPObject::CreateObjects()
+{
+ if (!xSource.is())
+ {
+ pOutput.reset(); // not valid when xSource is changed
+
+ if ( pServDesc )
+ {
+ xSource = CreateSource( *pServDesc );
+ }
+
+ if ( !xSource.is() ) // database or sheet data, or error in CreateSource
+ {
+ OSL_ENSURE( !pServDesc, "DPSource could not be created" );
+ ScDPTableData* pData = GetTableData();
+ if (pData)
+ {
+ if (pSaveData)
+ // Make sure to transfer these flags to the table data
+ // since they may have changed.
+ pData->SetEmptyFlags(pSaveData->GetIgnoreEmptyRows(), pSaveData->GetRepeatIfEmpty());
+
+ pData->ReloadCacheTable();
+ xSource = new ScDPSource( pData );
+ }
+ }
+
+ if (pSaveData)
+ pSaveData->WriteToSource( xSource );
+ }
+ else if (bSettingsChanged)
+ {
+ pOutput.reset(); // not valid when xSource is changed
+
+ uno::Reference<util::XRefreshable> xRef( xSource, uno::UNO_QUERY );
+ if (xRef.is())
+ {
+ try
+ {
+ xRef->refresh();
+ }
+ catch(uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION( "sc", "exception in refresh");
+ }
+ }
+
+ if (pSaveData)
+ pSaveData->WriteToSource( xSource );
+ }
+ bSettingsChanged = false;
+}
+
+void ScDPObject::InvalidateData()
+{
+ bSettingsChanged = true;
+}
+
+void ScDPObject::Clear()
+{
+ pOutput.reset();
+ pSaveData.reset();
+ pSheetDesc.reset();
+ pImpDesc.reset();
+ pServDesc.reset();
+ ClearTableData();
+ maInteropGrabBag.clear();
+}
+
+void ScDPObject::ClearTableData()
+{
+ ClearSource();
+
+ if (mpTableData)
+ mpTableData->GetCacheTable().getCache().RemoveReference(this);
+ mpTableData.reset();
+}
+
+void ScDPObject::ReloadGroupTableData()
+{
+ ClearSource();
+
+ if (!mpTableData)
+ // Table data not built yet. No need to reload the group data.
+ return;
+
+ if (!pSaveData)
+ // How could it not have the save data... but whatever.
+ return;
+
+ const ScDPDimensionSaveData* pDimData = pSaveData->GetExistingDimensionData();
+ if (!pDimData || !pDimData->HasGroupDimensions())
+ {
+ // No group dimensions exist. Check if it currently has group
+ // dimensions, and if so, remove all of them.
+ ScDPGroupTableData* pData = dynamic_cast<ScDPGroupTableData*>(mpTableData.get());
+ if (pData)
+ {
+ // Replace the existing group table data with the source data.
+ mpTableData = pData->GetSourceTableData();
+ }
+ return;
+ }
+
+ ScDPGroupTableData* pData = dynamic_cast<ScDPGroupTableData*>(mpTableData.get());
+ if (pData)
+ {
+ // This is already a group table data. Salvage the source data and
+ // re-create a new group data.
+ const shared_ptr<ScDPTableData>& pSource = pData->GetSourceTableData();
+ auto pGroupData = std::make_shared<ScDPGroupTableData>(pSource, pDoc);
+ pDimData->WriteToData(*pGroupData);
+ mpTableData = pGroupData;
+ }
+ else
+ {
+ // This is a source data. Create a group data based on it.
+ auto pGroupData = std::make_shared<ScDPGroupTableData>(mpTableData, pDoc);
+ pDimData->WriteToData(*pGroupData);
+ mpTableData = pGroupData;
+ }
+
+ bSettingsChanged = true;
+}
+
+void ScDPObject::ClearSource()
+{
+ Reference< XComponent > xObjectComp( xSource, UNO_QUERY );
+ if (xObjectComp.is())
+ {
+ try
+ {
+ xObjectComp->dispose();
+ }
+ catch( const Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("sc.core");
+ }
+ }
+ xSource = nullptr;
+}
+
+ScRange ScDPObject::GetNewOutputRange( bool& rOverflow )
+{
+ CreateOutput(); // create xSource and pOutput if not already done
+
+ rOverflow = pOutput->HasError(); // range overflow or exception from source
+ if ( rOverflow )
+ return ScRange( aOutRange.aStart );
+ else
+ {
+ // don't store the result in aOutRange, because nothing has been output yet
+ return pOutput->GetOutputRange();
+ }
+}
+
+void ScDPObject::Output( const ScAddress& rPos )
+{
+ // clear old output area
+ pDoc->DeleteAreaTab( aOutRange.aStart.Col(), aOutRange.aStart.Row(),
+ aOutRange.aEnd.Col(), aOutRange.aEnd.Row(),
+ aOutRange.aStart.Tab(), InsertDeleteFlags::ALL );
+ pDoc->RemoveFlagsTab( aOutRange.aStart.Col(), aOutRange.aStart.Row(),
+ aOutRange.aEnd.Col(), aOutRange.aEnd.Row(),
+ aOutRange.aStart.Tab(), ScMF::Auto );
+
+ CreateOutput(); // create xSource and pOutput if not already done
+
+ pOutput->SetPosition( rPos );
+
+ pOutput->Output();
+
+ // aOutRange is always the range that was last output to the document
+ aOutRange = pOutput->GetOutputRange();
+ const ScAddress& s = aOutRange.aStart;
+ const ScAddress& e = aOutRange.aEnd;
+ pDoc->ApplyFlagsTab(s.Col(), s.Row(), e.Col(), e.Row(), s.Tab(), ScMF::DpTable);
+}
+
+ScRange ScDPObject::GetOutputRangeByType( sal_Int32 nType )
+{
+ CreateOutput();
+
+ if (pOutput->HasError())
+ return ScRange(aOutRange.aStart);
+
+ return pOutput->GetOutputRange(nType);
+}
+
+ScRange ScDPObject::GetOutputRangeByType( sal_Int32 nType ) const
+{
+ if (!pOutput || pOutput->HasError())
+ return ScRange(ScAddress::INITIALIZE_INVALID);
+
+ return pOutput->GetOutputRange(nType);
+}
+
+static bool lcl_HasButton( const ScDocument* pDoc, SCCOL nCol, SCROW nRow, SCTAB nTab )
+{
+ return pDoc->GetAttr( nCol, nRow, nTab, ATTR_MERGE_FLAG )->HasPivotButton();
+}
+
+void ScDPObject::RefreshAfterLoad()
+{
+ // apply drop-down attribute, initialize nHeaderRows, without accessing the source
+ // (button attribute must be present)
+
+ // simple test: block of button cells at the top, followed by an empty cell
+
+ SCCOL nFirstCol = aOutRange.aStart.Col();
+ SCROW nFirstRow = aOutRange.aStart.Row();
+ SCTAB nTab = aOutRange.aStart.Tab();
+
+ SCROW nInitial = 0;
+ SCROW nOutRows = aOutRange.aEnd.Row() + 1 - aOutRange.aStart.Row();
+ while ( nInitial + 1 < nOutRows && lcl_HasButton( pDoc, nFirstCol, nFirstRow + nInitial, nTab ) )
+ ++nInitial;
+
+ if ( nInitial + 1 < nOutRows &&
+ pDoc->IsBlockEmpty( nFirstCol, nFirstRow + nInitial, nFirstCol, nFirstRow + nInitial, nTab ) &&
+ aOutRange.aEnd.Col() > nFirstCol )
+ {
+ nHeaderRows = nInitial;
+ }
+ else
+ nHeaderRows = 0; // nothing found, no drop-down lists
+}
+
+void ScDPObject::BuildAllDimensionMembers()
+{
+ if (!pSaveData)
+ return;
+
+ // #i111857# don't always create empty mpTableData for external service.
+ if (pServDesc)
+ return;
+
+ ScDPTableData* pTableData = GetTableData();
+ if(pTableData)
+ pSaveData->BuildAllDimensionMembers(pTableData);
+}
+
+bool ScDPObject::SyncAllDimensionMembers()
+{
+ if (!pSaveData)
+ return false;
+
+ // #i111857# don't always create empty mpTableData for external service.
+ // Ideally, xSource should be used instead of mpTableData.
+ if (pServDesc)
+ return false;
+
+ ScDPTableData* pData = GetTableData();
+ if (!pData)
+ // No table data exists. This can happen when refreshing from an
+ // external source which doesn't exist.
+ return false;
+
+ // Refresh the cache wrapper since the cache may have changed.
+ pData->SetEmptyFlags(pSaveData->GetIgnoreEmptyRows(), pSaveData->GetRepeatIfEmpty());
+ pData->ReloadCacheTable();
+ pSaveData->SyncAllDimensionMembers(pData);
+ return true;
+}
+
+bool ScDPObject::GetMemberNames( sal_Int32 nDim, Sequence<OUString>& rNames )
+{
+ vector<ScDPLabelData::Member> aMembers;
+ if (!GetMembers(nDim, GetUsedHierarchy(nDim), aMembers))
+ return false;
+
+ size_t n = aMembers.size();
+ rNames.realloc(n);
+ auto pNames = rNames.getArray();
+ for (size_t i = 0; i < n; ++i)
+ pNames[i] = aMembers[i].maName;
+
+ return true;
+}
+
+bool ScDPObject::GetMembers( sal_Int32 nDim, sal_Int32 nHier, vector<ScDPLabelData::Member>& rMembers )
+{
+ Reference< sheet::XMembersAccess > xMembersNA;
+ if (!GetMembersNA( nDim, nHier, xMembersNA ))
+ return false;
+
+ Reference<container::XIndexAccess> xMembersIA( new ScNameToIndexAccess(xMembersNA) );
+ sal_Int32 nCount = xMembersIA->getCount();
+ vector<ScDPLabelData::Member> aMembers;
+ aMembers.reserve(nCount);
+
+ for (sal_Int32 i = 0; i < nCount; ++i)
+ {
+ Reference<container::XNamed> xMember;
+ try
+ {
+ xMember = Reference<container::XNamed>(xMembersIA->getByIndex(i), UNO_QUERY);
+ }
+ catch (const container::NoSuchElementException&)
+ {
+ TOOLS_WARN_EXCEPTION("sc", "ScNameToIndexAccess getByIndex failed");
+ }
+
+ ScDPLabelData::Member aMem;
+
+ if (xMember.is())
+ aMem.maName = xMember->getName();
+
+ Reference<beans::XPropertySet> xMemProp(xMember, UNO_QUERY);
+ if (xMemProp.is())
+ {
+ aMem.mbVisible = ScUnoHelpFunctions::GetBoolProperty(xMemProp, SC_UNO_DP_ISVISIBLE);
+ aMem.mbShowDetails = ScUnoHelpFunctions::GetBoolProperty(xMemProp, SC_UNO_DP_SHOWDETAILS);
+
+ aMem.maLayoutName = ScUnoHelpFunctions::GetStringProperty(
+ xMemProp, SC_UNO_DP_LAYOUTNAME, OUString());
+ }
+
+ aMembers.push_back(aMem);
+ }
+ rMembers.swap(aMembers);
+ return true;
+}
+
+void ScDPObject::UpdateReference( UpdateRefMode eUpdateRefMode,
+ const ScRange& rRange, SCCOL nDx, SCROW nDy, SCTAB nDz )
+{
+ // Output area
+
+ SCCOL nCol1 = aOutRange.aStart.Col();
+ SCROW nRow1 = aOutRange.aStart.Row();
+ SCTAB nTab1 = aOutRange.aStart.Tab();
+ SCCOL nCol2 = aOutRange.aEnd.Col();
+ SCROW nRow2 = aOutRange.aEnd.Row();
+ SCTAB nTab2 = aOutRange.aEnd.Tab();
+
+ 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 )
+ SetOutRange( ScRange( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ) );
+
+ // sheet source data
+
+ if ( !pSheetDesc )
+ return;
+
+ const OUString& rRangeName = pSheetDesc->GetRangeName();
+ if (!rRangeName.isEmpty())
+ // Source range is a named range. No need to update.
+ return;
+
+ const ScRange& rSrcRange = pSheetDesc->GetSourceRange();
+ nCol1 = rSrcRange.aStart.Col();
+ nRow1 = rSrcRange.aStart.Row();
+ nTab1 = rSrcRange.aStart.Tab();
+ nCol2 = rSrcRange.aEnd.Col();
+ nRow2 = rSrcRange.aEnd.Row();
+ nTab2 = rSrcRange.aEnd.Tab();
+
+ 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 )
+ return;
+
+ SCCOL nDiffX = nCol1 - pSheetDesc->GetSourceRange().aStart.Col();
+ SCROW nDiffY = nRow1 - pSheetDesc->GetSourceRange().aStart.Row();
+
+ ScQueryParam aParam = pSheetDesc->GetQueryParam();
+ aParam.nCol1 = sal::static_int_cast<SCCOL>( aParam.nCol1 + nDiffX );
+ aParam.nCol2 = sal::static_int_cast<SCCOL>( aParam.nCol2 + nDiffX );
+ aParam.nRow1 += nDiffY; //TODO: used?
+ aParam.nRow2 += nDiffY; //TODO: used?
+ SCSIZE nEC = aParam.GetEntryCount();
+ for (SCSIZE i=0; i<nEC; i++)
+ if (aParam.GetEntry(i).bDoQuery)
+ aParam.GetEntry(i).nField += nDiffX;
+
+ pSheetDesc->SetQueryParam(aParam);
+ pSheetDesc->SetSourceRange(ScRange(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2));
+}
+
+bool ScDPObject::RefsEqual( const ScDPObject& r ) const
+{
+ if ( aOutRange != r.aOutRange )
+ return false;
+
+ if ( pSheetDesc && r.pSheetDesc )
+ {
+ if ( pSheetDesc->GetSourceRange() != r.pSheetDesc->GetSourceRange() )
+ return false;
+ }
+ else if ( pSheetDesc || r.pSheetDesc )
+ {
+ OSL_FAIL("RefsEqual: SheetDesc set at only one object");
+ return false;
+ }
+
+ return true;
+}
+
+void ScDPObject::WriteRefsTo( ScDPObject& r ) const
+{
+ r.SetOutRange( aOutRange );
+ if ( pSheetDesc )
+ r.SetSheetDesc( *pSheetDesc );
+}
+
+void ScDPObject::GetPositionData(const ScAddress& rPos, DataPilotTablePositionData& rPosData)
+{
+ CreateOutput();
+ pOutput->GetPositionData(rPos, rPosData);
+}
+
+bool ScDPObject::GetDataFieldPositionData(
+ const ScAddress& rPos, Sequence<sheet::DataPilotFieldFilter>& rFilters)
+{
+ CreateOutput();
+
+ vector<sheet::DataPilotFieldFilter> aFilters;
+ if (!pOutput->GetDataResultPositionData(aFilters, rPos))
+ return false;
+
+ sal_Int32 n = static_cast<sal_Int32>(aFilters.size());
+ rFilters.realloc(n);
+ auto pFilters = rFilters.getArray();
+ for (sal_Int32 i = 0; i < n; ++i)
+ pFilters[i] = aFilters[i];
+
+ return true;
+}
+
+void ScDPObject::GetDrillDownData(const ScAddress& rPos, Sequence< Sequence<Any> >& rTableData)
+{
+ CreateOutput();
+
+ Reference<sheet::XDrillDownDataSupplier> xDrillDownData(xSource, UNO_QUERY);
+ if (!xDrillDownData.is())
+ return;
+
+ Sequence<sheet::DataPilotFieldFilter> filters;
+ if (!GetDataFieldPositionData(rPos, filters))
+ return;
+
+ rTableData = xDrillDownData->getDrillDownData(filters);
+}
+
+bool ScDPObject::IsDimNameInUse(std::u16string_view rName) const
+{
+ if (!xSource.is())
+ return false;
+
+ Reference<container::XNameAccess> xDims = xSource->getDimensions();
+ const Sequence<OUString> aDimNames = xDims->getElementNames();
+ for (const OUString& rDimName : aDimNames)
+ {
+ if (rDimName.equalsIgnoreAsciiCase(rName))
+ return true;
+
+ Reference<beans::XPropertySet> xPropSet(xDims->getByName(rDimName), UNO_QUERY);
+ if (!xPropSet.is())
+ continue;
+
+ OUString aLayoutName = ScUnoHelpFunctions::GetStringProperty(
+ xPropSet, SC_UNO_DP_LAYOUTNAME, OUString());
+ if (aLayoutName.equalsIgnoreAsciiCase(rName))
+ return true;
+ }
+ return false;
+}
+
+OUString ScDPObject::GetDimName( tools::Long nDim, bool& rIsDataLayout, sal_Int32* pFlags )
+{
+ rIsDataLayout = false;
+ OUString aRet;
+
+ if ( xSource.is() )
+ {
+ uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions();
+ uno::Reference<container::XIndexAccess> xDims = new ScNameToIndexAccess( xDimsName );
+ tools::Long nDimCount = xDims->getCount();
+ if ( nDim < nDimCount )
+ {
+ uno::Reference<uno::XInterface> xIntDim(xDims->getByIndex(nDim), uno::UNO_QUERY);
+ uno::Reference<container::XNamed> xDimName( xIntDim, uno::UNO_QUERY );
+ uno::Reference<beans::XPropertySet> xDimProp( xIntDim, uno::UNO_QUERY );
+ if ( xDimName.is() && xDimProp.is() )
+ {
+ bool bData = ScUnoHelpFunctions::GetBoolProperty( xDimProp,
+ SC_UNO_DP_ISDATALAYOUT );
+ //TODO: error checking -- is "IsDataLayoutDimension" property required??
+
+ OUString aName;
+ try
+ {
+ aName = xDimName->getName();
+ }
+ catch(uno::Exception&)
+ {
+ }
+ if ( bData )
+ rIsDataLayout = true;
+ else
+ aRet = aName;
+
+ if (pFlags)
+ *pFlags = ScUnoHelpFunctions::GetLongProperty( xDimProp,
+ SC_UNO_DP_FLAGS );
+ }
+ }
+ }
+ else if (ScDPTableData* pData = GetTableData())
+ {
+ aRet = pData->getDimensionName(nDim);
+ rIsDataLayout = pData->getIsDataLayoutDimension(nDim);
+ }
+
+ return aRet;
+}
+
+bool ScDPObject::IsDuplicated( tools::Long nDim )
+{
+ bool bDuplicated = false;
+ if ( xSource.is() )
+ {
+ uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions();
+ uno::Reference<container::XIndexAccess> xDims = new ScNameToIndexAccess( xDimsName );
+ tools::Long nDimCount = xDims->getCount();
+ if ( nDim < nDimCount )
+ {
+ uno::Reference<beans::XPropertySet> xDimProp(xDims->getByIndex(nDim), uno::UNO_QUERY);
+ if ( xDimProp.is() )
+ {
+ try
+ {
+ uno::Any aOrigAny = xDimProp->getPropertyValue( SC_UNO_DP_ORIGINAL );
+ uno::Reference<uno::XInterface> xIntOrig;
+ if ( (aOrigAny >>= xIntOrig) && xIntOrig.is() )
+ bDuplicated = true;
+ }
+ catch(uno::Exception&)
+ {
+ }
+ }
+ }
+ }
+ return bDuplicated;
+}
+
+tools::Long ScDPObject::GetDimCount()
+{
+ tools::Long nRet = 0;
+ if ( xSource.is() )
+ {
+ try
+ {
+ uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions();
+ if ( xDimsName.is() )
+ nRet = xDimsName->getElementNames().getLength();
+ }
+ catch(uno::Exception&)
+ {
+ }
+ }
+ return nRet;
+}
+
+void ScDPObject::GetHeaderPositionData(const ScAddress& rPos, DataPilotTableHeaderData& rData)
+{
+ CreateOutput(); // create xSource and pOutput if not already done
+
+ // Reset member values to invalid state.
+ rData.Dimension = rData.Hierarchy = rData.Level = -1;
+ rData.Flags = 0;
+
+ DataPilotTablePositionData aPosData;
+ pOutput->GetPositionData(rPos, aPosData);
+ const sal_Int32 nPosType = aPosData.PositionType;
+ if (nPosType == css::sheet::DataPilotTablePositionType::COLUMN_HEADER || nPosType == css::sheet::DataPilotTablePositionType::ROW_HEADER)
+ aPosData.PositionData >>= rData;
+}
+
+namespace {
+
+class FindByName
+{
+ OUString maName; // must be all uppercase.
+public:
+ explicit FindByName(const OUString& rName) : maName(rName) {}
+ bool operator() (const ScDPSaveDimension* pDim) const
+ {
+ // Layout name takes precedence.
+ const std::optional<OUString> & pLayoutName = pDim->GetLayoutName();
+ if (pLayoutName && ScGlobal::getCharClass().uppercase(*pLayoutName) == maName)
+ return true;
+
+ ScGeneralFunction eGenFunc = pDim->GetFunction();
+ ScSubTotalFunc eFunc = ScDPUtil::toSubTotalFunc(eGenFunc);
+ OUString aSrcName = ScDPUtil::getSourceDimensionName(pDim->GetName());
+ OUString aFuncName = ScDPUtil::getDisplayedMeasureName(aSrcName, eFunc);
+ if (maName == ScGlobal::getCharClass().uppercase(aFuncName))
+ return true;
+
+ return maName == ScGlobal::getCharClass().uppercase(aSrcName);
+ }
+};
+
+class LessByDimOrder
+{
+ const ScDPSaveData::DimOrderType& mrDimOrder;
+
+public:
+ explicit LessByDimOrder(const ScDPSaveData::DimOrderType& rDimOrder) : mrDimOrder(rDimOrder) {}
+
+ bool operator() (const sheet::DataPilotFieldFilter& r1, const sheet::DataPilotFieldFilter& r2) const
+ {
+ size_t nRank1 = mrDimOrder.size();
+ size_t nRank2 = mrDimOrder.size();
+ ScDPSaveData::DimOrderType::const_iterator it1 = mrDimOrder.find(
+ ScGlobal::getCharClass().uppercase(r1.FieldName));
+ if (it1 != mrDimOrder.end())
+ nRank1 = it1->second;
+
+ ScDPSaveData::DimOrderType::const_iterator it2 = mrDimOrder.find(
+ ScGlobal::getCharClass().uppercase(r2.FieldName));
+ if (it2 != mrDimOrder.end())
+ nRank2 = it2->second;
+
+ return nRank1 < nRank2;
+ }
+};
+
+}
+
+double ScDPObject::GetPivotData(const OUString& rDataFieldName, std::vector<sheet::DataPilotFieldFilter>& rFilters)
+{
+ if (!mbEnableGetPivotData)
+ return std::numeric_limits<double>::quiet_NaN();
+
+ CreateObjects();
+
+ std::vector<const ScDPSaveDimension*> aDataDims;
+ pSaveData->GetAllDimensionsByOrientation(sheet::DataPilotFieldOrientation_DATA, aDataDims);
+ if (aDataDims.empty())
+ return std::numeric_limits<double>::quiet_NaN();
+
+ std::vector<const ScDPSaveDimension*>::iterator it = std::find_if(
+ aDataDims.begin(), aDataDims.end(),
+ FindByName(ScGlobal::getCharClass().uppercase(rDataFieldName)));
+
+ if (it == aDataDims.end())
+ return std::numeric_limits<double>::quiet_NaN();
+
+ size_t nDataIndex = std::distance(aDataDims.begin(), it);
+
+ uno::Reference<sheet::XDataPilotResults> xDPResults(xSource, uno::UNO_QUERY);
+ if (!xDPResults.is())
+ return std::numeric_limits<double>::quiet_NaN();
+
+ // Dimensions must be sorted in order of appearance, and row dimensions
+ // must come before column dimensions.
+ std::sort(rFilters.begin(), rFilters.end(), LessByDimOrder(pSaveData->GetDimensionSortOrder()));
+
+ size_t n = rFilters.size();
+ uno::Sequence<sheet::DataPilotFieldFilter> aFilters(n);
+ auto aFiltersRange = asNonConstRange(aFilters);
+ for (size_t i = 0; i < n; ++i)
+ aFiltersRange[i] = rFilters[i];
+
+ uno::Sequence<double> aRes = xDPResults->getFilteredResults(aFilters);
+ if (nDataIndex >= o3tl::make_unsigned(aRes.getLength()))
+ return std::numeric_limits<double>::quiet_NaN();
+
+ return aRes[nDataIndex];
+}
+
+bool ScDPObject::IsFilterButton( const ScAddress& rPos )
+{
+ CreateOutput(); // create xSource and pOutput if not already done
+
+ return pOutput->IsFilterButton( rPos );
+}
+
+tools::Long ScDPObject::GetHeaderDim( const ScAddress& rPos, sheet::DataPilotFieldOrientation& rOrient )
+{
+ CreateOutput(); // create xSource and pOutput if not already done
+
+ return pOutput->GetHeaderDim( rPos, rOrient );
+}
+
+bool ScDPObject::GetHeaderDrag( const ScAddress& rPos, bool bMouseLeft, bool bMouseTop, tools::Long nDragDim,
+ tools::Rectangle& rPosRect, sheet::DataPilotFieldOrientation& rOrient, tools::Long& rDimPos )
+{
+ CreateOutput(); // create xSource and pOutput if not already done
+
+ return pOutput->GetHeaderDrag( rPos, bMouseLeft, bMouseTop, nDragDim, rPosRect, rOrient, rDimPos );
+}
+
+void ScDPObject::GetMemberResultNames(ScDPUniqueStringSet& rNames, tools::Long nDimension)
+{
+ CreateOutput(); // create xSource and pOutput if not already done
+
+ pOutput->GetMemberResultNames(rNames, nDimension); // used only with table data -> level not needed
+}
+
+OUString ScDPObject::GetFormattedString(std::u16string_view rDimName, const double fValue)
+{
+ ScDPTableData* pTableData = GetTableData();
+ if(!pTableData)
+ return OUString();
+
+ tools::Long nDim;
+ for (nDim = 0; nDim < pTableData->GetColumnCount(); ++nDim)
+ {
+ if(rDimName == pTableData->getDimensionName(nDim))
+ break;
+ }
+ ScDPItemData aItemData;
+ aItemData.SetValue(fValue);
+ return GetTableData()->GetFormattedString(nDim, aItemData, false);
+}
+
+
+namespace {
+
+bool dequote( const OUString& rSource, sal_Int32 nStartPos, sal_Int32& rEndPos, OUString& rResult )
+{
+ // nStartPos has to point to opening quote
+
+ const sal_Unicode cQuote = '\'';
+
+ if (rSource[nStartPos] == cQuote)
+ {
+ OUStringBuffer aBuffer;
+ sal_Int32 nPos = nStartPos + 1;
+ const sal_Int32 nLen = rSource.getLength();
+
+ while ( nPos < nLen )
+ {
+ const sal_Unicode cNext = rSource[nPos];
+ if ( cNext == cQuote )
+ {
+ if (nPos+1 < nLen && rSource[nPos+1] == cQuote)
+ {
+ // double quote is used for an embedded quote
+ aBuffer.append( cNext ); // append one quote
+ ++nPos; // skip the next one
+ }
+ else
+ {
+ // end of quoted string
+ rResult = aBuffer.makeStringAndClear();
+ rEndPos = nPos + 1; // behind closing quote
+ return true;
+ }
+ }
+ else
+ aBuffer.append( cNext );
+
+ ++nPos;
+ }
+ // no closing quote before the end of the string -> error (bRet still false)
+ }
+
+ return false;
+}
+
+struct ScGetPivotDataFunctionEntry
+{
+ const char* pName;
+ sal_Int16 eFunc;
+};
+
+bool parseFunction( const OUString& rList, sal_Int32 nStartPos, sal_Int32& rEndPos, sal_Int16& rFunc )
+{
+ static const ScGetPivotDataFunctionEntry aFunctions[] =
+ {
+ // our names
+ { "Sum", sheet::GeneralFunction2::SUM },
+ { "Count", sheet::GeneralFunction2::COUNT },
+ { "Average", sheet::GeneralFunction2::AVERAGE },
+ { "Max", sheet::GeneralFunction2::MAX },
+ { "Min", sheet::GeneralFunction2::MIN },
+ { "Product", sheet::GeneralFunction2::PRODUCT },
+ { "CountNums", sheet::GeneralFunction2::COUNTNUMS },
+ { "StDev", sheet::GeneralFunction2::STDEV },
+ { "StDevp", sheet::GeneralFunction2::STDEVP },
+ { "Var", sheet::GeneralFunction2::VAR },
+ { "VarP", sheet::GeneralFunction2::VARP },
+ // compatibility names
+ { "Count Nums", sheet::GeneralFunction2::COUNTNUMS },
+ { "StdDev", sheet::GeneralFunction2::STDEV },
+ { "StdDevp", sheet::GeneralFunction2::STDEVP }
+ };
+
+ const sal_Int32 nListLen = rList.getLength();
+ while (nStartPos < nListLen && rList[nStartPos] == ' ')
+ ++nStartPos;
+
+ bool bParsed = false;
+ bool bFound = false;
+ OUString aFuncStr;
+ sal_Int32 nFuncEnd = 0;
+ if (nStartPos < nListLen && rList[nStartPos] == '\'')
+ bParsed = dequote( rList, nStartPos, nFuncEnd, aFuncStr );
+ else
+ {
+ nFuncEnd = rList.indexOf(']', nStartPos);
+ if (nFuncEnd >= 0)
+ {
+ aFuncStr = rList.copy(nStartPos, nFuncEnd - nStartPos);
+ bParsed = true;
+ }
+ }
+
+ if ( bParsed )
+ {
+ aFuncStr = comphelper::string::strip(aFuncStr, ' ');
+
+ const sal_Int32 nFuncCount = SAL_N_ELEMENTS(aFunctions);
+ for ( sal_Int32 nFunc=0; nFunc<nFuncCount && !bFound; nFunc++ )
+ {
+ if (aFuncStr.equalsIgnoreAsciiCaseAscii(aFunctions[nFunc].pName))
+ {
+ rFunc = aFunctions[nFunc].eFunc;
+ bFound = true;
+
+ while (nFuncEnd < nListLen && rList[nFuncEnd] == ' ')
+ ++nFuncEnd;
+ rEndPos = nFuncEnd;
+ }
+ }
+ }
+
+ return bFound;
+}
+
+bool extractAtStart( const OUString& rList, sal_Int32& rMatched, bool bAllowBracket, sal_Int16* pFunc,
+ OUString& rDequoted )
+{
+ sal_Int32 nMatchList = 0;
+ sal_Unicode cFirst = rList[0];
+ bool bParsed = false;
+ if ( cFirst == '\'' || cFirst == '[' )
+ {
+ // quoted string or string in brackets must match completely
+
+ OUString aDequoted;
+ sal_Int32 nQuoteEnd = 0;
+
+ if ( cFirst == '\'' )
+ bParsed = dequote( rList, 0, nQuoteEnd, aDequoted );
+ else if ( cFirst == '[' )
+ {
+ // skip spaces after the opening bracket
+
+ sal_Int32 nStartPos = 1;
+ const sal_Int32 nListLen = rList.getLength();
+ while (nStartPos < nListLen && rList[nStartPos] == ' ')
+ ++nStartPos;
+
+ if (nStartPos < nListLen && rList[nStartPos] == '\'') // quoted within the brackets?
+ {
+ if ( dequote( rList, nStartPos, nQuoteEnd, aDequoted ) )
+ {
+ // after the quoted string, there must be the closing bracket, optionally preceded by spaces,
+ // and/or a function name
+ while (nQuoteEnd < nListLen && rList[nQuoteEnd] == ' ')
+ ++nQuoteEnd;
+
+ // semicolon separates function name
+ if (nQuoteEnd < nListLen && rList[nQuoteEnd] == ';' && pFunc)
+ {
+ sal_Int32 nFuncEnd = 0;
+ if ( parseFunction( rList, nQuoteEnd + 1, nFuncEnd, *pFunc ) )
+ nQuoteEnd = nFuncEnd;
+ }
+ if (nQuoteEnd < nListLen && rList[nQuoteEnd] == ']')
+ {
+ ++nQuoteEnd; // include the closing bracket for the matched length
+ bParsed = true;
+ }
+ }
+ }
+ else
+ {
+ // implicit quoting to the closing bracket
+
+ sal_Int32 nClosePos = rList.indexOf(']', nStartPos);
+ if (nClosePos >= 0)
+ {
+ sal_Int32 nNameEnd = nClosePos;
+ sal_Int32 nSemiPos = rList.indexOf(';', nStartPos);
+ if (nSemiPos >= 0 && nSemiPos < nClosePos && pFunc)
+ {
+ sal_Int32 nFuncEnd = 0;
+ if (parseFunction(rList, nSemiPos+1, nFuncEnd, *pFunc))
+ nNameEnd = nSemiPos;
+ }
+
+ aDequoted = rList.copy(nStartPos, nNameEnd - nStartPos);
+ // spaces before the closing bracket or semicolon
+ aDequoted = comphelper::string::stripEnd(aDequoted, ' ');
+ nQuoteEnd = nClosePos + 1;
+ bParsed = true;
+ }
+ }
+ }
+
+ if ( bParsed )
+ {
+ nMatchList = nQuoteEnd; // match count in the list string, including quotes
+ rDequoted = aDequoted;
+ }
+ }
+
+ if (bParsed)
+ {
+ // look for following space or end of string
+
+ bool bValid = false;
+ if ( sal::static_int_cast<sal_Int32>(nMatchList) >= rList.getLength() )
+ bValid = true;
+ else
+ {
+ sal_Unicode cNext = rList[nMatchList];
+ if ( cNext == ' ' || ( bAllowBracket && cNext == '[' ) )
+ bValid = true;
+ }
+
+ if ( bValid )
+ {
+ rMatched = nMatchList;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool isAtStart(
+ const OUString& rList, const OUString& rSearch, sal_Int32& rMatched,
+ bool bAllowBracket, sal_Int16* pFunc )
+{
+ sal_Int32 nMatchList = 0;
+ sal_Int32 nMatchSearch = 0;
+ sal_Unicode cFirst = rList[0];
+ if ( cFirst == '\'' || cFirst == '[' )
+ {
+ OUString aDequoted;
+ bool bParsed = extractAtStart( rList, rMatched, bAllowBracket, pFunc, aDequoted);
+ if ( bParsed && ScGlobal::GetTransliteration().isEqual( aDequoted, rSearch ) )
+ {
+ nMatchList = rMatched; // match count in the list string, including quotes
+ nMatchSearch = rSearch.getLength();
+ }
+ }
+ else
+ {
+ // otherwise look for search string at the start of rList
+ ScGlobal::GetTransliteration().equals(
+ rList, 0, rList.getLength(), nMatchList, rSearch, 0, rSearch.getLength(), nMatchSearch);
+ }
+
+ if (nMatchSearch == rSearch.getLength())
+ {
+ // search string is at start of rList - look for following space or end of string
+
+ bool bValid = false;
+ if ( sal::static_int_cast<sal_Int32>(nMatchList) >= rList.getLength() )
+ bValid = true;
+ else
+ {
+ sal_Unicode cNext = rList[nMatchList];
+ if ( cNext == ' ' || ( bAllowBracket && cNext == '[' ) )
+ bValid = true;
+ }
+
+ if ( bValid )
+ {
+ rMatched = nMatchList;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // anonymous namespace
+
+bool ScDPObject::ParseFilters(
+ OUString& rDataFieldName,
+ std::vector<sheet::DataPilotFieldFilter>& rFilters,
+ std::vector<sal_Int16>& rFilterFuncs, std::u16string_view rFilterList )
+{
+ // parse the string rFilterList into parameters for GetPivotData
+
+ CreateObjects(); // create xSource if not already done
+
+ std::vector<OUString> aDataNames; // data fields (source name)
+ std::vector<OUString> aGivenNames; // data fields (compound name)
+ std::vector<OUString> aFieldNames; // column/row/data fields
+ std::vector< uno::Sequence<OUString> > aFieldValueNames;
+ std::vector< uno::Sequence<OUString> > aFieldValues;
+
+ // get all the field and item names
+
+ uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions();
+ uno::Reference<container::XIndexAccess> xIntDims = new ScNameToIndexAccess( xDimsName );
+ sal_Int32 nDimCount = xIntDims->getCount();
+ for ( sal_Int32 nDim = 0; nDim<nDimCount; nDim++ )
+ {
+ uno::Reference<uno::XInterface> xIntDim(xIntDims->getByIndex(nDim), uno::UNO_QUERY);
+ uno::Reference<container::XNamed> xDim( xIntDim, uno::UNO_QUERY );
+ uno::Reference<beans::XPropertySet> xDimProp( xDim, uno::UNO_QUERY );
+ uno::Reference<sheet::XHierarchiesSupplier> xDimSupp( xDim, uno::UNO_QUERY );
+ bool bDataLayout = ScUnoHelpFunctions::GetBoolProperty( xDimProp,
+ SC_UNO_DP_ISDATALAYOUT );
+ sheet::DataPilotFieldOrientation nOrient = ScUnoHelpFunctions::GetEnumProperty(
+ xDimProp, SC_UNO_DP_ORIENTATION,
+ sheet::DataPilotFieldOrientation_HIDDEN );
+ if ( !bDataLayout )
+ {
+ if ( nOrient == sheet::DataPilotFieldOrientation_DATA )
+ {
+ OUString aSourceName;
+ OUString aGivenName;
+ ScDPOutput::GetDataDimensionNames( aSourceName, aGivenName, xIntDim );
+ aDataNames.push_back( aSourceName );
+ aGivenNames.push_back( aGivenName );
+ }
+ else if ( nOrient != sheet::DataPilotFieldOrientation_HIDDEN )
+ {
+ // get level names, as in ScDPOutput
+
+ uno::Reference<container::XIndexAccess> xHiers = new ScNameToIndexAccess( xDimSupp->getHierarchies() );
+ sal_Int32 nHierarchy = ScUnoHelpFunctions::GetLongProperty( xDimProp,
+ SC_UNO_DP_USEDHIERARCHY );
+ if ( nHierarchy >= xHiers->getCount() )
+ nHierarchy = 0;
+
+ uno::Reference<sheet::XLevelsSupplier> xHierSupp(xHiers->getByIndex(nHierarchy),
+ uno::UNO_QUERY);
+ if ( xHierSupp.is() )
+ {
+ uno::Reference<container::XIndexAccess> xLevels = new ScNameToIndexAccess( xHierSupp->getLevels() );
+ sal_Int32 nLevCount = xLevels->getCount();
+ for (sal_Int32 nLev=0; nLev<nLevCount; nLev++)
+ {
+ uno::Reference<uno::XInterface> xLevel(xLevels->getByIndex(nLev),
+ uno::UNO_QUERY);
+ uno::Reference<container::XNamed> xLevNam( xLevel, uno::UNO_QUERY );
+ uno::Reference<sheet::XMembersSupplier> xLevSupp( xLevel, uno::UNO_QUERY );
+ if ( xLevNam.is() && xLevSupp.is() )
+ {
+ uno::Reference<sheet::XMembersAccess> xMembers = xLevSupp->getMembers();
+
+ OUString aFieldName( xLevNam->getName() );
+ // getElementNames() and getLocaleIndependentElementNames()
+ // must be consecutive calls to obtain strings in matching order.
+ uno::Sequence<OUString> aMemberValueNames( xMembers->getElementNames() );
+ uno::Sequence<OUString> aMemberValues( xMembers->getLocaleIndependentElementNames() );
+
+ aFieldNames.push_back( aFieldName );
+ aFieldValueNames.push_back( aMemberValueNames );
+ aFieldValues.push_back( aMemberValues );
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // compare and build filters
+
+ SCSIZE nDataFields = aDataNames.size();
+ SCSIZE nFieldCount = aFieldNames.size();
+ OSL_ENSURE( aGivenNames.size() == nDataFields && aFieldValueNames.size() == nFieldCount &&
+ aFieldValues.size() == nFieldCount, "wrong count" );
+
+ bool bError = false;
+ bool bHasData = false;
+ OUString aRemaining(comphelper::string::strip(rFilterList, ' '));
+ while (!aRemaining.isEmpty() && !bError)
+ {
+ bool bUsed = false;
+
+ // look for data field name
+
+ for ( SCSIZE nDataPos=0; nDataPos<nDataFields && !bUsed; nDataPos++ )
+ {
+ OUString aFound;
+ sal_Int32 nMatched = 0;
+ if (isAtStart(aRemaining, aDataNames[nDataPos], nMatched, false, nullptr))
+ aFound = aDataNames[nDataPos];
+ else if (isAtStart(aRemaining, aGivenNames[nDataPos], nMatched, false, nullptr))
+ aFound = aGivenNames[nDataPos];
+
+ if (!aFound.isEmpty())
+ {
+ rDataFieldName = aFound;
+ aRemaining = aRemaining.copy(nMatched);
+ bHasData = true;
+ bUsed = true;
+ }
+ }
+
+ // look for field name
+
+ OUString aSpecField;
+ bool bHasFieldName = false;
+ if ( !bUsed )
+ {
+ sal_Int32 nMatched = 0;
+ for ( SCSIZE nField=0; nField<nFieldCount && !bHasFieldName; nField++ )
+ {
+ if (isAtStart(aRemaining, aFieldNames[nField], nMatched, true, nullptr))
+ {
+ aSpecField = aFieldNames[nField];
+ aRemaining = aRemaining.copy(nMatched);
+ aRemaining = comphelper::string::stripStart(aRemaining, ' ');
+
+ // field name has to be followed by item name in brackets
+ if (aRemaining.startsWith("["))
+ {
+ bHasFieldName = true;
+ // bUsed remains false - still need the item
+ }
+ else
+ {
+ bUsed = true;
+ bError = true;
+ }
+ }
+ }
+ }
+
+ // look for field item
+
+ if ( !bUsed )
+ {
+ bool bItemFound = false;
+ sal_Int32 nMatched = 0;
+ OUString aFoundName;
+ OUString aFoundValueName;
+ OUString aFoundValue;
+ sal_Int16 eFunc = sheet::GeneralFunction2::NONE;
+ sal_Int16 eFoundFunc = sheet::GeneralFunction2::NONE;
+
+ OUString aQueryValueName;
+ const bool bHasQuery = extractAtStart( aRemaining, nMatched, false, &eFunc, aQueryValueName);
+
+ OUString aQueryValue = aQueryValueName;
+ if (mpTableData)
+ {
+ SvNumberFormatter* pFormatter = mpTableData->GetCacheTable().getCache().GetNumberFormatter();
+ if (pFormatter)
+ {
+ // Parse possible number from aQueryValueName and format
+ // locale independent as aQueryValue.
+ sal_uInt32 nNumFormat = 0;
+ double fValue;
+ if (pFormatter->IsNumberFormat( aQueryValueName, nNumFormat, fValue))
+ aQueryValue = ScDPCache::GetLocaleIndependentFormattedString( fValue, *pFormatter, nNumFormat);
+ }
+ }
+
+ for ( SCSIZE nField=0; nField<nFieldCount; nField++ )
+ {
+ // If a field name is given, look in that field only, otherwise in all fields.
+ // aSpecField is initialized from aFieldNames array, so exact comparison can be used.
+ if ( !bHasFieldName || aFieldNames[nField] == aSpecField )
+ {
+ const uno::Sequence<OUString>& rItemNames = aFieldValueNames[nField];
+ const uno::Sequence<OUString>& rItemValues = aFieldValues[nField];
+ sal_Int32 nItemCount = rItemNames.getLength();
+ assert(nItemCount == rItemValues.getLength());
+ const OUString* pItemNamesArr = rItemNames.getConstArray();
+ const OUString* pItemValuesArr = rItemValues.getConstArray();
+ for ( sal_Int32 nItem=0; nItem<nItemCount; nItem++ )
+ {
+ bool bThisItemFound;
+ if (bHasQuery)
+ {
+ // First check given value name against both.
+ bThisItemFound = ScGlobal::GetTransliteration().isEqual(
+ aQueryValueName, pItemNamesArr[nItem]);
+ if (!bThisItemFound && pItemValuesArr[nItem] != pItemNamesArr[nItem])
+ bThisItemFound = ScGlobal::GetTransliteration().isEqual(
+ aQueryValueName, pItemValuesArr[nItem]);
+ if (!bThisItemFound && aQueryValueName != aQueryValue)
+ {
+ // Second check locale independent value
+ // against both.
+ /* TODO: or check only value string against
+ * value string, not against the value name? */
+ bThisItemFound = ScGlobal::GetTransliteration().isEqual(
+ aQueryValue, pItemNamesArr[nItem]);
+ if (!bThisItemFound && pItemValuesArr[nItem] != pItemNamesArr[nItem])
+ bThisItemFound = ScGlobal::GetTransliteration().isEqual(
+ aQueryValue, pItemValuesArr[nItem]);
+ }
+ }
+ else
+ {
+ bThisItemFound = isAtStart( aRemaining, pItemNamesArr[nItem], nMatched, false, &eFunc );
+ if (!bThisItemFound && pItemValuesArr[nItem] != pItemNamesArr[nItem])
+ bThisItemFound = isAtStart( aRemaining, pItemValuesArr[nItem], nMatched, false, &eFunc );
+ /* TODO: this checks only the given value name,
+ * check also locale independent value. But we'd
+ * have to do that in each iteration of the loop
+ * inside isAtStart() since a query could not be
+ * extracted and a match could be on the passed
+ * item value name string or item value string
+ * starting at aRemaining. */
+ }
+ if (bThisItemFound)
+ {
+ if ( bItemFound )
+ bError = true; // duplicate (also across fields)
+ else
+ {
+ aFoundName = aFieldNames[nField];
+ aFoundValueName = pItemNamesArr[nItem];
+ aFoundValue = pItemValuesArr[nItem];
+ eFoundFunc = eFunc;
+ bItemFound = true;
+ bUsed = true;
+ }
+ }
+ }
+ }
+ }
+
+ if ( bItemFound && !bError )
+ {
+ sheet::DataPilotFieldFilter aField;
+ aField.FieldName = aFoundName;
+ aField.MatchValueName = aFoundValueName;
+ aField.MatchValue = aFoundValue;
+ rFilters.push_back(aField);
+ rFilterFuncs.push_back(eFoundFunc);
+ aRemaining = aRemaining.copy(nMatched);
+ }
+ }
+
+ if ( !bUsed )
+ bError = true;
+
+ // remove any number of spaces between entries
+ aRemaining = comphelper::string::stripStart(aRemaining, ' ');
+ }
+
+ if ( !bError && !bHasData && aDataNames.size() == 1 )
+ {
+ // if there's only one data field, its name need not be specified
+ rDataFieldName = aDataNames[0];
+ bHasData = true;
+ }
+
+ return bHasData && !bError;
+}
+
+void ScDPObject::ToggleDetails(const DataPilotTableHeaderData& rElemDesc, ScDPObject* pDestObj)
+{
+ CreateObjects(); // create xSource if not already done
+
+ // find dimension name
+
+ uno::Reference<container::XNamed> xDim;
+ uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions();
+ uno::Reference<container::XIndexAccess> xIntDims = new ScNameToIndexAccess( xDimsName );
+ tools::Long nIntCount = xIntDims->getCount();
+ if ( rElemDesc.Dimension < nIntCount )
+ {
+ xDim.set(xIntDims->getByIndex(rElemDesc.Dimension), uno::UNO_QUERY);
+ }
+ OSL_ENSURE( xDim.is(), "dimension not found" );
+ if ( !xDim.is() ) return;
+ OUString aDimName = xDim->getName();
+
+ uno::Reference<beans::XPropertySet> xDimProp( xDim, uno::UNO_QUERY );
+ bool bDataLayout = ScUnoHelpFunctions::GetBoolProperty( xDimProp,
+ SC_UNO_DP_ISDATALAYOUT );
+ if (bDataLayout)
+ {
+ // the elements of the data layout dimension can't be found by their names
+ // -> don't change anything
+ return;
+ }
+
+ // query old state
+
+ tools::Long nHierCount = 0;
+ uno::Reference<container::XIndexAccess> xHiers;
+ uno::Reference<sheet::XHierarchiesSupplier> xHierSupp( xDim, uno::UNO_QUERY );
+ if ( xHierSupp.is() )
+ {
+ uno::Reference<container::XNameAccess> xHiersName = xHierSupp->getHierarchies();
+ xHiers = new ScNameToIndexAccess( xHiersName );
+ nHierCount = xHiers->getCount();
+ }
+ uno::Reference<uno::XInterface> xHier;
+ if ( rElemDesc.Hierarchy < nHierCount )
+ xHier.set(xHiers->getByIndex(rElemDesc.Hierarchy), uno::UNO_QUERY);
+ OSL_ENSURE( xHier.is(), "hierarchy not found" );
+ if ( !xHier.is() ) return;
+
+ tools::Long nLevCount = 0;
+ uno::Reference<container::XIndexAccess> xLevels;
+ uno::Reference<sheet::XLevelsSupplier> xLevSupp( xHier, uno::UNO_QUERY );
+ if ( xLevSupp.is() )
+ {
+ uno::Reference<container::XNameAccess> xLevsName = xLevSupp->getLevels();
+ xLevels = new ScNameToIndexAccess( xLevsName );
+ nLevCount = xLevels->getCount();
+ }
+ uno::Reference<uno::XInterface> xLevel;
+ if ( rElemDesc.Level < nLevCount )
+ xLevel.set(xLevels->getByIndex(rElemDesc.Level), uno::UNO_QUERY);
+ OSL_ENSURE( xLevel.is(), "level not found" );
+ if ( !xLevel.is() ) return;
+
+ uno::Reference<sheet::XMembersAccess> xMembers;
+ uno::Reference<sheet::XMembersSupplier> xMbrSupp( xLevel, uno::UNO_QUERY );
+ if ( xMbrSupp.is() )
+ xMembers = xMbrSupp->getMembers();
+
+ bool bFound = false;
+ bool bShowDetails = true;
+
+ if ( xMembers.is() )
+ {
+ if ( xMembers->hasByName(rElemDesc.MemberName) )
+ {
+ uno::Reference<beans::XPropertySet> xMbrProp(xMembers->getByName(rElemDesc.MemberName),
+ uno::UNO_QUERY);
+ if ( xMbrProp.is() )
+ {
+ bShowDetails = ScUnoHelpFunctions::GetBoolProperty( xMbrProp,
+ SC_UNO_DP_SHOWDETAILS );
+ //TODO: don't set bFound if property is unknown?
+ bFound = true;
+ }
+ }
+ }
+
+ OSL_ENSURE( bFound, "member not found" );
+
+ //TODO: use Hierarchy and Level in SaveData !!!!
+
+ // modify pDestObj if set, this object otherwise
+ ScDPSaveData* pModifyData = pDestObj ? ( pDestObj->pSaveData.get() ) : pSaveData.get();
+ OSL_ENSURE( pModifyData, "no data?" );
+ if ( pModifyData )
+ {
+ const OUString aName = rElemDesc.MemberName;
+ pModifyData->GetDimensionByName(aDimName)->
+ GetMemberByName(aName)->SetShowDetails( !bShowDetails ); // toggle
+
+ if ( pDestObj )
+ pDestObj->InvalidateData(); // re-init source from SaveData
+ else
+ InvalidateData(); // re-init source from SaveData
+ }
+}
+
+static PivotFunc lcl_FirstSubTotal( const uno::Reference<beans::XPropertySet>& xDimProp ) // PIVOT_FUNC mask
+{
+ uno::Reference<sheet::XHierarchiesSupplier> xDimSupp( xDimProp, uno::UNO_QUERY );
+ if ( xDimProp.is() && xDimSupp.is() )
+ {
+ uno::Reference<container::XIndexAccess> xHiers = new ScNameToIndexAccess( xDimSupp->getHierarchies() );
+ tools::Long nHierarchy = ScUnoHelpFunctions::GetLongProperty( xDimProp,
+ SC_UNO_DP_USEDHIERARCHY );
+ if ( nHierarchy >= xHiers->getCount() )
+ nHierarchy = 0;
+
+ uno::Reference<sheet::XLevelsSupplier> xHierSupp(xHiers->getByIndex(nHierarchy),
+ uno::UNO_QUERY);
+ if ( xHierSupp.is() )
+ {
+ uno::Reference<container::XIndexAccess> xLevels = new ScNameToIndexAccess( xHierSupp->getLevels() );
+ uno::Reference<uno::XInterface> xLevel(xLevels->getByIndex(0), uno::UNO_QUERY);
+ uno::Reference<beans::XPropertySet> xLevProp( xLevel, uno::UNO_QUERY );
+ if ( xLevProp.is() )
+ {
+ uno::Any aSubAny;
+ try
+ {
+ aSubAny = xLevProp->getPropertyValue( SC_UNO_DP_SUBTOTAL2 );
+ }
+ catch(uno::Exception&)
+ {
+ }
+ uno::Sequence<sal_Int16> aSeq;
+ if ( aSubAny >>= aSeq )
+ {
+ PivotFunc nMask = PivotFunc::NONE;
+ for (const sal_Int16 nElem : std::as_const(aSeq))
+ nMask |= ScDataPilotConversion::FunctionBit(nElem);
+ return nMask;
+ }
+ }
+ }
+ }
+
+ OSL_FAIL("FirstSubTotal: NULL");
+ return PivotFunc::NONE;
+}
+
+namespace {
+
+class FindByColumn
+{
+ SCCOL mnCol;
+ PivotFunc mnMask;
+public:
+ FindByColumn(SCCOL nCol, PivotFunc nMask) : mnCol(nCol), mnMask(nMask) {}
+ bool operator() (const ScPivotField& r) const
+ {
+ return r.nCol == mnCol && r.nFuncMask == mnMask;
+ }
+};
+
+}
+
+static void lcl_FillOldFields( ScPivotFieldVector& rFields,
+ const uno::Reference<sheet::XDimensionsSupplier>& xSource,
+ sheet::DataPilotFieldOrientation nOrient, bool bAddData )
+{
+ ScPivotFieldVector aFields;
+
+ bool bDataFound = false;
+
+ //TODO: merge multiple occurrences (data field with different functions)
+ //TODO: force data field in one dimension
+
+ vector<tools::Long> aPos;
+
+ uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions();
+ uno::Reference<container::XIndexAccess> xDims = new ScNameToIndexAccess( xDimsName );
+ tools::Long nDimCount = xDims->getCount();
+ for (tools::Long nDim = 0; nDim < nDimCount; ++nDim)
+ {
+ // dimension properties
+ uno::Reference<beans::XPropertySet> xDimProp(xDims->getByIndex(nDim), uno::UNO_QUERY);
+
+ // dimension orientation, hidden by default.
+ sheet::DataPilotFieldOrientation nDimOrient = ScUnoHelpFunctions::GetEnumProperty(
+ xDimProp, SC_UNO_DP_ORIENTATION,
+ sheet::DataPilotFieldOrientation_HIDDEN );
+
+ if ( xDimProp.is() && nDimOrient == nOrient )
+ {
+ // Let's take this dimension.
+
+ // function mask.
+ PivotFunc nMask = PivotFunc::NONE;
+ if ( nOrient == sheet::DataPilotFieldOrientation_DATA )
+ {
+ sal_Int16 eFunc = ScUnoHelpFunctions::GetShortProperty(
+ xDimProp, SC_UNO_DP_FUNCTION2,
+ sheet::GeneralFunction2::NONE );
+ if ( eFunc == sheet::GeneralFunction2::AUTO )
+ {
+ //TODO: test for numeric data
+ eFunc = sheet::GeneralFunction2::SUM;
+ }
+ nMask = ScDataPilotConversion::FunctionBit(eFunc);
+ }
+ else
+ nMask = lcl_FirstSubTotal( xDimProp ); // from first hierarchy
+
+ // is this data layout dimension?
+ bool bDataLayout = ScUnoHelpFunctions::GetBoolProperty(
+ xDimProp, SC_UNO_DP_ISDATALAYOUT);
+
+ // is this dimension cloned?
+ tools::Long nDupSource = -1;
+ try
+ {
+ uno::Any aOrigAny = xDimProp->getPropertyValue(SC_UNO_DP_ORIGINAL_POS);
+ sal_Int32 nTmp = 0;
+ if (aOrigAny >>= nTmp)
+ nDupSource = nTmp;
+ }
+ catch(uno::Exception&)
+ {
+ }
+
+ sal_uInt8 nDupCount = 0;
+ if (nDupSource >= 0)
+ {
+ // this dimension is cloned.
+
+ SCCOL nCompCol; // ID of the original dimension.
+ if ( bDataLayout )
+ nCompCol = PIVOT_DATA_FIELD;
+ else
+ nCompCol = static_cast<SCCOL>(nDupSource); //TODO: seek source column from name
+
+ ScPivotFieldVector::iterator it = std::find_if(aFields.begin(), aFields.end(), FindByColumn(nCompCol, nMask));
+ if (it != aFields.end())
+ nDupCount = it->mnDupCount + 1;
+ }
+
+ aFields.emplace_back();
+ ScPivotField& rField = aFields.back();
+ if (bDataLayout)
+ {
+ rField.nCol = PIVOT_DATA_FIELD;
+ bDataFound = true;
+ }
+ else
+ {
+ rField.mnOriginalDim = nDupSource;
+ rField.nCol = static_cast<SCCOL>(nDim); //TODO: seek source column from name
+ }
+
+ rField.nFuncMask = nMask;
+ rField.mnDupCount = nDupCount;
+ tools::Long nPos = ScUnoHelpFunctions::GetLongProperty(
+ xDimProp, SC_UNO_DP_POSITION);
+ aPos.push_back(nPos);
+
+ try
+ {
+ if (nOrient == sheet::DataPilotFieldOrientation_DATA)
+ xDimProp->getPropertyValue(SC_UNO_DP_REFVALUE)
+ >>= rField.maFieldRef;
+ }
+ catch (uno::Exception&)
+ {
+ }
+ }
+ }
+
+ // sort by getPosition() value
+
+ size_t nOutCount = aFields.size();
+ if (nOutCount >= 1)
+ {
+ for (size_t i = 0; i < nOutCount - 1; ++i)
+ {
+ for (size_t j = 0; j + i < nOutCount - 1; ++j)
+ {
+ if ( aPos[j+1] < aPos[j] )
+ {
+ std::swap( aPos[j], aPos[j+1] );
+ std::swap( aFields[j], aFields[j+1] );
+ }
+ }
+ }
+ }
+
+ if (bAddData && !bDataFound)
+ aFields.emplace_back(PIVOT_DATA_FIELD);
+
+ rFields.swap(aFields);
+}
+
+void ScDPObject::FillOldParam(ScPivotParam& rParam) const
+{
+ const_cast<ScDPObject*>(this)->CreateObjects(); // xSource is needed for field numbers
+
+ if (!xSource.is())
+ return;
+
+ rParam.nCol = aOutRange.aStart.Col();
+ rParam.nRow = aOutRange.aStart.Row();
+ rParam.nTab = aOutRange.aStart.Tab();
+ // ppLabelArr / nLabels is not changed
+
+ bool bAddData = ( lcl_GetDataGetOrientation( xSource ) == sheet::DataPilotFieldOrientation_HIDDEN );
+ lcl_FillOldFields(
+ rParam.maPageFields, xSource, sheet::DataPilotFieldOrientation_PAGE, false);
+ lcl_FillOldFields(
+ rParam.maColFields, xSource, sheet::DataPilotFieldOrientation_COLUMN, bAddData);
+ lcl_FillOldFields(
+ rParam.maRowFields, xSource, sheet::DataPilotFieldOrientation_ROW, false);
+ lcl_FillOldFields(
+ rParam.maDataFields, xSource, sheet::DataPilotFieldOrientation_DATA, false);
+
+ uno::Reference<beans::XPropertySet> xProp( xSource, uno::UNO_QUERY );
+ if (!xProp.is())
+ return;
+
+ try
+ {
+ rParam.bMakeTotalCol = ScUnoHelpFunctions::GetBoolProperty( xProp,
+ SC_UNO_DP_COLGRAND, true );
+ rParam.bMakeTotalRow = ScUnoHelpFunctions::GetBoolProperty( xProp,
+ SC_UNO_DP_ROWGRAND, true );
+
+ // following properties may be missing for external sources
+ rParam.bIgnoreEmptyRows = ScUnoHelpFunctions::GetBoolProperty( xProp,
+ SC_UNO_DP_IGNOREEMPTY );
+ rParam.bDetectCategories = ScUnoHelpFunctions::GetBoolProperty( xProp,
+ SC_UNO_DP_REPEATEMPTY );
+ }
+ catch(uno::Exception&)
+ {
+ // no error
+ }
+}
+
+static void lcl_FillLabelData( ScDPLabelData& rData, const uno::Reference< beans::XPropertySet >& xDimProp )
+{
+ uno::Reference<sheet::XHierarchiesSupplier> xDimSupp( xDimProp, uno::UNO_QUERY );
+ if (!xDimProp.is() || !xDimSupp.is())
+ return;
+
+ uno::Reference<container::XIndexAccess> xHiers = new ScNameToIndexAccess( xDimSupp->getHierarchies() );
+ tools::Long nHierarchy = ScUnoHelpFunctions::GetLongProperty(
+ xDimProp, SC_UNO_DP_USEDHIERARCHY);
+ if ( nHierarchy >= xHiers->getCount() )
+ nHierarchy = 0;
+ rData.mnUsedHier = nHierarchy;
+
+ uno::Reference<sheet::XLevelsSupplier> xHierSupp(xHiers->getByIndex(nHierarchy),
+ uno::UNO_QUERY);
+ if (!xHierSupp.is())
+ return;
+
+ uno::Reference<container::XIndexAccess> xLevels =
+ new ScNameToIndexAccess( xHierSupp->getLevels() );
+
+ uno::Reference<beans::XPropertySet> xLevProp(xLevels->getByIndex(0), uno::UNO_QUERY);
+ if (!xLevProp.is())
+ return;
+
+ rData.mbShowAll = ScUnoHelpFunctions::GetBoolProperty(
+ xLevProp, SC_UNO_DP_SHOWEMPTY);
+
+ rData.mbRepeatItemLabels = ScUnoHelpFunctions::GetBoolProperty(
+ xLevProp, SC_UNO_DP_REPEATITEMLABELS);
+
+ try
+ {
+ xLevProp->getPropertyValue( SC_UNO_DP_SORTING )
+ >>= rData.maSortInfo;
+ xLevProp->getPropertyValue( SC_UNO_DP_LAYOUT )
+ >>= rData.maLayoutInfo;
+ xLevProp->getPropertyValue( SC_UNO_DP_AUTOSHOW )
+ >>= rData.maShowInfo;
+ }
+ catch(uno::Exception&)
+ {
+ }
+}
+
+void ScDPObject::FillLabelDataForDimension(
+ const uno::Reference<container::XIndexAccess>& xDims, sal_Int32 nDim, ScDPLabelData& rLabelData)
+{
+ uno::Reference<uno::XInterface> xIntDim(xDims->getByIndex(nDim), uno::UNO_QUERY);
+ uno::Reference<container::XNamed> xDimName( xIntDim, uno::UNO_QUERY );
+ uno::Reference<beans::XPropertySet> xDimProp( xIntDim, uno::UNO_QUERY );
+
+ if (!xDimName.is() || !xDimProp.is())
+ return;
+
+ bool bData = ScUnoHelpFunctions::GetBoolProperty(
+ xDimProp, SC_UNO_DP_ISDATALAYOUT);
+ //TODO: error checking -- is "IsDataLayoutDimension" property required??
+
+ sal_Int32 nOrigPos = -1;
+ OUString aFieldName;
+ try
+ {
+ aFieldName = xDimName->getName();
+ uno::Any aOrigAny = xDimProp->getPropertyValue(SC_UNO_DP_ORIGINAL_POS);
+ aOrigAny >>= nOrigPos;
+ }
+ catch(uno::Exception&)
+ {
+ }
+
+ OUString aLayoutName = ScUnoHelpFunctions::GetStringProperty(
+ xDimProp, SC_UNO_DP_LAYOUTNAME, OUString());
+
+ OUString aSubtotalName = ScUnoHelpFunctions::GetStringProperty(
+ xDimProp, SC_UNO_DP_FIELD_SUBTOTALNAME, OUString());
+
+ // Name from the UNO dimension object may have trailing '*'s in which
+ // case it's a duplicate dimension. Convert that to a duplicate index.
+
+ sal_uInt8 nDupCount = ScDPUtil::getDuplicateIndex(aFieldName);
+ aFieldName = ScDPUtil::getSourceDimensionName(aFieldName);
+
+ rLabelData.maName = aFieldName;
+ rLabelData.mnCol = static_cast<SCCOL>(nDim);
+ rLabelData.mnDupCount = nDupCount;
+ rLabelData.mbDataLayout = bData;
+ rLabelData.mbIsValue = true; //TODO: check
+
+ if (bData)
+ return;
+
+ rLabelData.mnOriginalDim = static_cast<tools::Long>(nOrigPos);
+ rLabelData.maLayoutName = aLayoutName;
+ rLabelData.maSubtotalName = aSubtotalName;
+ if (nOrigPos >= 0)
+ // This is a duplicated dimension. Use the original dimension index.
+ nDim = nOrigPos;
+ GetHierarchies(nDim, rLabelData.maHiers);
+ GetMembers(nDim, GetUsedHierarchy(nDim), rLabelData.maMembers);
+ lcl_FillLabelData(rLabelData, xDimProp);
+ rLabelData.mnFlags = ScUnoHelpFunctions::GetLongProperty(
+ xDimProp, SC_UNO_DP_FLAGS );
+}
+
+void ScDPObject::FillLabelData(sal_Int32 nDim, ScDPLabelData& rLabels)
+{
+ CreateObjects();
+ if (!xSource.is())
+ return;
+
+ uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions();
+ uno::Reference<container::XIndexAccess> xDims = new ScNameToIndexAccess( xDimsName );
+ sal_Int32 nDimCount = xDims->getCount();
+ if (nDimCount <= 0 || nDim >= nDimCount)
+ return;
+
+ FillLabelDataForDimension(xDims, nDim, rLabels);
+}
+
+void ScDPObject::FillLabelData(ScPivotParam& rParam)
+{
+ rParam.maLabelArray.clear();
+
+ CreateObjects();
+ if (!xSource.is())
+ return;
+
+ uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions();
+ uno::Reference<container::XIndexAccess> xDims = new ScNameToIndexAccess( xDimsName );
+ sal_Int32 nDimCount = xDims->getCount();
+ if (nDimCount <= 0)
+ return;
+
+ for (sal_Int32 nDim = 0; nDim < nDimCount; ++nDim)
+ {
+ ScDPLabelData* pNewLabel = new ScDPLabelData;
+ FillLabelDataForDimension(xDims, nDim, *pNewLabel);
+ rParam.maLabelArray.push_back(std::unique_ptr<ScDPLabelData>(pNewLabel));
+ }
+}
+
+bool ScDPObject::GetHierarchiesNA( sal_Int32 nDim, uno::Reference< container::XNameAccess >& xHiers )
+{
+ bool bRet = false;
+ uno::Reference<container::XNameAccess> xDimsName( GetSource()->getDimensions() );
+ uno::Reference<container::XIndexAccess> xIntDims(new ScNameToIndexAccess( xDimsName ));
+ if( xIntDims.is() )
+ {
+ uno::Reference<sheet::XHierarchiesSupplier> xHierSup(xIntDims->getByIndex( nDim ), uno::UNO_QUERY);
+ if (xHierSup.is())
+ {
+ xHiers.set( xHierSup->getHierarchies() );
+ bRet = xHiers.is();
+ }
+ }
+ return bRet;
+}
+
+void ScDPObject::GetHierarchies( sal_Int32 nDim, uno::Sequence< OUString >& rHiers )
+{
+ uno::Reference< container::XNameAccess > xHiersNA;
+ if( GetHierarchiesNA( nDim, xHiersNA ) )
+ {
+ rHiers = xHiersNA->getElementNames();
+ }
+}
+
+sal_Int32 ScDPObject::GetUsedHierarchy( sal_Int32 nDim )
+{
+ sal_Int32 nHier = 0;
+ uno::Reference<container::XNameAccess> xDimsName( GetSource()->getDimensions() );
+ uno::Reference<container::XIndexAccess> xIntDims(new ScNameToIndexAccess( xDimsName ));
+ uno::Reference<beans::XPropertySet> xDim(xIntDims->getByIndex( nDim ), uno::UNO_QUERY);
+ if (xDim.is())
+ nHier = ScUnoHelpFunctions::GetLongProperty( xDim, SC_UNO_DP_USEDHIERARCHY );
+ return nHier;
+}
+
+bool ScDPObject::GetMembersNA( sal_Int32 nDim, uno::Reference< sheet::XMembersAccess >& xMembers )
+{
+ return GetMembersNA( nDim, GetUsedHierarchy( nDim ), xMembers );
+}
+
+bool ScDPObject::GetMembersNA( sal_Int32 nDim, sal_Int32 nHier, uno::Reference< sheet::XMembersAccess >& xMembers )
+{
+ bool bRet = false;
+ uno::Reference<container::XNameAccess> xDimsName( GetSource()->getDimensions() );
+ uno::Reference<container::XIndexAccess> xIntDims(new ScNameToIndexAccess( xDimsName ));
+ uno::Reference<beans::XPropertySet> xDim(xIntDims->getByIndex( nDim ), uno::UNO_QUERY);
+ if (xDim.is())
+ {
+ uno::Reference<sheet::XHierarchiesSupplier> xHierSup(xDim, uno::UNO_QUERY);
+ if (xHierSup.is())
+ {
+ uno::Reference<container::XIndexAccess> xHiers(new ScNameToIndexAccess(xHierSup->getHierarchies()));
+ uno::Reference<sheet::XLevelsSupplier> xLevSupp( xHiers->getByIndex(nHier), uno::UNO_QUERY );
+ if ( xLevSupp.is() )
+ {
+ uno::Reference<container::XIndexAccess> xLevels(new ScNameToIndexAccess( xLevSupp->getLevels()));
+ if (xLevels.is())
+ {
+ sal_Int32 nLevCount = xLevels->getCount();
+ if (nLevCount > 0)
+ {
+ uno::Reference<sheet::XMembersSupplier> xMembSupp( xLevels->getByIndex(0), uno::UNO_QUERY );
+ if ( xMembSupp.is() )
+ {
+ xMembers.set(xMembSupp->getMembers());
+ bRet = true;
+ }
+ }
+ }
+ }
+ }
+ }
+ return bRet;
+}
+
+// convert old pivot tables into new datapilot tables
+
+namespace {
+
+OUString lcl_GetDimName( const uno::Reference<sheet::XDimensionsSupplier>& xSource, tools::Long nDim )
+{
+ OUString aName;
+ if ( xSource.is() )
+ {
+ uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions();
+ uno::Reference<container::XIndexAccess> xDims = new ScNameToIndexAccess( xDimsName );
+ tools::Long nDimCount = xDims->getCount();
+ if ( nDim < nDimCount )
+ {
+ uno::Reference<container::XNamed> xDimName(xDims->getByIndex(nDim), uno::UNO_QUERY);
+ if (xDimName.is())
+ {
+ try
+ {
+ aName = xDimName->getName();
+ }
+ catch(uno::Exception&)
+ {
+ }
+ }
+ }
+ }
+ return aName;
+}
+
+bool hasFieldColumn(const vector<ScPivotField>* pRefFields, SCCOL nCol)
+{
+ if (!pRefFields)
+ return false;
+
+ return std::any_of(pRefFields->begin(), pRefFields->end(),
+ [&nCol](const ScPivotField& rField) {
+ // This array of fields contains the specified column.
+ return rField.nCol == nCol; });
+}
+
+class FindByOriginalDim
+{
+ tools::Long mnDim;
+public:
+ explicit FindByOriginalDim(tools::Long nDim) : mnDim(nDim) {}
+ bool operator() (const ScPivotField& r) const
+ {
+ return mnDim == r.getOriginalDim();
+ }
+};
+
+}
+
+void ScDPObject::ConvertOrientation(
+ ScDPSaveData& rSaveData, const ScPivotFieldVector& rFields, sheet::DataPilotFieldOrientation nOrient,
+ const Reference<XDimensionsSupplier>& xSource,
+ const ScDPLabelDataVector& rLabels,
+ const ScPivotFieldVector* pRefColFields,
+ const ScPivotFieldVector* pRefRowFields,
+ const ScPivotFieldVector* pRefPageFields )
+{
+ ScPivotFieldVector::const_iterator itr, itrBeg = rFields.begin(), itrEnd = rFields.end();
+ for (itr = itrBeg; itr != itrEnd; ++itr)
+ {
+ const ScPivotField& rField = *itr;
+
+ tools::Long nCol = rField.getOriginalDim();
+ PivotFunc nFuncs = rField.nFuncMask;
+ const sheet::DataPilotFieldReference& rFieldRef = rField.maFieldRef;
+
+ ScDPSaveDimension* pDim = nullptr;
+ if ( nCol == PIVOT_DATA_FIELD )
+ pDim = rSaveData.GetDataLayoutDimension();
+ else
+ {
+ OUString aDocStr = lcl_GetDimName( xSource, nCol ); // cols must start at 0
+ if (!aDocStr.isEmpty())
+ pDim = rSaveData.GetDimensionByName(aDocStr);
+ else
+ pDim = nullptr;
+ }
+
+ if (!pDim)
+ continue;
+
+ if ( nOrient == sheet::DataPilotFieldOrientation_DATA ) // set summary function
+ {
+ // generate an individual entry for each function
+ bool bFirst = true;
+
+ // if a dimension is used for column/row/page and data,
+ // use duplicated dimensions for all data occurrences
+ if (hasFieldColumn(pRefColFields, nCol))
+ bFirst = false;
+
+ if (bFirst && hasFieldColumn(pRefRowFields, nCol))
+ bFirst = false;
+
+ if (bFirst && hasFieldColumn(pRefPageFields, nCol))
+ bFirst = false;
+
+ if (bFirst)
+ {
+ // if set via api, a data column may occur several times
+ // (if the function hasn't been changed yet) -> also look for duplicate data column
+ bFirst = std::none_of(itrBeg, itr, FindByOriginalDim(nCol));
+ }
+
+ ScGeneralFunction eFunc = ScDataPilotConversion::FirstFunc(rField.nFuncMask);
+ if (!bFirst)
+ pDim = rSaveData.DuplicateDimension(pDim->GetName());
+ pDim->SetOrientation(nOrient);
+ pDim->SetFunction(eFunc);
+
+ if( rFieldRef.ReferenceType == sheet::DataPilotFieldReferenceType::NONE )
+ pDim->SetReferenceValue(nullptr);
+ else
+ pDim->SetReferenceValue(&rFieldRef);
+ }
+ else // set SubTotals
+ {
+ pDim->SetOrientation( nOrient );
+
+ std::vector<ScGeneralFunction> nSubTotalFuncs;
+ nSubTotalFuncs.reserve(16);
+ sal_uInt16 nMask = 1;
+ for (sal_uInt16 nBit=0; nBit<16; nBit++)
+ {
+ if ( nFuncs & static_cast<PivotFunc>(nMask) )
+ nSubTotalFuncs.push_back( ScDataPilotConversion::FirstFunc( static_cast<PivotFunc>(nMask) ) );
+ nMask *= 2;
+ }
+ pDim->SetSubTotals( std::move(nSubTotalFuncs) );
+
+ // ShowEmpty was implicit in old tables,
+ // must be set for data layout dimension (not accessible in dialog)
+ if ( nCol == PIVOT_DATA_FIELD )
+ pDim->SetShowEmpty( true );
+ }
+
+ size_t nDimIndex = rField.nCol;
+ pDim->RemoveLayoutName();
+ pDim->RemoveSubtotalName();
+ if (nDimIndex < rLabels.size())
+ {
+ const ScDPLabelData& rLabel = *rLabels[nDimIndex];
+ if (!rLabel.maLayoutName.isEmpty())
+ pDim->SetLayoutName(rLabel.maLayoutName);
+ if (!rLabel.maSubtotalName.isEmpty())
+ pDim->SetSubtotalName(rLabel.maSubtotalName);
+ }
+ }
+}
+
+bool ScDPObject::IsOrientationAllowed( sheet::DataPilotFieldOrientation nOrient, sal_Int32 nDimFlags )
+{
+ bool bAllowed = true;
+ switch (nOrient)
+ {
+ case sheet::DataPilotFieldOrientation_PAGE:
+ bAllowed = ( nDimFlags & sheet::DimensionFlags::NO_PAGE_ORIENTATION ) == 0;
+ break;
+ case sheet::DataPilotFieldOrientation_COLUMN:
+ bAllowed = ( nDimFlags & sheet::DimensionFlags::NO_COLUMN_ORIENTATION ) == 0;
+ break;
+ case sheet::DataPilotFieldOrientation_ROW:
+ bAllowed = ( nDimFlags & sheet::DimensionFlags::NO_ROW_ORIENTATION ) == 0;
+ break;
+ case sheet::DataPilotFieldOrientation_DATA:
+ bAllowed = ( nDimFlags & sheet::DimensionFlags::NO_DATA_ORIENTATION ) == 0;
+ break;
+ default:
+ {
+ // allowed to remove from previous orientation
+ }
+ }
+ return bAllowed;
+}
+
+bool ScDPObject::HasRegisteredSources()
+{
+ bool bFound = false;
+
+ 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(
+ SCDPSOURCE_SERVICE );
+ if ( xEnum.is() && xEnum->hasMoreElements() )
+ bFound = true;
+ }
+
+ return bFound;
+}
+
+std::vector<OUString> ScDPObject::GetRegisteredSources()
+{
+ std::vector<OUString> aVec;
+
+ // use implementation names...
+
+ 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(
+ SCDPSOURCE_SERVICE );
+ if ( xEnum.is() )
+ {
+ while ( xEnum->hasMoreElements() )
+ {
+ uno::Any aAddInAny = xEnum->nextElement();
+// if ( aAddInAny.getReflection()->getTypeClass() == TypeClass_INTERFACE )
+ {
+ uno::Reference<uno::XInterface> xIntFac;
+ aAddInAny >>= xIntFac;
+ if ( xIntFac.is() )
+ {
+ uno::Reference<lang::XServiceInfo> xInfo( xIntFac, uno::UNO_QUERY );
+ if ( xInfo.is() )
+ {
+ OUString sName = xInfo->getImplementationName();
+ aVec.push_back( sName );
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return aVec;
+}
+
+uno::Reference<sheet::XDimensionsSupplier> ScDPObject::CreateSource( const ScDPServiceDesc& rDesc )
+{
+ OUString aImplName = rDesc.aServiceName;
+ uno::Reference<sheet::XDimensionsSupplier> xRet;
+
+ uno::Reference<lang::XMultiServiceFactory> xManager = comphelper::getProcessServiceFactory();
+ uno::Reference<container::XContentEnumerationAccess> xEnAc(xManager, uno::UNO_QUERY);
+ if (!xEnAc.is())
+ return xRet;
+
+ uno::Reference<container::XEnumeration> xEnum =
+ xEnAc->createContentEnumeration(SCDPSOURCE_SERVICE);
+ if (!xEnum.is())
+ return xRet;
+
+ while (xEnum->hasMoreElements() && !xRet.is())
+ {
+ uno::Any aAddInAny = xEnum->nextElement();
+ uno::Reference<uno::XInterface> xIntFac;
+ aAddInAny >>= xIntFac;
+ if (!xIntFac.is())
+ continue;
+
+ uno::Reference<lang::XServiceInfo> xInfo(xIntFac, uno::UNO_QUERY);
+ if (!xInfo.is() || xInfo->getImplementationName() != aImplName)
+ continue;
+
+ try
+ {
+ // #i113160# try XSingleComponentFactory in addition to (old) XSingleServiceFactory,
+ // passing the context to the component (see ScUnoAddInCollection::Initialize)
+
+ 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())
+ {
+ uno::Reference<lang::XSingleServiceFactory> xFac( xIntFac, uno::UNO_QUERY );
+ if ( xFac.is() )
+ xInterface = xFac->createInstance();
+ }
+
+ uno::Reference<lang::XInitialization> xInit( xInterface, uno::UNO_QUERY );
+ if (xInit.is())
+ {
+ // initialize
+ uno::Sequence<uno::Any> aSeq(4);
+ uno::Any* pArray = aSeq.getArray();
+ pArray[0] <<= rDesc.aParSource;
+ pArray[1] <<= rDesc.aParName;
+ pArray[2] <<= rDesc.aParUser;
+ pArray[3] <<= rDesc.aParPass;
+ xInit->initialize( aSeq );
+ }
+ xRet.set( xInterface, uno::UNO_QUERY );
+ }
+ catch(uno::Exception&)
+ {
+ }
+ }
+
+ return xRet;
+}
+
+#if DUMP_PIVOT_TABLE
+
+void ScDPObject::Dump() const
+{
+ if (pSaveData)
+ pSaveData->Dump();
+
+ if (mpTableData)
+ mpTableData->Dump();
+}
+
+void ScDPObject::DumpCache() const
+{
+ if (!mpTableData)
+ return;
+
+ const ScDPCache &rCache = mpTableData->GetCacheTable().getCache();
+
+ rCache.Dump();
+}
+#endif
+
+ScDPCollection::SheetCaches::SheetCaches(ScDocument& rDoc) : mrDoc(rDoc) {}
+
+namespace {
+
+struct FindInvalidRange
+{
+ bool operator() (const ScRange& r) const
+ {
+ return !r.IsValid();
+ }
+};
+
+void setGroupItemsToCache( ScDPCache& rCache, const o3tl::sorted_vector<ScDPObject*>& rRefs )
+{
+ // Go through all referencing pivot tables, and re-fill the group dimension info.
+ for (const ScDPObject* pObj : rRefs)
+ {
+ const ScDPSaveData* pSave = pObj->GetSaveData();
+ if (!pSave)
+ continue;
+
+ const ScDPDimensionSaveData* pGroupDims = pSave->GetExistingDimensionData();
+ if (!pGroupDims)
+ continue;
+
+ pGroupDims->WriteToCache(rCache);
+ }
+}
+
+}
+
+bool ScDPCollection::SheetCaches::hasCache(const ScRange& rRange) const
+{
+ RangeIndexType::const_iterator it = std::find(maRanges.begin(), maRanges.end(), rRange);
+ if (it == maRanges.end())
+ return false;
+
+ // Already cached.
+ size_t nIndex = std::distance(maRanges.begin(), it);
+ CachesType::const_iterator const itCache = m_Caches.find(nIndex);
+ return itCache != m_Caches.end();
+}
+
+const ScDPCache* ScDPCollection::SheetCaches::getCache(const ScRange& rRange, const ScDPDimensionSaveData* pDimData)
+{
+ RangeIndexType::iterator it = std::find(maRanges.begin(), maRanges.end(), rRange);
+ if (it != maRanges.end())
+ {
+ // Already cached.
+ size_t nIndex = std::distance(maRanges.begin(), it);
+ CachesType::iterator const itCache = m_Caches.find(nIndex);
+ if (itCache == m_Caches.end())
+ {
+ OSL_FAIL("Cache pool and index pool out-of-sync !!!");
+ return nullptr;
+ }
+
+ if (pDimData)
+ {
+ (itCache->second)->ClearGroupFields();
+ pDimData->WriteToCache(*itCache->second);
+ }
+
+ return itCache->second.get();
+ }
+
+ // Not cached. Create a new cache.
+ ::std::unique_ptr<ScDPCache> pCache(new ScDPCache(mrDoc));
+ pCache->InitFromDoc(mrDoc, rRange);
+ if (pDimData)
+ pDimData->WriteToCache(*pCache);
+
+ // Get the smallest available range index.
+ it = std::find_if(maRanges.begin(), maRanges.end(), FindInvalidRange());
+
+ size_t nIndex = maRanges.size();
+ if (it == maRanges.end())
+ {
+ // All range indices are valid. Append a new index.
+ maRanges.push_back(rRange);
+ }
+ else
+ {
+ // Slot with invalid range. Re-use this slot.
+ *it = rRange;
+ nIndex = std::distance(maRanges.begin(), it);
+ }
+
+ const ScDPCache* p = pCache.get();
+ m_Caches.insert(std::make_pair(nIndex, std::move(pCache)));
+ return p;
+}
+
+ScDPCache* ScDPCollection::SheetCaches::getExistingCache(const ScRange& rRange)
+{
+ RangeIndexType::iterator it = std::find(maRanges.begin(), maRanges.end(), rRange);
+ if (it == maRanges.end())
+ // Not cached.
+ return nullptr;
+
+ // Already cached.
+ size_t nIndex = std::distance(maRanges.begin(), it);
+ CachesType::iterator const itCache = m_Caches.find(nIndex);
+ if (itCache == m_Caches.end())
+ {
+ OSL_FAIL("Cache pool and index pool out-of-sync !!!");
+ return nullptr;
+ }
+
+ return itCache->second.get();
+}
+
+const ScDPCache* ScDPCollection::SheetCaches::getExistingCache(const ScRange& rRange) const
+{
+ RangeIndexType::const_iterator it = std::find(maRanges.begin(), maRanges.end(), rRange);
+ if (it == maRanges.end())
+ // Not cached.
+ return nullptr;
+
+ // Already cached.
+ size_t nIndex = std::distance(maRanges.begin(), it);
+ CachesType::const_iterator const itCache = m_Caches.find(nIndex);
+ if (itCache == m_Caches.end())
+ {
+ OSL_FAIL("Cache pool and index pool out-of-sync !!!");
+ return nullptr;
+ }
+
+ return itCache->second.get();
+}
+
+size_t ScDPCollection::SheetCaches::size() const
+{
+ return m_Caches.size();
+}
+
+void ScDPCollection::SheetCaches::updateReference(
+ UpdateRefMode eMode, const ScRange& r, SCCOL nDx, SCROW nDy, SCTAB nDz)
+{
+ if (maRanges.empty())
+ // No caches.
+ return;
+
+ for (ScRange& rKeyRange : maRanges)
+ {
+ SCCOL nCol1 = rKeyRange.aStart.Col();
+ SCROW nRow1 = rKeyRange.aStart.Row();
+ SCTAB nTab1 = rKeyRange.aStart.Tab();
+ SCCOL nCol2 = rKeyRange.aEnd.Col();
+ SCROW nRow2 = rKeyRange.aEnd.Row();
+ SCTAB nTab2 = rKeyRange.aEnd.Tab();
+
+ ScRefUpdateRes eRes = ScRefUpdate::Update(
+ &mrDoc, eMode,
+ r.aStart.Col(), r.aStart.Row(), r.aStart.Tab(),
+ r.aEnd.Col(), r.aEnd.Row(), r.aEnd.Tab(), nDx, nDy, nDz,
+ nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
+
+ if (eRes != UR_NOTHING)
+ {
+ // range updated.
+ ScRange aNew(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
+ rKeyRange = aNew;
+ }
+ }
+}
+
+void ScDPCollection::SheetCaches::updateCache(const ScRange& rRange, o3tl::sorted_vector<ScDPObject*>& rRefs)
+{
+ RangeIndexType::iterator it = std::find(maRanges.begin(), maRanges.end(), rRange);
+ if (it == maRanges.end())
+ {
+ // Not cached. Nothing to do.
+ rRefs.clear();
+ return;
+ }
+
+ size_t nIndex = std::distance(maRanges.begin(), it);
+ CachesType::iterator const itCache = m_Caches.find(nIndex);
+ if (itCache == m_Caches.end())
+ {
+ OSL_FAIL("Cache pool and index pool out-of-sync !!!");
+ rRefs.clear();
+ return;
+ }
+
+ ScDPCache& rCache = *itCache->second;
+
+ // Update the cache with new cell values. This will clear all group dimension info.
+ rCache.InitFromDoc(mrDoc, rRange);
+
+ o3tl::sorted_vector<ScDPObject*> aRefs(rCache.GetAllReferences());
+ rRefs.swap(aRefs);
+
+ // Make sure to re-populate the group dimension info.
+ setGroupItemsToCache(rCache, rRefs);
+}
+
+bool ScDPCollection::SheetCaches::remove(const ScDPCache* p)
+{
+ CachesType::iterator it = std::find_if(m_Caches.begin(), m_Caches.end(),
+ [&p](const CachesType::value_type& rEntry) { return rEntry.second.get() == p; });
+ if (it != m_Caches.end())
+ {
+ size_t idx = it->first;
+ m_Caches.erase(it);
+ maRanges[idx].SetInvalid();
+ return true;
+ }
+ return false;
+}
+
+const std::vector<ScRange>& ScDPCollection::SheetCaches::getAllRanges() const
+{
+ return maRanges;
+}
+
+ScDPCollection::NameCaches::NameCaches(ScDocument& rDoc) : mrDoc(rDoc) {}
+
+bool ScDPCollection::NameCaches::hasCache(const OUString& rName) const
+{
+ return m_Caches.count(rName) != 0;
+}
+
+const ScDPCache* ScDPCollection::NameCaches::getCache(
+ const OUString& rName, const ScRange& rRange, const ScDPDimensionSaveData* pDimData)
+{
+ CachesType::const_iterator const itr = m_Caches.find(rName);
+ if (itr != m_Caches.end())
+ // already cached.
+ return itr->second.get();
+
+ ::std::unique_ptr<ScDPCache> pCache(new ScDPCache(mrDoc));
+ pCache->InitFromDoc(mrDoc, rRange);
+ if (pDimData)
+ pDimData->WriteToCache(*pCache);
+
+ const ScDPCache *const p = pCache.get();
+ m_Caches.insert(std::make_pair(rName, std::move(pCache)));
+ return p;
+}
+
+ScDPCache* ScDPCollection::NameCaches::getExistingCache(const OUString& rName)
+{
+ CachesType::iterator const itr = m_Caches.find(rName);
+ return itr != m_Caches.end() ? itr->second.get() : nullptr;
+}
+
+size_t ScDPCollection::NameCaches::size() const
+{
+ return m_Caches.size();
+}
+
+void ScDPCollection::NameCaches::updateCache(
+ const OUString& rName, const ScRange& rRange, o3tl::sorted_vector<ScDPObject*>& rRefs)
+{
+ CachesType::iterator const itr = m_Caches.find(rName);
+ if (itr == m_Caches.end())
+ {
+ rRefs.clear();
+ return;
+ }
+
+ ScDPCache& rCache = *itr->second;
+ // Update the cache with new cell values. This will clear all group dimension info.
+ rCache.InitFromDoc(mrDoc, rRange);
+
+ o3tl::sorted_vector<ScDPObject*> aRefs(rCache.GetAllReferences());
+ rRefs.swap(aRefs);
+
+ // Make sure to re-populate the group dimension info.
+ setGroupItemsToCache(rCache, rRefs);
+}
+
+bool ScDPCollection::NameCaches::remove(const ScDPCache* p)
+{
+ CachesType::iterator it = std::find_if(m_Caches.begin(), m_Caches.end(),
+ [&p](const CachesType::value_type& rEntry) { return rEntry.second.get() == p; });
+ if (it != m_Caches.end())
+ {
+ m_Caches.erase(it);
+ return true;
+ }
+ return false;
+}
+
+ScDPCollection::DBType::DBType(sal_Int32 nSdbType, const OUString& rDBName, const OUString& rCommand) :
+ mnSdbType(nSdbType), maDBName(rDBName), maCommand(rCommand) {}
+
+bool ScDPCollection::DBType::less::operator() (const DBType& left, const DBType& right) const
+{
+ return left < right;
+}
+
+ScDPCollection::DBCaches::DBCaches(ScDocument& rDoc) : mrDoc(rDoc) {}
+
+bool ScDPCollection::DBCaches::hasCache(sal_Int32 nSdbType, const OUString& rDBName, const OUString& rCommand) const
+{
+ DBType aType(nSdbType, rDBName, rCommand);
+ CachesType::const_iterator const itr = m_Caches.find(aType);
+ return itr != m_Caches.end();
+}
+
+const ScDPCache* ScDPCollection::DBCaches::getCache(
+ sal_Int32 nSdbType, const OUString& rDBName, const OUString& rCommand,
+ const ScDPDimensionSaveData* pDimData)
+{
+ DBType aType(nSdbType, rDBName, rCommand);
+ CachesType::const_iterator const itr = m_Caches.find(aType);
+ if (itr != m_Caches.end())
+ // already cached.
+ return itr->second.get();
+
+ uno::Reference<sdbc::XRowSet> xRowSet = createRowSet(nSdbType, rDBName, rCommand);
+ if (!xRowSet.is())
+ return nullptr;
+
+ ::std::unique_ptr<ScDPCache> pCache(new ScDPCache(mrDoc));
+ SvNumberFormatter aFormat( comphelper::getProcessComponentContext(), ScGlobal::eLnge);
+ DBConnector aDB(*pCache, xRowSet, aFormat.GetNullDate());
+ if (!aDB.isValid())
+ return nullptr;
+
+ if (!pCache->InitFromDataBase(aDB))
+ {
+ // initialization failed.
+ comphelper::disposeComponent(xRowSet);
+ return nullptr;
+ }
+
+ if (pDimData)
+ pDimData->WriteToCache(*pCache);
+
+ ::comphelper::disposeComponent(xRowSet);
+ const ScDPCache* p = pCache.get();
+ m_Caches.insert(std::make_pair(aType, std::move(pCache)));
+ return p;
+}
+
+ScDPCache* ScDPCollection::DBCaches::getExistingCache(
+ sal_Int32 nSdbType, const OUString& rDBName, const OUString& rCommand)
+{
+ DBType aType(nSdbType, rDBName, rCommand);
+ CachesType::iterator const itr = m_Caches.find(aType);
+ return itr != m_Caches.end() ? itr->second.get() : nullptr;
+}
+
+uno::Reference<sdbc::XRowSet> ScDPCollection::DBCaches::createRowSet(
+ sal_Int32 nSdbType, const OUString& rDBName, const OUString& rCommand)
+{
+ uno::Reference<sdbc::XRowSet> xRowSet;
+ try
+ {
+ xRowSet.set(comphelper::getProcessServiceFactory()->createInstance(
+ SC_SERVICE_ROWSET),
+ UNO_QUERY);
+
+ uno::Reference<beans::XPropertySet> xRowProp(xRowSet, UNO_QUERY);
+ OSL_ENSURE( xRowProp.is(), "can't get RowSet" );
+ if (!xRowProp.is())
+ {
+ xRowSet.set(nullptr);
+ return xRowSet;
+ }
+
+ // set source parameters
+
+ xRowProp->setPropertyValue( SC_DBPROP_DATASOURCENAME, Any(rDBName) );
+ xRowProp->setPropertyValue( SC_DBPROP_COMMAND, Any(rCommand) );
+ xRowProp->setPropertyValue( SC_DBPROP_COMMANDTYPE, Any(nSdbType) );
+
+ uno::Reference<sdb::XCompletedExecution> xExecute( xRowSet, uno::UNO_QUERY );
+ if ( xExecute.is() )
+ {
+ uno::Reference<task::XInteractionHandler> xHandler(
+ task::InteractionHandler::createWithParent(comphelper::getProcessComponentContext(), nullptr),
+ uno::UNO_QUERY_THROW);
+ xExecute->executeWithCompletion( xHandler );
+ }
+ else
+ xRowSet->execute();
+
+ return xRowSet;
+ }
+ catch ( const sdbc::SQLException& rError )
+ {
+ //! store error message
+ std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(ScDocShell::GetActiveDialogParent(),
+ VclMessageType::Info, VclButtonsType::Ok,
+ rError.Message));
+ xInfoBox->run();
+ }
+ catch ( uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "sc", "Unexpected exception in database");
+ }
+
+ xRowSet.set(nullptr);
+ return xRowSet;
+}
+
+void ScDPCollection::DBCaches::updateCache(
+ sal_Int32 nSdbType, const OUString& rDBName, const OUString& rCommand,
+ o3tl::sorted_vector<ScDPObject*>& rRefs)
+{
+ DBType aType(nSdbType, rDBName, rCommand);
+ CachesType::iterator const it = m_Caches.find(aType);
+ if (it == m_Caches.end())
+ {
+ // not cached.
+ rRefs.clear();
+ return;
+ }
+
+ ScDPCache& rCache = *it->second;
+
+ uno::Reference<sdbc::XRowSet> xRowSet = createRowSet(nSdbType, rDBName, rCommand);
+ if (!xRowSet.is())
+ {
+ rRefs.clear();
+ return;
+ }
+
+ SvNumberFormatter aFormat( comphelper::getProcessComponentContext(), ScGlobal::eLnge);
+ DBConnector aDB(rCache, xRowSet, aFormat.GetNullDate());
+ if (!aDB.isValid())
+ return;
+
+ if (!rCache.InitFromDataBase(aDB))
+ {
+ // initialization failed.
+ rRefs.clear();
+ comphelper::disposeComponent(xRowSet);
+ return;
+ }
+
+ comphelper::disposeComponent(xRowSet);
+ o3tl::sorted_vector<ScDPObject*> aRefs(rCache.GetAllReferences());
+ aRefs.swap(rRefs);
+
+ // Make sure to re-populate the group dimension info.
+ setGroupItemsToCache(rCache, rRefs);
+}
+
+bool ScDPCollection::DBCaches::remove(const ScDPCache* p)
+{
+ CachesType::iterator it = std::find_if(m_Caches.begin(), m_Caches.end(),
+ [&p](const CachesType::value_type& rEntry) { return rEntry.second.get() == p; });
+ if (it != m_Caches.end())
+ {
+ m_Caches.erase(it);
+ return true;
+ }
+ return false;
+}
+
+ScDPCollection::ScDPCollection(ScDocument& rDocument) :
+ mrDoc(rDocument),
+ maSheetCaches(rDocument),
+ maNameCaches(rDocument),
+ maDBCaches(rDocument)
+{
+}
+
+ScDPCollection::ScDPCollection(const ScDPCollection& r) :
+ mrDoc(r.mrDoc),
+ maSheetCaches(r.mrDoc),
+ maNameCaches(r.mrDoc),
+ maDBCaches(r.mrDoc)
+{
+}
+
+ScDPCollection::~ScDPCollection()
+{
+ maTables.clear();
+}
+
+namespace {
+
+/**
+ * Unary predicate to match DP objects by the table ID.
+ */
+class MatchByTable
+{
+ SCTAB mnTab;
+public:
+ explicit MatchByTable(SCTAB nTab) : mnTab(nTab) {}
+
+ bool operator() (const std::unique_ptr<ScDPObject>& rObj) const
+ {
+ return rObj->GetOutRange().aStart.Tab() == mnTab;
+ }
+};
+
+}
+
+TranslateId ScDPCollection::ReloadCache(const ScDPObject* pDPObj, o3tl::sorted_vector<ScDPObject*>& rRefs)
+{
+ if (!pDPObj)
+ return STR_ERR_DATAPILOTSOURCE;
+
+ if (pDPObj->IsSheetData())
+ {
+ // data source is internal sheet.
+ const ScSheetSourceDesc* pDesc = pDPObj->GetSheetDesc();
+ if (!pDesc)
+ return STR_ERR_DATAPILOTSOURCE;
+
+ TranslateId pErrId = pDesc->CheckSourceRange();
+ if (pErrId)
+ return pErrId;
+
+ if (pDesc->HasRangeName())
+ {
+ // cache by named range
+ ScDPCollection::NameCaches& rCaches = GetNameCaches();
+ if (rCaches.hasCache(pDesc->GetRangeName()))
+ rCaches.updateCache(pDesc->GetRangeName(), pDesc->GetSourceRange(), rRefs);
+ else
+ {
+ // Not cached yet. Collect all tables that use this named
+ // range as data source.
+ GetAllTables(pDesc->GetRangeName(), rRefs);
+ }
+ }
+ else
+ {
+ // cache by cell range
+ ScDPCollection::SheetCaches& rCaches = GetSheetCaches();
+ if (rCaches.hasCache(pDesc->GetSourceRange()))
+ rCaches.updateCache(pDesc->GetSourceRange(), rRefs);
+ else
+ {
+ // Not cached yet. Collect all tables that use this range as
+ // data source.
+ GetAllTables(pDesc->GetSourceRange(), rRefs);
+ }
+ }
+ }
+ else if (pDPObj->IsImportData())
+ {
+ // data source is external database.
+ const ScImportSourceDesc* pDesc = pDPObj->GetImportSourceDesc();
+ if (!pDesc)
+ return STR_ERR_DATAPILOTSOURCE;
+
+ ScDPCollection::DBCaches& rCaches = GetDBCaches();
+ if (rCaches.hasCache(pDesc->GetCommandType(), pDesc->aDBName, pDesc->aObject))
+ rCaches.updateCache(
+ pDesc->GetCommandType(), pDesc->aDBName, pDesc->aObject, rRefs);
+ else
+ {
+ // Not cached yet. Collect all tables that use this range as
+ // data source.
+ GetAllTables(pDesc->GetCommandType(), pDesc->aDBName, pDesc->aObject, rRefs);
+ }
+ }
+ return {};
+}
+
+bool ScDPCollection::ReloadGroupsInCache(const ScDPObject* pDPObj, o3tl::sorted_vector<ScDPObject*>& rRefs)
+{
+ if (!pDPObj)
+ return false;
+
+ const ScDPSaveData* pSaveData = pDPObj->GetSaveData();
+ if (!pSaveData)
+ return false;
+
+ // Note: Unlike reloading cache, when modifying the group dimensions the
+ // cache may not have all its references when this method is called.
+ // Therefore, we need to always call GetAllTables to get its correct
+ // references even when the cache exists. This may become a non-issue
+ // if/when we implement loading and saving of pivot caches.
+
+ ScDPCache* pCache = nullptr;
+
+ if (pDPObj->IsSheetData())
+ {
+ // data source is internal sheet.
+ const ScSheetSourceDesc* pDesc = pDPObj->GetSheetDesc();
+ if (!pDesc)
+ return false;
+
+ if (pDesc->HasRangeName())
+ {
+ // cache by named range
+ ScDPCollection::NameCaches& rCaches = GetNameCaches();
+ if (rCaches.hasCache(pDesc->GetRangeName()))
+ pCache = rCaches.getExistingCache(pDesc->GetRangeName());
+ else
+ {
+ // Not cached yet. Cache the source dimensions. Groups will
+ // be added below.
+ pCache = const_cast<ScDPCache*>(
+ rCaches.getCache(pDesc->GetRangeName(), pDesc->GetSourceRange(), nullptr));
+ }
+ GetAllTables(pDesc->GetRangeName(), rRefs);
+ }
+ else
+ {
+ // cache by cell range
+ ScDPCollection::SheetCaches& rCaches = GetSheetCaches();
+ if (rCaches.hasCache(pDesc->GetSourceRange()))
+ pCache = rCaches.getExistingCache(pDesc->GetSourceRange());
+ else
+ {
+ // Not cached yet. Cache the source dimensions. Groups will
+ // be added below.
+ pCache = const_cast<ScDPCache*>(
+ rCaches.getCache(pDesc->GetSourceRange(), nullptr));
+ }
+ GetAllTables(pDesc->GetSourceRange(), rRefs);
+ }
+ }
+ else if (pDPObj->IsImportData())
+ {
+ // data source is external database.
+ const ScImportSourceDesc* pDesc = pDPObj->GetImportSourceDesc();
+ if (!pDesc)
+ return false;
+
+ ScDPCollection::DBCaches& rCaches = GetDBCaches();
+ if (rCaches.hasCache(pDesc->GetCommandType(), pDesc->aDBName, pDesc->aObject))
+ pCache = rCaches.getExistingCache(
+ pDesc->GetCommandType(), pDesc->aDBName, pDesc->aObject);
+ else
+ {
+ // Not cached yet. Cache the source dimensions. Groups will
+ // be added below.
+ pCache = const_cast<ScDPCache*>(
+ rCaches.getCache(pDesc->GetCommandType(), pDesc->aDBName, pDesc->aObject, nullptr));
+ }
+ GetAllTables(pDesc->GetCommandType(), pDesc->aDBName, pDesc->aObject, rRefs);
+ }
+
+ if (!pCache)
+ return false;
+
+ // Clear the existing group/field data from the cache, and rebuild it from the
+ // dimension data.
+ pCache->ClearAllFields();
+ const ScDPDimensionSaveData* pDimData = pSaveData->GetExistingDimensionData();
+ if (pDimData)
+ pDimData->WriteToCache(*pCache);
+ return true;
+}
+
+bool ScDPCollection::GetReferenceGroups(const ScDPObject& rDPObj, const ScDPDimensionSaveData** pGroups) const
+{
+ for (const std::unique_ptr<ScDPObject>& aTable : maTables)
+ {
+ const ScDPObject& rRefObj = *aTable;
+
+ if (&rRefObj == &rDPObj)
+ continue;
+
+ if (rDPObj.IsSheetData()){
+ if(!rRefObj.IsSheetData())
+ continue;
+
+ const ScSheetSourceDesc* pDesc = rDPObj.GetSheetDesc();
+ const ScSheetSourceDesc* pRefDesc = rRefObj.GetSheetDesc();
+ if (pDesc == nullptr || pRefDesc == nullptr)
+ continue;
+
+ if (pDesc->HasRangeName())
+ {
+ if (!pRefDesc->HasRangeName())
+ continue;
+
+ if (pDesc->GetRangeName() == pRefDesc->GetRangeName())
+ {
+ *pGroups = rRefObj.GetSaveData()->GetExistingDimensionData();
+ return true;
+ }
+ }
+ else
+ {
+ if (pRefDesc->HasRangeName())
+ continue;
+
+ if (pDesc->GetSourceRange() == pRefDesc->GetSourceRange())
+ {
+ *pGroups = rRefObj.GetSaveData()->GetExistingDimensionData();
+ return true;
+ }
+ }
+ }
+ else if (rDPObj.IsImportData())
+ {
+ if (!rRefObj.IsImportData ())
+ continue;
+
+ const ScImportSourceDesc* pDesc = rDPObj.GetImportSourceDesc();
+ const ScImportSourceDesc* pRefDesc = rRefObj.GetImportSourceDesc();
+ if (pDesc == nullptr || pRefDesc == nullptr)
+ continue;
+
+ if (pDesc->aDBName == pRefDesc->aDBName &&
+ pDesc->aObject == pRefDesc->aObject &&
+ pDesc->GetCommandType() == pRefDesc->GetCommandType())
+ {
+ *pGroups = rRefObj.GetSaveData()->GetExistingDimensionData();
+ return true;
+ }
+
+ }
+ }
+ return false;
+}
+
+
+void ScDPCollection::DeleteOnTab( SCTAB nTab )
+{
+ maTables.erase( std::remove_if(maTables.begin(), maTables.end(), MatchByTable(nTab)), maTables.end());
+}
+
+void ScDPCollection::UpdateReference( UpdateRefMode eUpdateRefMode,
+ const ScRange& r, SCCOL nDx, SCROW nDy, SCTAB nDz )
+{
+ for (auto& rxTable : maTables)
+ rxTable->UpdateReference(eUpdateRefMode, r, nDx, nDy, nDz);
+
+ // Update the source ranges of the caches.
+ maSheetCaches.updateReference(eUpdateRefMode, r, nDx, nDy, nDz);
+}
+
+void ScDPCollection::CopyToTab( SCTAB nOld, SCTAB nNew )
+{
+ TablesType aAdded;
+ for (const auto& rxTable : maTables)
+ {
+ const ScDPObject& rObj = *rxTable;
+ ScRange aOutRange = rObj.GetOutRange();
+ if (aOutRange.aStart.Tab() != nOld)
+ continue;
+
+ ScAddress& s = aOutRange.aStart;
+ ScAddress& e = aOutRange.aEnd;
+ s.SetTab(nNew);
+ e.SetTab(nNew);
+ ScDPObject* pNew = new ScDPObject(rObj);
+ pNew->SetOutRange(aOutRange);
+ mrDoc.ApplyFlagsTab(s.Col(), s.Row(), e.Col(), e.Row(), s.Tab(), ScMF::DpTable);
+ aAdded.push_back(std::unique_ptr<ScDPObject>(pNew));
+ }
+
+ std::move(aAdded.begin(), aAdded.end(), std::back_inserter(maTables));
+}
+
+bool ScDPCollection::RefsEqual( const ScDPCollection& r ) const
+{
+ return std::equal(maTables.begin(), maTables.end(), r.maTables.begin(), r.maTables.end(),
+ [](const TablesType::value_type& a, const TablesType::value_type& b) { return a->RefsEqual(*b); });
+}
+
+void ScDPCollection::WriteRefsTo( ScDPCollection& r ) const
+{
+ if ( maTables.size() == r.maTables.size() )
+ {
+ //TODO: assert equal names?
+ TablesType::iterator itr2 = r.maTables.begin();
+ for (const auto& rxTable : maTables)
+ {
+ rxTable->WriteRefsTo(**itr2);
+ ++itr2;
+ }
+ }
+ else
+ {
+ // #i8180# If data pilot tables were deleted with their sheet,
+ // this collection contains extra entries that must be restored.
+ // Matching objects are found by their names.
+ size_t nSrcSize = maTables.size();
+ size_t nDestSize = r.maTables.size();
+ OSL_ENSURE( nSrcSize >= nDestSize, "WriteRefsTo: missing entries in document" );
+ for (size_t nSrcPos = 0; nSrcPos < nSrcSize; ++nSrcPos)
+ {
+ const ScDPObject& rSrcObj = *maTables[nSrcPos];
+ const OUString& aName = rSrcObj.GetName();
+ bool bFound = false;
+ for (size_t nDestPos = 0; nDestPos < nDestSize && !bFound; ++nDestPos)
+ {
+ ScDPObject& rDestObj = *r.maTables[nDestPos];
+ if (rDestObj.GetName() == aName)
+ {
+ rSrcObj.WriteRefsTo(rDestObj); // found object, copy refs
+ bFound = true;
+ }
+ }
+
+ if (!bFound)
+ {
+ // none found, re-insert deleted object (see ScUndoDataPilot::Undo)
+ r.InsertNewTable(std::make_unique<ScDPObject>(rSrcObj));
+ }
+ }
+ OSL_ENSURE( maTables.size() == r.maTables.size(), "WriteRefsTo: couldn't restore all entries" );
+ }
+}
+
+size_t ScDPCollection::GetCount() const
+{
+ return maTables.size();
+}
+
+ScDPObject& ScDPCollection::operator [](size_t nIndex)
+{
+ return *maTables[nIndex];
+}
+
+const ScDPObject& ScDPCollection::operator [](size_t nIndex) const
+{
+ return *maTables[nIndex];
+}
+
+ScDPObject* ScDPCollection::GetByName(std::u16string_view rName) const
+{
+ for (std::unique_ptr<ScDPObject> const & pObject : maTables)
+ {
+ if (pObject->GetName() == rName)
+ return pObject.get();
+ }
+
+ return nullptr;
+}
+
+OUString ScDPCollection::CreateNewName() const
+{
+ size_t n = maTables.size();
+ for (size_t nAdd = 0; nAdd <= n; ++nAdd) // nCount+1 tries
+ {
+ OUString aNewName = "DataPilot" + OUString::number(1 + nAdd);
+ if (std::none_of(maTables.begin(), maTables.end(),
+ [&aNewName](const TablesType::value_type& rxObj) { return rxObj->GetName() == aNewName; }))
+ return aNewName; // found unused Name
+ }
+ return OUString(); // should not happen
+}
+
+void ScDPCollection::FreeTable(const ScDPObject* pDPObject)
+{
+ const ScRange& rOutRange = pDPObject->GetOutRange();
+ const ScAddress& s = rOutRange.aStart;
+ const ScAddress& e = rOutRange.aEnd;
+ mrDoc.RemoveFlagsTab(s.Col(), s.Row(), e.Col(), e.Row(), s.Tab(), ScMF::DpTable);
+
+ auto funcRemoveCondition = [pDPObject] (std::unique_ptr<ScDPObject> const & pCurrent)
+ {
+ return pCurrent.get() == pDPObject;
+ };
+
+ maTables.erase(std::remove_if(maTables.begin(), maTables.end(), funcRemoveCondition), maTables.end());
+}
+
+ScDPObject* ScDPCollection::InsertNewTable(std::unique_ptr<ScDPObject> pDPObj)
+{
+ const ScRange& rOutRange = pDPObj->GetOutRange();
+ const ScAddress& s = rOutRange.aStart;
+ const ScAddress& e = rOutRange.aEnd;
+ mrDoc.ApplyFlagsTab(s.Col(), s.Row(), e.Col(), e.Row(), s.Tab(), ScMF::DpTable);
+
+ maTables.push_back(std::move(pDPObj));
+ return maTables.back().get();
+}
+
+bool ScDPCollection::HasTable(const ScDPObject* pDPObj) const
+{
+ for (const std::unique_ptr<ScDPObject>& aTable : maTables)
+ {
+ if (aTable.get() == pDPObj)
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+ScDPCollection::SheetCaches& ScDPCollection::GetSheetCaches()
+{
+ return maSheetCaches;
+}
+
+const ScDPCollection::SheetCaches& ScDPCollection::GetSheetCaches() const
+{
+ return maSheetCaches;
+}
+
+ScDPCollection::NameCaches& ScDPCollection::GetNameCaches()
+{
+ return maNameCaches;
+}
+
+const ScDPCollection::NameCaches& ScDPCollection::GetNameCaches() const
+{
+ return maNameCaches;
+}
+
+ScDPCollection::DBCaches& ScDPCollection::GetDBCaches()
+{
+ return maDBCaches;
+}
+
+const ScDPCollection::DBCaches& ScDPCollection::GetDBCaches() const
+{
+ return maDBCaches;
+}
+
+ScRangeList ScDPCollection::GetAllTableRanges( SCTAB nTab ) const
+{
+ return std::for_each(maTables.begin(), maTables.end(), AccumulateOutputRanges(nTab)).getRanges();
+}
+
+bool ScDPCollection::IntersectsTableByColumns( SCCOL nCol1, SCCOL nCol2, SCROW nRow, SCTAB nTab ) const
+{
+ return std::any_of(maTables.begin(), maTables.end(), FindIntersectingTableByColumns(nCol1, nCol2, nRow, nTab));
+}
+
+bool ScDPCollection::IntersectsTableByRows( SCCOL nCol, SCROW nRow1, SCROW nRow2, SCTAB nTab ) const
+{
+ return std::any_of(maTables.begin(), maTables.end(), FindIntersectingTableByRows(nCol, nRow1, nRow2, nTab));
+}
+
+bool ScDPCollection::HasTable( const ScRange& rRange ) const
+{
+ return std::any_of(maTables.begin(), maTables.end(), FindIntersectingTable(rRange));
+}
+
+#if DEBUG_PIVOT_TABLE
+
+namespace {
+
+struct DumpTable
+{
+ void operator() (const std::unique_ptr<ScDPObject>& rObj) const
+ {
+ cout << "-- '" << rObj->GetName() << "'" << endl;
+ ScDPSaveData* pSaveData = rObj->GetSaveData();
+ if (!pSaveData)
+ return;
+
+ pSaveData->Dump();
+
+ cout << endl; // blank line
+ }
+};
+
+}
+
+void ScDPCollection::DumpTables() const
+{
+ std::for_each(maTables.begin(), maTables.end(), DumpTable());
+}
+
+#endif
+
+void ScDPCollection::RemoveCache(const ScDPCache* pCache)
+{
+ if (maSheetCaches.remove(pCache))
+ // sheet cache removed.
+ return;
+
+ if (maNameCaches.remove(pCache))
+ // named range cache removed.
+ return;
+
+ if (maDBCaches.remove(pCache))
+ // database cache removed.
+ return;
+}
+
+void ScDPCollection::GetAllTables(const ScRange& rSrcRange, o3tl::sorted_vector<ScDPObject*>& rRefs) const
+{
+ o3tl::sorted_vector<ScDPObject*> aRefs;
+ for (const auto& rxTable : maTables)
+ {
+ const ScDPObject& rObj = *rxTable;
+ if (!rObj.IsSheetData())
+ // Source is not a sheet range.
+ continue;
+
+ const ScSheetSourceDesc* pDesc = rObj.GetSheetDesc();
+ if (!pDesc)
+ continue;
+
+ if (pDesc->HasRangeName())
+ // This table has a range name as its source.
+ continue;
+
+ if (pDesc->GetSourceRange() != rSrcRange)
+ // Different source range.
+ continue;
+
+ aRefs.insert(const_cast<ScDPObject*>(&rObj));
+ }
+
+ rRefs.swap(aRefs);
+}
+
+void ScDPCollection::GetAllTables(std::u16string_view rSrcName, o3tl::sorted_vector<ScDPObject*>& rRefs) const
+{
+ o3tl::sorted_vector<ScDPObject*> aRefs;
+ for (const auto& rxTable : maTables)
+ {
+ const ScDPObject& rObj = *rxTable;
+ if (!rObj.IsSheetData())
+ // Source is not a sheet range.
+ continue;
+
+ const ScSheetSourceDesc* pDesc = rObj.GetSheetDesc();
+ if (!pDesc)
+ continue;
+
+ if (!pDesc->HasRangeName())
+ // This table probably has a sheet range as its source.
+ continue;
+
+ if (pDesc->GetRangeName() != rSrcName)
+ // Different source name.
+ continue;
+
+ aRefs.insert(const_cast<ScDPObject*>(&rObj));
+ }
+
+ rRefs.swap(aRefs);
+}
+
+void ScDPCollection::GetAllTables(
+ sal_Int32 nSdbType, std::u16string_view rDBName, std::u16string_view rCommand,
+ o3tl::sorted_vector<ScDPObject*>& rRefs) const
+{
+ o3tl::sorted_vector<ScDPObject*> aRefs;
+ for (const auto& rxTable : maTables)
+ {
+ const ScDPObject& rObj = *rxTable;
+ if (!rObj.IsImportData())
+ // Source data is not a database.
+ continue;
+
+ const ScImportSourceDesc* pDesc = rObj.GetImportSourceDesc();
+ if (!pDesc)
+ continue;
+
+ if (pDesc->aDBName != rDBName || pDesc->aObject != rCommand || pDesc->GetCommandType() != nSdbType)
+ // Different database source.
+ continue;
+
+ aRefs.insert(const_cast<ScDPObject*>(&rObj));
+ }
+
+ rRefs.swap(aRefs);
+}
+
+bool operator<(const ScDPCollection::DBType& left, const ScDPCollection::DBType& right)
+{
+ if (left.mnSdbType != right.mnSdbType)
+ return left.mnSdbType < right.mnSdbType;
+
+ if (left.maDBName != right.maDBName)
+ return left.maDBName < right.maDBName;
+
+ return left.maCommand < right.maCommand;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dpoutput.cxx b/sc/source/core/data/dpoutput.cxx
new file mode 100644
index 000000000..bf2109b30
--- /dev/null
+++ b/sc/source/core/data/dpoutput.cxx
@@ -0,0 +1,1781 @@
+/* -*- 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/sequence.hxx>
+#include <editeng/borderline.hxx>
+#include <editeng/boxitem.hxx>
+#include <editeng/wghtitem.hxx>
+#include <editeng/justifyitem.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+#include <svl/itemset.hxx>
+
+#include <dpoutput.hxx>
+#include <document.hxx>
+#include <attrib.hxx>
+#include <formula/errorcodes.hxx>
+#include <miscuno.hxx>
+#include <globstr.hrc>
+#include <stlpool.hxx>
+#include <stlsheet.hxx>
+#include <scresid.hxx>
+#include <unonames.hxx>
+#include <strings.hrc>
+#include <stringutil.hxx>
+#include <dputil.hxx>
+
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/sheet/DataPilotTableHeaderData.hpp>
+#include <com/sun/star/sheet/DataPilotFieldOrientation.hpp>
+#include <com/sun/star/sheet/DataPilotTablePositionData.hpp>
+#include <com/sun/star/sheet/DataPilotTableResultData.hpp>
+#include <com/sun/star/sheet/MemberResultFlags.hpp>
+#include <com/sun/star/sheet/DataResultFlags.hpp>
+#include <com/sun/star/sheet/DataPilotTablePositionType.hpp>
+#include <com/sun/star/sheet/GeneralFunction2.hpp>
+#include <com/sun/star/sheet/MemberResult.hpp>
+#include <com/sun/star/sheet/XDataPilotMemberResults.hpp>
+#include <com/sun/star/sheet/XDataPilotResults.hpp>
+#include <com/sun/star/sheet/XDimensionsSupplier.hpp>
+#include <com/sun/star/sheet/XHierarchiesSupplier.hpp>
+#include <com/sun/star/sheet/XLevelsSupplier.hpp>
+#include <com/sun/star/sheet/XMembersAccess.hpp>
+#include <com/sun/star/sheet/XMembersSupplier.hpp>
+
+#include <limits>
+#include <string_view>
+#include <vector>
+
+using namespace com::sun::star;
+using ::std::vector;
+using ::com::sun::star::beans::XPropertySet;
+using ::com::sun::star::uno::Sequence;
+using ::com::sun::star::uno::UNO_QUERY;
+using ::com::sun::star::uno::Reference;
+using ::com::sun::star::sheet::DataPilotTablePositionData;
+using ::com::sun::star::sheet::DataPilotTableResultData;
+
+#define SC_DP_FRAME_INNER_BOLD 20
+#define SC_DP_FRAME_OUTER_BOLD 40
+
+#define SC_DP_FRAME_COLOR Color(0,0,0) //( 0x20, 0x40, 0x68 )
+
+struct ScDPOutLevelData
+{
+ tools::Long mnDim;
+ tools::Long mnHier;
+ tools::Long mnLevel;
+ tools::Long mnDimPos;
+ sal_uInt32 mnSrcNumFmt; /// Prevailing number format used in the source data.
+ uno::Sequence<sheet::MemberResult> maResult;
+ OUString maName; /// Name is the internal field name.
+ OUString maCaption; /// Caption is the name visible in the output table.
+ bool mbHasHiddenMember:1;
+ bool mbDataLayout:1;
+ bool mbPageDim:1;
+
+ ScDPOutLevelData(tools::Long nDim, tools::Long nHier, tools::Long nLevel, tools::Long nDimPos, sal_uInt32 nSrcNumFmt, const uno::Sequence<sheet::MemberResult> &aResult,
+ const OUString &aName, const OUString &aCaption, bool bHasHiddenMember, bool bDataLayout, bool bPageDim) :
+ mnDim(nDim), mnHier(nHier), mnLevel(nLevel), mnDimPos(nDimPos), mnSrcNumFmt(nSrcNumFmt), maResult(aResult),
+ maName(aName), maCaption(aCaption), mbHasHiddenMember(bHasHiddenMember), mbDataLayout(bDataLayout),
+ mbPageDim(bPageDim)
+ {
+ }
+
+ // bug (73840) in uno::Sequence - copy and then assign doesn't work!
+};
+
+
+
+namespace {
+ struct ScDPOutLevelDataComparator
+ {
+ bool operator()(const ScDPOutLevelData & rA, const ScDPOutLevelData & rB)
+ {
+ return rA.mnDimPos<rB.mnDimPos || ( rA.mnDimPos==rB.mnDimPos && rA.mnHier<rB.mnHier ) ||
+ ( rA.mnDimPos==rB.mnDimPos && rA.mnHier==rB.mnHier && rA.mnLevel<rB.mnLevel );
+ }
+ };
+
+
+class ScDPOutputImpl
+{
+ ScDocument* mpDoc;
+ sal_uInt16 mnTab;
+ ::std::vector< bool > mbNeedLineCols;
+ ::std::vector< SCCOL > mnCols;
+
+ ::std::vector< bool > mbNeedLineRows;
+ ::std::vector< SCROW > mnRows;
+
+ SCCOL mnTabStartCol;
+ SCROW mnTabStartRow;
+
+ SCCOL mnDataStartCol;
+ SCROW mnDataStartRow;
+ SCCOL mnTabEndCol;
+ SCROW mnTabEndRow;
+
+public:
+ ScDPOutputImpl( ScDocument* pDoc, sal_uInt16 nTab,
+ SCCOL nTabStartCol,
+ SCROW nTabStartRow,
+ SCCOL nDataStartCol,
+ SCROW nDataStartRow,
+ SCCOL nTabEndCol,
+ SCROW nTabEndRow );
+ bool AddRow( SCROW nRow );
+ bool AddCol( SCCOL nCol );
+
+ void OutputDataArea();
+ void OutputBlockFrame ( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, bool bHori = false );
+
+};
+
+void ScDPOutputImpl::OutputDataArea()
+{
+ AddRow( mnDataStartRow );
+ AddCol( mnDataStartCol );
+
+ mnCols.push_back( mnTabEndCol+1); //set last row bottom
+ mnRows.push_back( mnTabEndRow+1); //set last col bottom
+
+ bool bAllRows = ( ( mnTabEndRow - mnDataStartRow + 2 ) == static_cast<SCROW>(mnRows.size()) );
+
+ std::sort( mnCols.begin(), mnCols.end());
+ std::sort( mnRows.begin(), mnRows.end());
+
+ for( SCCOL nCol = 0; nCol < static_cast<SCCOL>(mnCols.size())-1; nCol ++ )
+ {
+ if ( !bAllRows )
+ {
+ if ( nCol < static_cast<SCCOL>(mnCols.size())-2)
+ {
+ for ( SCROW i = nCol%2; i < static_cast<SCROW>(mnRows.size())-2; i +=2 )
+ OutputBlockFrame( mnCols[nCol], mnRows[i], mnCols[nCol+1]-1, mnRows[i+1]-1 );
+ if ( mnRows.size()>=2 )
+ OutputBlockFrame( mnCols[nCol], mnRows[mnRows.size()-2], mnCols[nCol+1]-1, mnRows[mnRows.size()-1]-1 );
+ }
+ else
+ {
+ for ( SCROW i = 0 ; i < static_cast<SCROW>(mnRows.size())-1; i++ )
+ OutputBlockFrame( mnCols[nCol], mnRows[i], mnCols[nCol+1]-1, mnRows[i+1]-1 );
+ }
+ }
+ else
+ OutputBlockFrame( mnCols[nCol], mnRows.front(), mnCols[nCol+1]-1, mnRows.back()-1, bAllRows );
+ }
+ //out put rows area outer framer
+ if ( mnTabStartCol != mnDataStartCol )
+ {
+ if ( mnTabStartRow != mnDataStartRow )
+ OutputBlockFrame( mnTabStartCol, mnTabStartRow, mnDataStartCol-1, mnDataStartRow-1 );
+ OutputBlockFrame( mnTabStartCol, mnDataStartRow, mnDataStartCol-1, mnTabEndRow );
+ }
+ //out put cols area outer framer
+ OutputBlockFrame( mnDataStartCol, mnTabStartRow, mnTabEndCol, mnDataStartRow-1 );
+}
+
+ScDPOutputImpl::ScDPOutputImpl( ScDocument* pDoc, sal_uInt16 nTab,
+ SCCOL nTabStartCol,
+ SCROW nTabStartRow,
+ SCCOL nDataStartCol,
+ SCROW nDataStartRow,
+ SCCOL nTabEndCol,
+ SCROW nTabEndRow ):
+ mpDoc( pDoc ),
+ mnTab( nTab ),
+ mnTabStartCol( nTabStartCol ),
+ mnTabStartRow( nTabStartRow ),
+ mnDataStartCol ( nDataStartCol ),
+ mnDataStartRow ( nDataStartRow ),
+ mnTabEndCol( nTabEndCol ),
+ mnTabEndRow( nTabEndRow )
+{
+ mbNeedLineCols.resize( nTabEndCol-nDataStartCol+1, false );
+ mbNeedLineRows.resize( nTabEndRow-nDataStartRow+1, false );
+
+}
+
+bool ScDPOutputImpl::AddRow( SCROW nRow )
+{
+ if ( !mbNeedLineRows[ nRow - mnDataStartRow ] )
+ {
+ mbNeedLineRows[ nRow - mnDataStartRow ] = true;
+ mnRows.push_back( nRow );
+ return true;
+ }
+ else
+ return false;
+}
+
+bool ScDPOutputImpl::AddCol( SCCOL nCol )
+{
+
+ if ( !mbNeedLineCols[ nCol - mnDataStartCol ] )
+ {
+ mbNeedLineCols[ nCol - mnDataStartCol ] = true;
+ mnCols.push_back( nCol );
+ return true;
+ }
+ else
+ return false;
+}
+
+void ScDPOutputImpl::OutputBlockFrame ( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, bool bHori )
+{
+ Color color = SC_DP_FRAME_COLOR;
+ ::editeng::SvxBorderLine aLine( &color, SC_DP_FRAME_INNER_BOLD );
+ ::editeng::SvxBorderLine aOutLine( &color, SC_DP_FRAME_OUTER_BOLD );
+
+ SvxBoxItem aBox( ATTR_BORDER );
+
+ if ( nStartCol == mnTabStartCol )
+ aBox.SetLine(&aOutLine, SvxBoxItemLine::LEFT);
+ else
+ aBox.SetLine(&aLine, SvxBoxItemLine::LEFT);
+
+ if ( nStartRow == mnTabStartRow )
+ aBox.SetLine(&aOutLine, SvxBoxItemLine::TOP);
+ else
+ aBox.SetLine(&aLine, SvxBoxItemLine::TOP);
+
+ if ( nEndCol == mnTabEndCol ) //bottom row
+ aBox.SetLine(&aOutLine, SvxBoxItemLine::RIGHT);
+ else
+ aBox.SetLine(&aLine, SvxBoxItemLine::RIGHT);
+
+ if ( nEndRow == mnTabEndRow ) //bottom
+ aBox.SetLine(&aOutLine, SvxBoxItemLine::BOTTOM);
+ else
+ aBox.SetLine(&aLine, SvxBoxItemLine::BOTTOM);
+
+ SvxBoxInfoItem aBoxInfo( ATTR_BORDER_INNER );
+ aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::VERT,false );
+ if ( bHori )
+ {
+ aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::HORI);
+ aBoxInfo.SetLine( &aLine, SvxBoxInfoItemLine::HORI );
+ }
+ else
+ aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::HORI,false );
+
+ aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::DISTANCE,false);
+
+ mpDoc->ApplyFrameAreaTab(ScRange(nStartCol, nStartRow, mnTab, nEndCol, nEndRow , mnTab), aBox, aBoxInfo);
+
+}
+
+void lcl_SetStyleById(ScDocument* pDoc, SCTAB nTab,
+ SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ TranslateId pStrId)
+{
+ if ( nCol1 > nCol2 || nRow1 > nRow2 )
+ {
+ OSL_FAIL("SetStyleById: invalid range");
+ return;
+ }
+
+ OUString aStyleName = ScResId(pStrId);
+ ScStyleSheetPool* pStlPool = pDoc->GetStyleSheetPool();
+ ScStyleSheet* pStyle = static_cast<ScStyleSheet*>( pStlPool->Find( aStyleName, SfxStyleFamily::Para ) );
+ if (!pStyle)
+ {
+ // create new style (was in ScPivot::SetStyle)
+
+ pStyle = static_cast<ScStyleSheet*>( &pStlPool->Make( aStyleName, SfxStyleFamily::Para,
+ SfxStyleSearchBits::UserDefined ) );
+ pStyle->SetParent( ScResId(STR_STYLENAME_STANDARD) );
+ SfxItemSet& rSet = pStyle->GetItemSet();
+ if (pStrId == STR_PIVOT_STYLENAME_RESULT || pStrId == STR_PIVOT_STYLENAME_TITLE){
+ rSet.Put( SvxWeightItem( WEIGHT_BOLD, ATTR_FONT_WEIGHT ) );
+ rSet.Put( SvxWeightItem( WEIGHT_BOLD, ATTR_CJK_FONT_WEIGHT ) );
+ rSet.Put( SvxWeightItem( WEIGHT_BOLD, ATTR_CTL_FONT_WEIGHT ) );
+ }
+ if (pStrId == STR_PIVOT_STYLENAME_CATEGORY || pStrId == STR_PIVOT_STYLENAME_TITLE)
+ rSet.Put( SvxHorJustifyItem( SvxCellHorJustify::Left, ATTR_HOR_JUSTIFY ) );
+ }
+
+ pDoc->ApplyStyleAreaTab( nCol1, nRow1, nCol2, nRow2, nTab, *pStyle );
+}
+
+void lcl_SetFrame( ScDocument* pDoc, SCTAB nTab,
+ SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ sal_uInt16 nWidth )
+{
+ ::editeng::SvxBorderLine aLine(nullptr, nWidth, SvxBorderLineStyle::SOLID);
+ SvxBoxItem aBox( ATTR_BORDER );
+ aBox.SetLine(&aLine, SvxBoxItemLine::LEFT);
+ aBox.SetLine(&aLine, SvxBoxItemLine::TOP);
+ aBox.SetLine(&aLine, SvxBoxItemLine::RIGHT);
+ aBox.SetLine(&aLine, SvxBoxItemLine::BOTTOM);
+ SvxBoxInfoItem aBoxInfo( ATTR_BORDER_INNER );
+ aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::HORI,false);
+ aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::VERT,false);
+ aBoxInfo.SetValid(SvxBoxInfoItemValidFlags::DISTANCE,false);
+
+ pDoc->ApplyFrameAreaTab(ScRange(nCol1, nRow1, nTab, nCol2, nRow2, nTab), aBox, aBoxInfo);
+}
+
+void lcl_FillNumberFormats( std::unique_ptr<sal_uInt32[]>& rFormats, sal_Int32& rCount,
+ const uno::Reference<sheet::XDataPilotMemberResults>& xLevRes,
+ const uno::Reference<container::XIndexAccess>& xDims )
+{
+ if ( rFormats )
+ return; // already set
+
+ // xLevRes is from the data layout dimension
+ //TODO: use result sequence from ScDPOutLevelData!
+
+ uno::Sequence<sheet::MemberResult> aResult = xLevRes->getResults();
+
+ tools::Long nSize = aResult.getLength();
+ if (!nSize)
+ return;
+
+ // get names/formats for all data dimensions
+ //TODO: merge this with the loop to collect ScDPOutLevelData?
+
+ std::vector <OUString> aDataNames;
+ std::vector <sal_uInt32> aDataFormats;
+ sal_Int32 nDimCount = xDims->getCount();
+ sal_Int32 nDim = 0;
+ for ( ; nDim < nDimCount ; nDim++)
+ {
+ uno::Reference<uno::XInterface> xDim(xDims->getByIndex(nDim), uno::UNO_QUERY);
+ uno::Reference<beans::XPropertySet> xDimProp( xDim, uno::UNO_QUERY );
+ uno::Reference<container::XNamed> xDimName( xDim, uno::UNO_QUERY );
+ if ( xDimProp.is() && xDimName.is() )
+ {
+ sheet::DataPilotFieldOrientation eDimOrient =
+ ScUnoHelpFunctions::GetEnumProperty(
+ xDimProp, SC_UNO_DP_ORIENTATION,
+ sheet::DataPilotFieldOrientation_HIDDEN );
+ if ( eDimOrient == sheet::DataPilotFieldOrientation_DATA )
+ {
+ aDataNames.push_back(xDimName->getName());
+ tools::Long nFormat = ScUnoHelpFunctions::GetLongProperty(
+ xDimProp,
+ SC_UNONAME_NUMFMT );
+ aDataFormats.push_back(nFormat);
+ }
+ }
+ }
+
+ if (aDataFormats.empty())
+ return;
+
+ const sheet::MemberResult* pArray = aResult.getConstArray();
+
+ OUString aName;
+ sal_uInt32* pNumFmt = new sal_uInt32[nSize];
+ if (aDataFormats.size() == 1)
+ {
+ // only one data dimension -> use its numberformat everywhere
+ tools::Long nFormat = aDataFormats[0];
+ for (tools::Long nPos=0; nPos<nSize; nPos++)
+ pNumFmt[nPos] = nFormat;
+ }
+ else
+ {
+ for (tools::Long nPos=0; nPos<nSize; nPos++)
+ {
+ // if CONTINUE bit is set, keep previous name
+ //TODO: keep number format instead!
+ if ( !(pArray[nPos].Flags & sheet::MemberResultFlags::CONTINUE) )
+ aName = pArray[nPos].Name;
+
+ sal_uInt32 nFormat = 0;
+ for (size_t i=0; i<aDataFormats.size(); i++)
+ if (aName == aDataNames[i]) //TODO: search more efficiently?
+ {
+ nFormat = aDataFormats[i];
+ break;
+ }
+ pNumFmt[nPos] = nFormat;
+ }
+ }
+
+ rFormats.reset( pNumFmt );
+ rCount = nSize;
+}
+
+sal_uInt32 lcl_GetFirstNumberFormat( const uno::Reference<container::XIndexAccess>& xDims )
+{
+ tools::Long nDimCount = xDims->getCount();
+ for (tools::Long nDim=0; nDim<nDimCount; nDim++)
+ {
+ uno::Reference<beans::XPropertySet> xDimProp(xDims->getByIndex(nDim), uno::UNO_QUERY);
+ if ( xDimProp.is() )
+ {
+ sheet::DataPilotFieldOrientation eDimOrient =
+ ScUnoHelpFunctions::GetEnumProperty(
+ xDimProp, SC_UNO_DP_ORIENTATION,
+ sheet::DataPilotFieldOrientation_HIDDEN );
+ if ( eDimOrient == sheet::DataPilotFieldOrientation_DATA )
+ {
+ tools::Long nFormat = ScUnoHelpFunctions::GetLongProperty(
+ xDimProp,
+ SC_UNONAME_NUMFMT );
+
+ return nFormat; // use format from first found data dimension
+ }
+ }
+ }
+
+ return 0; // none found
+}
+
+bool lcl_MemberEmpty( const uno::Sequence<sheet::MemberResult>& rSeq )
+{
+ // used to skip levels that have no members
+
+ return std::none_of(rSeq.begin(), rSeq.end(),
+ [](const sheet::MemberResult& rMem) {
+ return rMem.Flags & sheet::MemberResultFlags::HASMEMBER; });
+}
+
+/**
+ * Get visible page dimension members as results, except that, if all
+ * members are visible, then this function returns empty result.
+ */
+uno::Sequence<sheet::MemberResult> getVisiblePageMembersAsResults( const uno::Reference<uno::XInterface>& xLevel )
+{
+ if (!xLevel.is())
+ return uno::Sequence<sheet::MemberResult>();
+
+ uno::Reference<sheet::XMembersSupplier> xMSupplier(xLevel, UNO_QUERY);
+ if (!xMSupplier.is())
+ return uno::Sequence<sheet::MemberResult>();
+
+ uno::Reference<sheet::XMembersAccess> xNA = xMSupplier->getMembers();
+ if (!xNA.is())
+ return uno::Sequence<sheet::MemberResult>();
+
+ std::vector<sheet::MemberResult> aRes;
+ const uno::Sequence<OUString> aNames = xNA->getElementNames();
+ for (const OUString& rName : aNames)
+ {
+ xNA->getByName(rName);
+
+ uno::Reference<beans::XPropertySet> xMemPS(xNA->getByName(rName), UNO_QUERY);
+ if (!xMemPS.is())
+ continue;
+
+ OUString aCaption = ScUnoHelpFunctions::GetStringProperty(xMemPS, SC_UNO_DP_LAYOUTNAME, OUString());
+ if (aCaption.isEmpty())
+ aCaption = rName;
+
+ bool bVisible = ScUnoHelpFunctions::GetBoolProperty(xMemPS, SC_UNO_DP_ISVISIBLE);
+
+ if (bVisible)
+ {
+ /* TODO: any numeric value to obtain? */
+ aRes.emplace_back(rName, aCaption, 0, std::numeric_limits<double>::quiet_NaN());
+ }
+ }
+
+ if (o3tl::make_unsigned(aNames.getLength()) == aRes.size())
+ // All members are visible. Return empty result.
+ return uno::Sequence<sheet::MemberResult>();
+
+ return ScUnoHelpFunctions::VectorToSequence(aRes);
+}
+
+}
+
+ScDPOutput::ScDPOutput( ScDocument* pD, const uno::Reference<sheet::XDimensionsSupplier>& xSrc,
+ const ScAddress& rPos, bool bFilter ) :
+ pDoc( pD ),
+ xSource( xSrc ),
+ aStartPos( rPos ),
+ nColFmtCount( 0 ),
+ nRowFmtCount( 0 ),
+ nSingleNumFmt( 0 ),
+ nColCount(0),
+ nRowCount(0),
+ nHeaderSize(0),
+ bDoFilter(bFilter),
+ bResultsError(false),
+ bSizesValid(false),
+ bSizeOverflow(false),
+ mbHeaderLayout(false)
+{
+ nTabStartCol = nMemberStartCol = nDataStartCol = nTabEndCol = 0;
+ nTabStartRow = nMemberStartRow = nDataStartRow = nTabEndRow = 0;
+
+ uno::Reference<sheet::XDataPilotResults> xResult( xSource, uno::UNO_QUERY );
+ if ( xSource.is() && xResult.is() )
+ {
+ // get dimension results:
+
+ uno::Reference<container::XIndexAccess> xDims =
+ new ScNameToIndexAccess( xSource->getDimensions() );
+ tools::Long nDimCount = xDims->getCount();
+ for (tools::Long nDim=0; nDim<nDimCount; nDim++)
+ {
+ uno::Reference<uno::XInterface> xDim(xDims->getByIndex(nDim), uno::UNO_QUERY);
+ uno::Reference<beans::XPropertySet> xDimProp( xDim, uno::UNO_QUERY );
+ uno::Reference<sheet::XHierarchiesSupplier> xDimSupp( xDim, uno::UNO_QUERY );
+ if ( xDimProp.is() && xDimSupp.is() )
+ {
+ sheet::DataPilotFieldOrientation eDimOrient =
+ ScUnoHelpFunctions::GetEnumProperty(
+ xDimProp, SC_UNO_DP_ORIENTATION,
+ sheet::DataPilotFieldOrientation_HIDDEN );
+ tools::Long nDimPos = ScUnoHelpFunctions::GetLongProperty( xDimProp,
+ SC_UNO_DP_POSITION );
+ bool bIsDataLayout = ScUnoHelpFunctions::GetBoolProperty(
+ xDimProp, SC_UNO_DP_ISDATALAYOUT);
+ bool bHasHiddenMember = ScUnoHelpFunctions::GetBoolProperty(
+ xDimProp, SC_UNO_DP_HAS_HIDDEN_MEMBER);
+ sal_Int32 nNumFmt = ScUnoHelpFunctions::GetLongProperty(
+ xDimProp, SC_UNO_DP_NUMBERFO);
+
+ if ( eDimOrient != sheet::DataPilotFieldOrientation_HIDDEN )
+ {
+ uno::Reference<container::XIndexAccess> xHiers =
+ new ScNameToIndexAccess( xDimSupp->getHierarchies() );
+ tools::Long nHierarchy = ScUnoHelpFunctions::GetLongProperty(
+ xDimProp,
+ SC_UNO_DP_USEDHIERARCHY );
+ if ( nHierarchy >= xHiers->getCount() )
+ nHierarchy = 0;
+
+ uno::Reference<sheet::XLevelsSupplier> xHierSupp(xHiers->getByIndex(nHierarchy),
+ uno::UNO_QUERY);
+ if ( xHierSupp.is() )
+ {
+ uno::Reference<container::XIndexAccess> xLevels =
+ new ScNameToIndexAccess( xHierSupp->getLevels() );
+ tools::Long nLevCount = xLevels->getCount();
+ for (tools::Long nLev=0; nLev<nLevCount; nLev++)
+ {
+ uno::Reference<uno::XInterface> xLevel(xLevels->getByIndex(nLev),
+ uno::UNO_QUERY);
+ uno::Reference<container::XNamed> xLevNam( xLevel, uno::UNO_QUERY );
+ uno::Reference<sheet::XDataPilotMemberResults> xLevRes(
+ xLevel, uno::UNO_QUERY );
+ if ( xLevNam.is() && xLevRes.is() )
+ {
+ OUString aName = xLevNam->getName();
+ Reference<XPropertySet> xPropSet(xLevel, UNO_QUERY);
+ // Caption equals the field name by default.
+ // #i108948# use ScUnoHelpFunctions::GetStringProperty, because
+ // LayoutName is new and may not be present in external implementation
+ OUString aCaption = ScUnoHelpFunctions::GetStringProperty( xPropSet,
+ SC_UNO_DP_LAYOUTNAME, aName );
+
+ switch ( eDimOrient )
+ {
+ case sheet::DataPilotFieldOrientation_COLUMN:
+ {
+ uno::Sequence<sheet::MemberResult> aResult = xLevRes->getResults();
+ if (!lcl_MemberEmpty(aResult))
+ {
+ ScDPOutLevelData tmp(nDim, nHierarchy, nLev, nDimPos, nNumFmt, aResult, aName,
+ aCaption, bHasHiddenMember, bIsDataLayout, false);
+ pColFields.push_back(tmp);
+ }
+ }
+ break;
+ case sheet::DataPilotFieldOrientation_ROW:
+ {
+ uno::Sequence<sheet::MemberResult> aResult = xLevRes->getResults();
+ if (!lcl_MemberEmpty(aResult))
+ {
+ ScDPOutLevelData tmp(nDim, nHierarchy, nLev, nDimPos, nNumFmt, aResult, aName,
+ aCaption, bHasHiddenMember, bIsDataLayout, false);
+ pRowFields.push_back(tmp);
+ }
+ }
+ break;
+ case sheet::DataPilotFieldOrientation_PAGE:
+ {
+ uno::Sequence<sheet::MemberResult> aResult = getVisiblePageMembersAsResults(xLevel);
+ // no check on results for page fields
+ ScDPOutLevelData tmp(nDim, nHierarchy, nLev, nDimPos, nNumFmt, aResult, aName,
+ aCaption, bHasHiddenMember, false, true);
+ pPageFields.push_back(tmp);
+ }
+ break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+
+ // get number formats from data dimensions
+ if ( bIsDataLayout )
+ {
+ OSL_ENSURE( nLevCount == 1, "data layout: multiple levels?" );
+ if ( eDimOrient == sheet::DataPilotFieldOrientation_COLUMN )
+ lcl_FillNumberFormats( pColNumFmt, nColFmtCount, xLevRes, xDims );
+ else if ( eDimOrient == sheet::DataPilotFieldOrientation_ROW )
+ lcl_FillNumberFormats( pRowNumFmt, nRowFmtCount, xLevRes, xDims );
+ }
+ }
+ }
+ }
+ }
+ else if ( bIsDataLayout )
+ {
+ // data layout dimension is hidden (allowed if there is only one data dimension)
+ // -> use the number format from the first data dimension for all results
+
+ nSingleNumFmt = lcl_GetFirstNumberFormat( xDims );
+ }
+ }
+ }
+ std::sort(pColFields.begin(), pColFields.end(), ScDPOutLevelDataComparator());
+ std::sort(pRowFields.begin(), pRowFields.end(), ScDPOutLevelDataComparator());
+ std::sort(pPageFields.begin(), pPageFields.end(), ScDPOutLevelDataComparator());
+
+ // get data results:
+
+ try
+ {
+ aData = xResult->getResults();
+ }
+ catch (const uno::RuntimeException&)
+ {
+ bResultsError = true;
+ }
+ }
+
+ // get "DataDescription" property (may be missing in external sources)
+
+ uno::Reference<beans::XPropertySet> xSrcProp( xSource, uno::UNO_QUERY );
+ if ( !xSrcProp.is() )
+ return;
+
+ try
+ {
+ uno::Any aAny = xSrcProp->getPropertyValue( SC_UNO_DP_DATADESC );
+ OUString aUStr;
+ aAny >>= aUStr;
+ aDataDescription = aUStr;
+ }
+ catch(const uno::Exception&)
+ {
+ }
+}
+
+ScDPOutput::~ScDPOutput()
+{
+}
+
+void ScDPOutput::SetPosition( const ScAddress& rPos )
+{
+ aStartPos = rPos;
+ bSizesValid = bSizeOverflow = false;
+}
+
+void ScDPOutput::DataCell( SCCOL nCol, SCROW nRow, SCTAB nTab, const sheet::DataResult& rData )
+{
+ tools::Long nFlags = rData.Flags;
+ if ( nFlags & sheet::DataResultFlags::ERROR )
+ {
+ pDoc->SetError( nCol, nRow, nTab, FormulaError::NoValue );
+ }
+ else if ( nFlags & sheet::DataResultFlags::HASDATA )
+ {
+ pDoc->SetValue( nCol, nRow, nTab, rData.Value );
+
+ // use number formats from source
+
+ OSL_ENSURE( bSizesValid, "DataCell: !bSizesValid" );
+ sal_uInt32 nFormat = 0;
+ bool bApplyFormat = false;
+ if ( pColNumFmt )
+ {
+ if ( nCol >= nDataStartCol )
+ {
+ tools::Long nIndex = nCol - nDataStartCol;
+ if ( nIndex < nColFmtCount )
+ {
+ nFormat = pColNumFmt[nIndex];
+ bApplyFormat = true;
+ }
+ }
+ }
+ else if ( pRowNumFmt )
+ {
+ if ( nRow >= nDataStartRow )
+ {
+ tools::Long nIndex = nRow - nDataStartRow;
+ if ( nIndex < nRowFmtCount )
+ {
+ nFormat = pRowNumFmt[nIndex];
+ bApplyFormat = true;
+ }
+ }
+ }
+ else if ( nSingleNumFmt != 0 )
+ {
+ nFormat = nSingleNumFmt; // single format is used everywhere
+ bApplyFormat = true;
+ }
+
+ if (bApplyFormat)
+ pDoc->ApplyAttr(nCol, nRow, nTab, SfxUInt32Item(ATTR_VALUE_FORMAT, nFormat));
+ }
+ // SubTotal formatting is controlled by headers
+}
+
+void ScDPOutput::HeaderCell( SCCOL nCol, SCROW nRow, SCTAB nTab,
+ const sheet::MemberResult& rData, bool bColHeader, tools::Long nLevel )
+{
+ tools::Long nFlags = rData.Flags;
+
+ if ( nFlags & sheet::MemberResultFlags::HASMEMBER )
+ {
+ bool bNumeric = (nFlags & sheet::MemberResultFlags::NUMERIC) != 0;
+ if (bNumeric && std::isfinite( rData.Value))
+ {
+ pDoc->SetValue( nCol, nRow, nTab, rData.Value);
+ }
+ else
+ {
+ ScSetStringParam aParam;
+ if (bNumeric)
+ aParam.setNumericInput();
+ else
+ aParam.setTextInput();
+
+ pDoc->SetString(nCol, nRow, nTab, rData.Caption, &aParam);
+ }
+ }
+
+ if ( !(nFlags & sheet::MemberResultFlags::SUBTOTAL) )
+ return;
+
+ ScDPOutputImpl outputimp( pDoc, nTab,
+ nTabStartCol, nTabStartRow,
+ nDataStartCol, nDataStartRow, nTabEndCol, nTabEndRow );
+ //TODO: limit frames to horizontal or vertical?
+ if (bColHeader)
+ {
+ outputimp.OutputBlockFrame( nCol,nMemberStartRow+static_cast<SCROW>(nLevel), nCol,nDataStartRow-1 );
+
+ lcl_SetStyleById( pDoc,nTab, nCol,nMemberStartRow+static_cast<SCROW>(nLevel), nCol,nDataStartRow-1,
+ STR_PIVOT_STYLENAME_TITLE );
+ lcl_SetStyleById( pDoc,nTab, nCol,nDataStartRow, nCol,nTabEndRow,
+ STR_PIVOT_STYLENAME_RESULT );
+ }
+ else
+ {
+ outputimp.OutputBlockFrame( nMemberStartCol+static_cast<SCCOL>(nLevel),nRow, nDataStartCol-1,nRow );
+ lcl_SetStyleById( pDoc,nTab, nMemberStartCol+static_cast<SCCOL>(nLevel),nRow, nDataStartCol-1,nRow,
+ STR_PIVOT_STYLENAME_TITLE );
+ lcl_SetStyleById( pDoc,nTab, nDataStartCol,nRow, nTabEndCol,nRow,
+ STR_PIVOT_STYLENAME_RESULT );
+ }
+}
+
+void ScDPOutput::FieldCell(
+ SCCOL nCol, SCROW nRow, SCTAB nTab, const ScDPOutLevelData& rData, bool bInTable)
+{
+ // Avoid unwanted automatic format detection.
+ ScSetStringParam aParam;
+ aParam.mbDetectNumberFormat = false;
+ aParam.meSetTextNumFormat = ScSetStringParam::Always;
+ aParam.mbHandleApostrophe = false;
+ pDoc->SetString(nCol, nRow, nTab, rData.maCaption, &aParam);
+
+ if (bInTable)
+ lcl_SetFrame( pDoc,nTab, nCol,nRow, nCol,nRow, 20 );
+
+ // For field button drawing
+ ScMF nMergeFlag = ScMF::NONE;
+ if (rData.mbHasHiddenMember)
+ nMergeFlag |= ScMF::HiddenMember;
+
+ if (rData.mbPageDim)
+ {
+ nMergeFlag |= ScMF::ButtonPopup;
+ pDoc->ApplyFlagsTab(nCol, nRow, nCol, nRow, nTab, ScMF::Button);
+ pDoc->ApplyFlagsTab(nCol+1, nRow, nCol+1, nRow, nTab, nMergeFlag);
+ }
+ else
+ {
+ nMergeFlag |= ScMF::Button;
+ if (!rData.mbDataLayout)
+ nMergeFlag |= ScMF::ButtonPopup;
+ pDoc->ApplyFlagsTab(nCol, nRow, nCol, nRow, nTab, nMergeFlag);
+ }
+
+ lcl_SetStyleById( pDoc,nTab, nCol,nRow, nCol,nRow, STR_PIVOT_STYLENAME_FIELDNAME );
+}
+
+static void lcl_DoFilterButton( ScDocument* pDoc, SCCOL nCol, SCROW nRow, SCTAB nTab )
+{
+ pDoc->SetString( nCol, nRow, nTab, ScResId(STR_CELL_FILTER) );
+ pDoc->ApplyFlagsTab(nCol, nRow, nCol, nRow, nTab, ScMF::Button);
+}
+
+void ScDPOutput::CalcSizes()
+{
+ if (bSizesValid)
+ return;
+
+ // get column size of data from first row
+ //TODO: allow different sizes (and clear following areas) ???
+
+ nRowCount = aData.getLength();
+ const uno::Sequence<sheet::DataResult>* pRowAry = aData.getConstArray();
+ nColCount = nRowCount ? ( pRowAry[0].getLength() ) : 0;
+
+ nHeaderSize = 1;
+ if (GetHeaderLayout() && pColFields.empty())
+ // Insert an extra header row only when there is no column field.
+ nHeaderSize = 2;
+
+ // calculate output positions and sizes
+
+ tools::Long nPageSize = 0; // use page fields!
+ if ( bDoFilter || !pPageFields.empty() )
+ {
+ nPageSize += pPageFields.size() + 1; // plus one empty row
+ if ( bDoFilter )
+ ++nPageSize; // filter button above the page fields
+ }
+
+ if ( aStartPos.Col() + static_cast<tools::Long>(pRowFields.size()) + nColCount - 1 > pDoc->MaxCol() ||
+ aStartPos.Row() + nPageSize + nHeaderSize + static_cast<tools::Long>(pColFields.size()) + nRowCount > pDoc->MaxRow())
+ {
+ bSizeOverflow = true;
+ }
+
+ nTabStartCol = aStartPos.Col();
+ nTabStartRow = aStartPos.Row() + static_cast<SCROW>(nPageSize); // below page fields
+ nMemberStartCol = nTabStartCol;
+ nMemberStartRow = nTabStartRow + static_cast<SCROW>(nHeaderSize);
+ nDataStartCol = nMemberStartCol + static_cast<SCCOL>(pRowFields.size());
+ nDataStartRow = nMemberStartRow + static_cast<SCROW>(pColFields.size());
+ if ( nColCount > 0 )
+ nTabEndCol = nDataStartCol + static_cast<SCCOL>(nColCount) - 1;
+ else
+ nTabEndCol = nDataStartCol; // single column will remain empty
+ // if page fields are involved, include the page selection cells
+ if ( !pPageFields.empty() && nTabEndCol < nTabStartCol + 1 )
+ nTabEndCol = nTabStartCol + 1;
+ if ( nRowCount > 0 )
+ nTabEndRow = nDataStartRow + static_cast<SCROW>(nRowCount) - 1;
+ else
+ nTabEndRow = nDataStartRow; // single row will remain empty
+ bSizesValid = true;
+}
+
+sal_Int32 ScDPOutput::GetPositionType(const ScAddress& rPos)
+{
+ using namespace ::com::sun::star::sheet;
+
+ SCCOL nCol = rPos.Col();
+ SCROW nRow = rPos.Row();
+ SCTAB nTab = rPos.Tab();
+ if ( nTab != aStartPos.Tab() )
+ return DataPilotTablePositionType::NOT_IN_TABLE;
+
+ CalcSizes();
+
+ // Make sure the cursor is within the table.
+ if (nCol < nTabStartCol || nRow < nTabStartRow || nCol > nTabEndCol || nRow > nTabEndRow)
+ return DataPilotTablePositionType::NOT_IN_TABLE;
+
+ // test for result data area.
+ if (nCol >= nDataStartCol && nCol <= nTabEndCol && nRow >= nDataStartRow && nRow <= nTabEndRow)
+ return DataPilotTablePositionType::RESULT;
+
+ bool bInColHeader = (nRow >= nTabStartRow && nRow < nDataStartRow);
+ bool bInRowHeader = (nCol >= nTabStartCol && nCol < nDataStartCol);
+
+ if (bInColHeader && bInRowHeader)
+ // probably in that ugly little box at the upper-left corner of the table.
+ return DataPilotTablePositionType::OTHER;
+
+ if (bInColHeader)
+ {
+ if (nRow == nTabStartRow)
+ // first row in the column header area is always used for column
+ // field buttons.
+ return DataPilotTablePositionType::OTHER;
+
+ return DataPilotTablePositionType::COLUMN_HEADER;
+ }
+
+ if (bInRowHeader)
+ return DataPilotTablePositionType::ROW_HEADER;
+
+ return DataPilotTablePositionType::OTHER;
+}
+
+void ScDPOutput::Output()
+{
+ SCTAB nTab = aStartPos.Tab();
+ const uno::Sequence<sheet::DataResult>* pRowAry = aData.getConstArray();
+
+ // calculate output positions and sizes
+
+ CalcSizes();
+ if ( bSizeOverflow || bResultsError ) // does output area exceed sheet limits?
+ return; // nothing
+
+ // clear whole (new) output area
+ // when modifying table, clear old area !
+ //TODO: include InsertDeleteFlags::OBJECTS ???
+ pDoc->DeleteAreaTab( aStartPos.Col(), aStartPos.Row(), nTabEndCol, nTabEndRow, nTab, InsertDeleteFlags::ALL );
+
+ if ( bDoFilter )
+ lcl_DoFilterButton( pDoc, aStartPos.Col(), aStartPos.Row(), nTab );
+
+ // output page fields:
+
+ for (size_t nField=0; nField<pPageFields.size(); ++nField)
+ {
+ SCCOL nHdrCol = aStartPos.Col();
+ SCROW nHdrRow = aStartPos.Row() + nField + ( bDoFilter ? 1 : 0 );
+ // draw without frame for consistency with filter button:
+ FieldCell(nHdrCol, nHdrRow, nTab, pPageFields[nField], false);
+ SCCOL nFldCol = nHdrCol + 1;
+
+ OUString aPageValue = ScResId(SCSTR_ALL);
+ const uno::Sequence<sheet::MemberResult>& rRes = pPageFields[nField].maResult;
+ sal_Int32 n = rRes.getLength();
+ if (n == 1)
+ {
+ if (rRes[0].Caption.isEmpty())
+ aPageValue = ScResId(STR_EMPTYDATA);
+ else
+ aPageValue = rRes[0].Caption;
+ }
+ else if (n > 1)
+ aPageValue = ScResId(SCSTR_MULTIPLE);
+
+ ScSetStringParam aParam;
+ aParam.setTextInput();
+ pDoc->SetString(nFldCol, nHdrRow, nTab, aPageValue, &aParam);
+
+ lcl_SetFrame( pDoc,nTab, nFldCol,nHdrRow, nFldCol,nHdrRow, 20 );
+ }
+
+ // data description
+ // (may get overwritten by first row field)
+
+ if (aDataDescription.isEmpty())
+ {
+ //TODO: use default string ("result") ?
+ }
+ pDoc->SetString(nTabStartCol, nTabStartRow, nTab, aDataDescription);
+
+ // set STR_PIVOT_STYLENAME_INNER for whole data area (subtotals are overwritten)
+
+ if ( nDataStartRow > nTabStartRow )
+ lcl_SetStyleById( pDoc, nTab, nTabStartCol, nTabStartRow, nTabEndCol, nDataStartRow-1,
+ STR_PIVOT_STYLENAME_TOP );
+ lcl_SetStyleById( pDoc, nTab, nDataStartCol, nDataStartRow, nTabEndCol, nTabEndRow,
+ STR_PIVOT_STYLENAME_INNER );
+
+ // output column headers:
+ ScDPOutputImpl outputimp( pDoc, nTab,
+ nTabStartCol, nTabStartRow,
+ nDataStartCol, nDataStartRow, nTabEndCol, nTabEndRow );
+ for (size_t nField=0; nField<pColFields.size(); nField++)
+ {
+ SCCOL nHdrCol = nDataStartCol + static_cast<SCCOL>(nField); //TODO: check for overflow
+ FieldCell(nHdrCol, nTabStartRow, nTab, pColFields[nField], true);
+
+ SCROW nRowPos = nMemberStartRow + static_cast<SCROW>(nField); //TODO: check for overflow
+ const uno::Sequence<sheet::MemberResult> rSequence = pColFields[nField].maResult;
+ const sheet::MemberResult* pArray = rSequence.getConstArray();
+ tools::Long nThisColCount = rSequence.getLength();
+ OSL_ENSURE( nThisColCount == nColCount, "count mismatch" ); //TODO: ???
+ for (tools::Long nCol=0; nCol<nThisColCount; nCol++)
+ {
+ SCCOL nColPos = nDataStartCol + static_cast<SCCOL>(nCol); //TODO: check for overflow
+ HeaderCell( nColPos, nRowPos, nTab, pArray[nCol], true, nField );
+ if ( ( pArray[nCol].Flags & sheet::MemberResultFlags::HASMEMBER ) &&
+ !( pArray[nCol].Flags & sheet::MemberResultFlags::SUBTOTAL ) )
+ {
+ tools::Long nEnd = nCol;
+ while ( nEnd+1 < nThisColCount && ( pArray[nEnd+1].Flags & sheet::MemberResultFlags::CONTINUE ) )
+ ++nEnd;
+ SCCOL nEndColPos = nDataStartCol + static_cast<SCCOL>(nEnd); //TODO: check for overflow
+ if ( nField+1 < pColFields.size())
+ {
+ if ( nField == pColFields.size() - 2 )
+ {
+ outputimp.AddCol( nColPos );
+ if ( nColPos + 1 == nEndColPos )
+ outputimp.OutputBlockFrame( nColPos,nRowPos, nEndColPos,nRowPos+1, true );
+ }
+ else
+ outputimp.OutputBlockFrame( nColPos,nRowPos, nEndColPos,nRowPos );
+
+ lcl_SetStyleById( pDoc, nTab, nColPos,nRowPos, nEndColPos,nDataStartRow-1, STR_PIVOT_STYLENAME_CATEGORY );
+ }
+ else
+ lcl_SetStyleById( pDoc, nTab, nColPos,nRowPos, nColPos,nDataStartRow-1, STR_PIVOT_STYLENAME_CATEGORY );
+ }
+ else if ( pArray[nCol].Flags & sheet::MemberResultFlags::SUBTOTAL )
+ outputimp.AddCol( nColPos );
+
+ // Apply the same number format as in data source.
+ pDoc->ApplyAttr(nColPos, nRowPos, nTab, SfxUInt32Item(ATTR_VALUE_FORMAT, pColFields[nField].mnSrcNumFmt));
+ }
+ if ( nField== 0 && pColFields.size() == 1 )
+ outputimp.OutputBlockFrame( nDataStartCol,nTabStartRow, nTabEndCol,nRowPos-1 );
+ }
+
+ // output row headers:
+ std::vector<bool> vbSetBorder;
+ vbSetBorder.resize( nTabEndRow - nDataStartRow + 1, false );
+ for (size_t nField=0; nField<pRowFields.size(); nField++)
+ {
+ SCCOL nHdrCol = nTabStartCol + static_cast<SCCOL>(nField); //TODO: check for overflow
+ SCROW nHdrRow = nDataStartRow - 1;
+ FieldCell(nHdrCol, nHdrRow, nTab, pRowFields[nField], true);
+
+ SCCOL nColPos = nMemberStartCol + static_cast<SCCOL>(nField); //TODO: check for overflow
+ const uno::Sequence<sheet::MemberResult> rSequence = pRowFields[nField].maResult;
+ const sheet::MemberResult* pArray = rSequence.getConstArray();
+ sal_Int32 nThisRowCount = rSequence.getLength();
+ OSL_ENSURE( nThisRowCount == nRowCount, "count mismatch" ); //TODO: ???
+ for (sal_Int32 nRow=0; nRow<nThisRowCount; nRow++)
+ {
+ SCROW nRowPos = nDataStartRow + static_cast<SCROW>(nRow); //TODO: check for overflow
+ HeaderCell( nColPos, nRowPos, nTab, pArray[nRow], false, nField );
+ if ( ( pArray[nRow].Flags & sheet::MemberResultFlags::HASMEMBER ) &&
+ !( pArray[nRow].Flags & sheet::MemberResultFlags::SUBTOTAL ) )
+ {
+ if ( nField+1 < pRowFields.size() )
+ {
+ tools::Long nEnd = nRow;
+ while ( nEnd+1 < nThisRowCount && ( pArray[nEnd+1].Flags & sheet::MemberResultFlags::CONTINUE ) )
+ ++nEnd;
+ SCROW nEndRowPos = nDataStartRow + static_cast<SCROW>(nEnd); //TODO: check for overflow
+ outputimp.AddRow( nRowPos );
+ if ( !vbSetBorder[ nRow ] )
+ {
+ outputimp.OutputBlockFrame( nColPos, nRowPos, nTabEndCol, nEndRowPos );
+ vbSetBorder[ nRow ] = true;
+ }
+ outputimp.OutputBlockFrame( nColPos, nRowPos, nColPos, nEndRowPos );
+
+ if ( nField == pRowFields.size() - 2 )
+ outputimp.OutputBlockFrame( nColPos+1, nRowPos, nColPos+1, nEndRowPos );
+
+ lcl_SetStyleById( pDoc, nTab, nColPos,nRowPos, nDataStartCol-1,nEndRowPos, STR_PIVOT_STYLENAME_CATEGORY );
+ }
+ else
+ lcl_SetStyleById( pDoc, nTab, nColPos,nRowPos, nDataStartCol-1,nRowPos, STR_PIVOT_STYLENAME_CATEGORY );
+ }
+ else if ( pArray[nRow].Flags & sheet::MemberResultFlags::SUBTOTAL )
+ outputimp.AddRow( nRowPos );
+
+ // Apply the same number format as in data source.
+ pDoc->ApplyAttr(nColPos, nRowPos, nTab, SfxUInt32Item(ATTR_VALUE_FORMAT, pRowFields[nField].mnSrcNumFmt));
+ }
+ }
+
+ if (nColCount == 1 && nRowCount > 0 && pColFields.empty())
+ {
+ // the table contains exactly one data field and no column fields.
+ // Display data description at top right corner.
+ ScSetStringParam aParam;
+ aParam.setTextInput();
+ pDoc->SetString(nDataStartCol, nDataStartRow-1, nTab, aDataDescription, &aParam);
+ }
+
+ // output data results:
+
+ for (sal_Int32 nRow=0; nRow<nRowCount; nRow++)
+ {
+ SCROW nRowPos = nDataStartRow + static_cast<SCROW>(nRow); //TODO: check for overflow
+ const sheet::DataResult* pColAry = pRowAry[nRow].getConstArray();
+ sal_Int32 nThisColCount = pRowAry[nRow].getLength();
+ OSL_ENSURE( nThisColCount == nColCount, "count mismatch" ); //TODO: ???
+ for (sal_Int32 nCol=0; nCol<nThisColCount; nCol++)
+ {
+ SCCOL nColPos = nDataStartCol + static_cast<SCCOL>(nCol); //TODO: check for overflow
+ DataCell( nColPos, nRowPos, nTab, pColAry[nCol] );
+ }
+ }
+
+ outputimp.OutputDataArea();
+}
+
+ScRange ScDPOutput::GetOutputRange( sal_Int32 nRegionType )
+{
+ using namespace ::com::sun::star::sheet;
+
+ CalcSizes();
+
+ SCTAB nTab = aStartPos.Tab();
+ switch (nRegionType)
+ {
+ case DataPilotOutputRangeType::RESULT:
+ return ScRange(nDataStartCol, nDataStartRow, nTab, nTabEndCol, nTabEndRow, nTab);
+ case DataPilotOutputRangeType::TABLE:
+ return ScRange(aStartPos.Col(), nTabStartRow, nTab, nTabEndCol, nTabEndRow, nTab);
+ default:
+ OSL_ENSURE(nRegionType == DataPilotOutputRangeType::WHOLE, "ScDPOutput::GetOutputRange: unknown region type");
+ break;
+ }
+ return ScRange(aStartPos.Col(), aStartPos.Row(), nTab, nTabEndCol, nTabEndRow, nTab);
+}
+
+bool ScDPOutput::HasError()
+{
+ CalcSizes();
+
+ return bSizeOverflow || bResultsError;
+}
+
+sal_Int32 ScDPOutput::GetHeaderRows() const
+{
+ return pPageFields.size() + ( bDoFilter ? 1 : 0 );
+}
+
+namespace
+{
+ void insertNames(ScDPUniqueStringSet& rNames, const uno::Sequence<sheet::MemberResult>& rMemberResults)
+ {
+ for (const sheet::MemberResult& rMemberResult : rMemberResults)
+ {
+ if (rMemberResult.Flags & sheet::MemberResultFlags::HASMEMBER)
+ rNames.insert(rMemberResult.Name);
+ }
+ }
+}
+
+void ScDPOutput::GetMemberResultNames(ScDPUniqueStringSet& rNames, tools::Long nDimension)
+{
+ // Return the list of all member names in a dimension's MemberResults.
+ // Only the dimension has to be compared because this is only used with table data,
+ // where each dimension occurs only once.
+
+ auto lFindDimension = [nDimension](const ScDPOutLevelData& rField) { return rField.mnDim == nDimension; };
+
+ // look in column fields
+ auto colit = std::find_if(pColFields.begin(), pColFields.end(), lFindDimension);
+ if (colit != pColFields.end())
+ {
+ // collect the member names
+ insertNames(rNames, colit->maResult);
+ return;
+ }
+
+ // look in row fields
+ auto rowit = std::find_if(pRowFields.begin(), pRowFields.end(), lFindDimension);
+ if (rowit != pRowFields.end())
+ {
+ // collect the member names
+ insertNames(rNames, rowit->maResult);
+ }
+}
+
+void ScDPOutput::SetHeaderLayout(bool bUseGrid)
+{
+ mbHeaderLayout = bUseGrid;
+ bSizesValid = false;
+}
+
+namespace {
+
+void lcl_GetTableVars( sal_Int32& rGrandTotalCols, sal_Int32& rGrandTotalRows, sal_Int32& rDataLayoutIndex,
+ std::vector<OUString>& rDataNames, std::vector<OUString>& rGivenNames,
+ sheet::DataPilotFieldOrientation& rDataOrient,
+ const uno::Reference<sheet::XDimensionsSupplier>& xSource )
+{
+ rDataLayoutIndex = -1; // invalid
+ rGrandTotalCols = 0;
+ rGrandTotalRows = 0;
+ rDataOrient = sheet::DataPilotFieldOrientation_HIDDEN;
+
+ uno::Reference<beans::XPropertySet> xSrcProp( xSource, uno::UNO_QUERY );
+ bool bColGrand = ScUnoHelpFunctions::GetBoolProperty(
+ xSrcProp, SC_UNO_DP_COLGRAND);
+ if ( bColGrand )
+ rGrandTotalCols = 1; // default if data layout not in columns
+
+ bool bRowGrand = ScUnoHelpFunctions::GetBoolProperty(
+ xSrcProp, SC_UNO_DP_ROWGRAND);
+ if ( bRowGrand )
+ rGrandTotalRows = 1; // default if data layout not in rows
+
+ if ( !xSource.is() )
+ return;
+
+ // find index and orientation of "data layout" dimension, count data dimensions
+
+ sal_Int32 nDataCount = 0;
+
+ uno::Reference<container::XIndexAccess> xDims = new ScNameToIndexAccess( xSource->getDimensions() );
+ tools::Long nDimCount = xDims->getCount();
+ for (tools::Long nDim=0; nDim<nDimCount; nDim++)
+ {
+ uno::Reference<uno::XInterface> xDim(xDims->getByIndex(nDim), uno::UNO_QUERY);
+ uno::Reference<beans::XPropertySet> xDimProp( xDim, uno::UNO_QUERY );
+ if ( xDimProp.is() )
+ {
+ sheet::DataPilotFieldOrientation eDimOrient =
+ ScUnoHelpFunctions::GetEnumProperty(
+ xDimProp, SC_UNO_DP_ORIENTATION,
+ sheet::DataPilotFieldOrientation_HIDDEN );
+ if ( ScUnoHelpFunctions::GetBoolProperty( xDimProp,
+ SC_UNO_DP_ISDATALAYOUT ) )
+ {
+ rDataLayoutIndex = nDim;
+ rDataOrient = eDimOrient;
+ }
+ if ( eDimOrient == sheet::DataPilotFieldOrientation_DATA )
+ {
+ OUString aSourceName;
+ OUString aGivenName;
+ ScDPOutput::GetDataDimensionNames( aSourceName, aGivenName, xDim );
+ try
+ {
+ uno::Any aValue = xDimProp->getPropertyValue( SC_UNO_DP_LAYOUTNAME );
+
+ if( aValue.hasValue() )
+ {
+ OUString strLayoutName;
+
+ if( ( aValue >>= strLayoutName ) && !strLayoutName.isEmpty() )
+ aGivenName = strLayoutName;
+ }
+ }
+ catch(const uno::Exception&)
+ {
+ }
+ rDataNames.push_back( aSourceName );
+ rGivenNames.push_back( aGivenName );
+
+ ++nDataCount;
+ }
+ }
+ }
+
+ if ( ( rDataOrient == sheet::DataPilotFieldOrientation_COLUMN ) && bColGrand )
+ rGrandTotalCols = nDataCount;
+ else if ( ( rDataOrient == sheet::DataPilotFieldOrientation_ROW ) && bRowGrand )
+ rGrandTotalRows = nDataCount;
+}
+
+}
+
+void ScDPOutput::GetPositionData(const ScAddress& rPos, DataPilotTablePositionData& rPosData)
+{
+ using namespace ::com::sun::star::sheet;
+
+ SCCOL nCol = rPos.Col();
+ SCROW nRow = rPos.Row();
+ SCTAB nTab = rPos.Tab();
+ if ( nTab != aStartPos.Tab() )
+ return; // wrong sheet
+
+ // calculate output positions and sizes
+
+ CalcSizes();
+
+ rPosData.PositionType = GetPositionType(rPos);
+ switch (rPosData.PositionType)
+ {
+ case DataPilotTablePositionType::RESULT:
+ {
+ vector<DataPilotFieldFilter> aFilters;
+ GetDataResultPositionData(aFilters, rPos);
+
+ DataPilotTableResultData aResData;
+ aResData.FieldFilters = comphelper::containerToSequence(aFilters);
+ aResData.DataFieldIndex = 0;
+ Reference<beans::XPropertySet> xPropSet(xSource, UNO_QUERY);
+ if (xPropSet.is())
+ {
+ sal_Int32 nDataFieldCount = ScUnoHelpFunctions::GetLongProperty( xPropSet,
+ SC_UNO_DP_DATAFIELDCOUNT );
+ if (nDataFieldCount > 0)
+ aResData.DataFieldIndex = (nRow - nDataStartRow) % nDataFieldCount;
+ }
+
+ // Copy appropriate DataResult object from the cached sheet::DataResult table.
+ if (aData.getLength() > nRow - nDataStartRow &&
+ aData[nRow-nDataStartRow].getLength() > nCol-nDataStartCol)
+ aResData.Result = aData[nRow-nDataStartRow][nCol-nDataStartCol];
+
+ rPosData.PositionData <<= aResData;
+ return;
+ }
+ case DataPilotTablePositionType::COLUMN_HEADER:
+ {
+ tools::Long nField = nRow - nTabStartRow - 1; // 1st line is used for the buttons
+ if (nField < 0)
+ break;
+
+ if (pColFields.size() < o3tl::make_unsigned(nField) + 1 )
+ break;
+ const uno::Sequence<sheet::MemberResult> rSequence = pColFields[nField].maResult;
+ if (!rSequence.hasElements())
+ break;
+ const sheet::MemberResult* pArray = rSequence.getConstArray();
+
+ tools::Long nItem = nCol - nDataStartCol;
+ // get origin of "continue" fields
+ while (nItem > 0 && ( pArray[nItem].Flags & sheet::MemberResultFlags::CONTINUE) )
+ --nItem;
+
+ if (nItem < 0)
+ break;
+
+ DataPilotTableHeaderData aHeaderData;
+ aHeaderData.MemberName = pArray[nItem].Name;
+ aHeaderData.Flags = pArray[nItem].Flags;
+ aHeaderData.Dimension = static_cast<sal_Int32>(pColFields[nField].mnDim);
+ aHeaderData.Hierarchy = static_cast<sal_Int32>(pColFields[nField].mnHier);
+ aHeaderData.Level = static_cast<sal_Int32>(pColFields[nField].mnLevel);
+
+ rPosData.PositionData <<= aHeaderData;
+ return;
+ }
+ case DataPilotTablePositionType::ROW_HEADER:
+ {
+ tools::Long nField = nCol - nTabStartCol;
+ if (nField < 0)
+ break;
+
+ if (pRowFields.size() < o3tl::make_unsigned(nField) + 1 )
+ break;
+ const uno::Sequence<sheet::MemberResult> rSequence = pRowFields[nField].maResult;
+ if (!rSequence.hasElements())
+ break;
+ const sheet::MemberResult* pArray = rSequence.getConstArray();
+
+ tools::Long nItem = nRow - nDataStartRow;
+ // get origin of "continue" fields
+ while ( nItem > 0 && (pArray[nItem].Flags & sheet::MemberResultFlags::CONTINUE) )
+ --nItem;
+
+ if (nItem < 0)
+ break;
+
+ DataPilotTableHeaderData aHeaderData;
+ aHeaderData.MemberName = pArray[nItem].Name;
+ aHeaderData.Flags = pArray[nItem].Flags;
+ aHeaderData.Dimension = static_cast<sal_Int32>(pRowFields[nField].mnDim);
+ aHeaderData.Hierarchy = static_cast<sal_Int32>(pRowFields[nField].mnHier);
+ aHeaderData.Level = static_cast<sal_Int32>(pRowFields[nField].mnLevel);
+
+ rPosData.PositionData <<= aHeaderData;
+ return;
+ }
+ }
+}
+
+bool ScDPOutput::GetDataResultPositionData(vector<sheet::DataPilotFieldFilter>& rFilters, const ScAddress& rPos)
+{
+ // Check to make sure there is at least one data field.
+ Reference<beans::XPropertySet> xPropSet(xSource, UNO_QUERY);
+ if (!xPropSet.is())
+ return false;
+
+ sal_Int32 nDataFieldCount = ScUnoHelpFunctions::GetLongProperty( xPropSet,
+ SC_UNO_DP_DATAFIELDCOUNT );
+ if (nDataFieldCount == 0)
+ // No data field is present in this datapilot table.
+ return false;
+
+ // #i111421# use lcl_GetTableVars for correct size of totals and data layout position
+ sal_Int32 nGrandTotalCols;
+ sal_Int32 nGrandTotalRows;
+ sal_Int32 nDataLayoutIndex;
+ std::vector<OUString> aDataNames;
+ std::vector<OUString> aGivenNames;
+ sheet::DataPilotFieldOrientation eDataOrient;
+ lcl_GetTableVars( nGrandTotalCols, nGrandTotalRows, nDataLayoutIndex, aDataNames, aGivenNames, eDataOrient, xSource );
+
+ SCCOL nCol = rPos.Col();
+ SCROW nRow = rPos.Row();
+ SCTAB nTab = rPos.Tab();
+ if ( nTab != aStartPos.Tab() )
+ return false; // wrong sheet
+
+ CalcSizes();
+
+ // test for data area.
+ if (nCol < nDataStartCol || nCol > nTabEndCol || nRow < nDataStartRow || nRow > nTabEndRow)
+ {
+ // Cell is outside the data field area.
+ return false;
+ }
+
+ bool bFilterByCol = (nCol <= static_cast<SCCOL>(nTabEndCol - nGrandTotalCols));
+ bool bFilterByRow = (nRow <= static_cast<SCROW>(nTabEndRow - nGrandTotalRows));
+
+ // column fields
+ for (size_t nColField = 0; nColField < pColFields.size() && bFilterByCol; ++nColField)
+ {
+ if (pColFields[nColField].mnDim == nDataLayoutIndex)
+ // There is no sense including the data layout field for filtering.
+ continue;
+
+ sheet::DataPilotFieldFilter filter;
+ filter.FieldName = pColFields[nColField].maName;
+
+ const uno::Sequence<sheet::MemberResult> rSequence = pColFields[nColField].maResult;
+ const sheet::MemberResult* pArray = rSequence.getConstArray();
+
+ OSL_ENSURE(nDataStartCol + rSequence.getLength() - 1 == nTabEndCol, "ScDPOutput::GetDataFieldCellData: error in geometric assumption");
+
+ tools::Long nItem = nCol - nDataStartCol;
+ // get origin of "continue" fields
+ while ( nItem > 0 && (pArray[nItem].Flags & sheet::MemberResultFlags::CONTINUE) )
+ --nItem;
+
+ filter.MatchValueName = pArray[nItem].Name;
+ rFilters.push_back(filter);
+ }
+
+ // row fields
+ for (size_t nRowField = 0; nRowField < pRowFields.size() && bFilterByRow; ++nRowField)
+ {
+ if (pRowFields[nRowField].mnDim == nDataLayoutIndex)
+ // There is no sense including the data layout field for filtering.
+ continue;
+
+ sheet::DataPilotFieldFilter filter;
+ filter.FieldName = pRowFields[nRowField].maName;
+
+ const uno::Sequence<sheet::MemberResult> rSequence = pRowFields[nRowField].maResult;
+ const sheet::MemberResult* pArray = rSequence.getConstArray();
+
+ OSL_ENSURE(nDataStartRow + rSequence.getLength() - 1 == nTabEndRow, "ScDPOutput::GetDataFieldCellData: error in geometric assumption");
+
+ tools::Long nItem = nRow - nDataStartRow;
+ // get origin of "continue" fields
+ while ( nItem > 0 && (pArray[nItem].Flags & sheet::MemberResultFlags::CONTINUE) )
+ --nItem;
+
+ filter.MatchValueName = pArray[nItem].Name;
+ rFilters.push_back(filter);
+ }
+
+ return true;
+}
+
+namespace {
+
+OUString lcl_GetDataFieldName( std::u16string_view rSourceName, sal_Int16 eFunc )
+{
+ TranslateId pStrId;
+ switch ( eFunc )
+ {
+ case sheet::GeneralFunction2::SUM: pStrId = STR_FUN_TEXT_SUM; break;
+ case sheet::GeneralFunction2::COUNT:
+ case sheet::GeneralFunction2::COUNTNUMS: pStrId = STR_FUN_TEXT_COUNT; break;
+ case sheet::GeneralFunction2::AVERAGE: pStrId = STR_FUN_TEXT_AVG; break;
+ case sheet::GeneralFunction2::MEDIAN: pStrId = STR_FUN_TEXT_MEDIAN; break;
+ case sheet::GeneralFunction2::MAX: pStrId = STR_FUN_TEXT_MAX; break;
+ case sheet::GeneralFunction2::MIN: pStrId = STR_FUN_TEXT_MIN; break;
+ case sheet::GeneralFunction2::PRODUCT: pStrId = STR_FUN_TEXT_PRODUCT; break;
+ case sheet::GeneralFunction2::STDEV:
+ case sheet::GeneralFunction2::STDEVP: pStrId = STR_FUN_TEXT_STDDEV; break;
+ case sheet::GeneralFunction2::VAR:
+ case sheet::GeneralFunction2::VARP: pStrId = STR_FUN_TEXT_VAR; break;
+ case sheet::GeneralFunction2::NONE:
+ case sheet::GeneralFunction2::AUTO: break;
+ default:
+ {
+ assert(false);
+ }
+ }
+ if (!pStrId)
+ return OUString();
+
+ return ScResId(pStrId) + " - " + rSourceName;
+}
+
+}
+
+void ScDPOutput::GetDataDimensionNames(
+ OUString& rSourceName, OUString& rGivenName, const uno::Reference<uno::XInterface>& xDim )
+{
+ uno::Reference<beans::XPropertySet> xDimProp( xDim, uno::UNO_QUERY );
+ uno::Reference<container::XNamed> xDimName( xDim, uno::UNO_QUERY );
+ if ( !(xDimProp.is() && xDimName.is()) )
+ return;
+
+ // Asterisks are added in ScDPSaveData::WriteToSource to create unique names.
+ //TODO: preserve original name there?
+ rSourceName = ScDPUtil::getSourceDimensionName(xDimName->getName());
+
+ // Generate "given name" the same way as in dptabres.
+ //TODO: Should use a stored name when available
+
+ sal_Int16 eFunc = ScUnoHelpFunctions::GetShortProperty(
+ xDimProp, SC_UNO_DP_FUNCTION2,
+ sheet::GeneralFunction2::NONE );
+ rGivenName = lcl_GetDataFieldName( rSourceName, eFunc );
+}
+
+bool ScDPOutput::IsFilterButton( const ScAddress& rPos )
+{
+ SCCOL nCol = rPos.Col();
+ SCROW nRow = rPos.Row();
+ SCTAB nTab = rPos.Tab();
+ if ( nTab != aStartPos.Tab() || !bDoFilter )
+ return false; // wrong sheet or no button at all
+
+ // filter button is at top left
+ return ( nCol == aStartPos.Col() && nRow == aStartPos.Row() );
+}
+
+tools::Long ScDPOutput::GetHeaderDim( const ScAddress& rPos, sheet::DataPilotFieldOrientation& rOrient )
+{
+ SCCOL nCol = rPos.Col();
+ SCROW nRow = rPos.Row();
+ SCTAB nTab = rPos.Tab();
+ if ( nTab != aStartPos.Tab() )
+ return -1; // wrong sheet
+
+ // calculate output positions and sizes
+
+ CalcSizes();
+
+ // test for column header
+
+ if ( nRow == nTabStartRow && nCol >= nDataStartCol && o3tl::make_unsigned(nCol) < nDataStartCol + pColFields.size())
+ {
+ rOrient = sheet::DataPilotFieldOrientation_COLUMN;
+ tools::Long nField = nCol - nDataStartCol;
+ return pColFields[nField].mnDim;
+ }
+
+ // test for row header
+
+ if ( nRow+1 == nDataStartRow && nCol >= nTabStartCol && o3tl::make_unsigned(nCol) < nTabStartCol + pRowFields.size() )
+ {
+ rOrient = sheet::DataPilotFieldOrientation_ROW;
+ tools::Long nField = nCol - nTabStartCol;
+ return pRowFields[nField].mnDim;
+ }
+
+ // test for page field
+
+ SCROW nPageStartRow = aStartPos.Row() + ( bDoFilter ? 1 : 0 );
+ if ( nCol == aStartPos.Col() && nRow >= nPageStartRow && o3tl::make_unsigned(nRow) < nPageStartRow + pPageFields.size() )
+ {
+ rOrient = sheet::DataPilotFieldOrientation_PAGE;
+ tools::Long nField = nRow - nPageStartRow;
+ return pPageFields[nField].mnDim;
+ }
+
+ //TODO: single data field (?)
+
+ rOrient = sheet::DataPilotFieldOrientation_HIDDEN;
+ return -1; // invalid
+}
+
+bool ScDPOutput::GetHeaderDrag( const ScAddress& rPos, bool bMouseLeft, bool bMouseTop,
+ tools::Long nDragDim,
+ tools::Rectangle& rPosRect, sheet::DataPilotFieldOrientation& rOrient, tools::Long& rDimPos )
+{
+ // Rectangle instead of ScRange for rPosRect to allow for negative values
+
+ SCCOL nCol = rPos.Col();
+ SCROW nRow = rPos.Row();
+ SCTAB nTab = rPos.Tab();
+ if ( nTab != aStartPos.Tab() )
+ return false; // wrong sheet
+
+ // calculate output positions and sizes
+
+ CalcSizes();
+
+ // test for column header
+
+ if ( nCol >= nDataStartCol && nCol <= nTabEndCol &&
+ nRow + 1 >= nMemberStartRow && o3tl::make_unsigned(nRow) < nMemberStartRow + pColFields.size())
+ {
+ tools::Long nField = nRow - nMemberStartRow;
+ if (nField < 0)
+ {
+ nField = 0;
+ bMouseTop = true;
+ }
+ //TODO: find start of dimension
+
+ rPosRect = tools::Rectangle( nDataStartCol, nMemberStartRow + nField,
+ nTabEndCol, nMemberStartRow + nField -1 );
+
+ bool bFound = false; // is this within the same orientation?
+ bool bBeforeDrag = false;
+ bool bAfterDrag = false;
+ for (tools::Long nPos=0; o3tl::make_unsigned(nPos)<pColFields.size() && !bFound; nPos++)
+ {
+ if (pColFields[nPos].mnDim == nDragDim)
+ {
+ bFound = true;
+ if ( nField < nPos )
+ bBeforeDrag = true;
+ else if ( nField > nPos )
+ bAfterDrag = true;
+ }
+ }
+
+ if ( bFound )
+ {
+ if (!bBeforeDrag)
+ {
+ rPosRect.AdjustBottom( 1 );
+ if (bAfterDrag)
+ rPosRect.AdjustTop( 1 );
+ }
+ }
+ else
+ {
+ if ( !bMouseTop )
+ {
+ rPosRect.AdjustTop( 1 );
+ rPosRect.AdjustBottom( 1 );
+ ++nField;
+ }
+ }
+
+ rOrient = sheet::DataPilotFieldOrientation_COLUMN;
+ rDimPos = nField; //!...
+ return true;
+ }
+
+ // test for row header
+
+ // special case if no row fields
+ bool bSpecial = ( nRow+1 >= nDataStartRow && nRow <= nTabEndRow &&
+ pRowFields.empty() && nCol == nTabStartCol && bMouseLeft );
+
+ if ( bSpecial || ( nRow+1 >= nDataStartRow && nRow <= nTabEndRow &&
+ nCol + 1 >= nTabStartCol && o3tl::make_unsigned(nCol) < nTabStartCol + pRowFields.size() ) )
+ {
+ tools::Long nField = nCol - nTabStartCol;
+ //TODO: find start of dimension
+
+ rPosRect = tools::Rectangle( nTabStartCol + nField, nDataStartRow - 1,
+ nTabStartCol + nField - 1, nTabEndRow );
+
+ bool bFound = false; // is this within the same orientation?
+ bool bBeforeDrag = false;
+ bool bAfterDrag = false;
+ for (tools::Long nPos=0; o3tl::make_unsigned(nPos)<pRowFields.size() && !bFound; nPos++)
+ {
+ if (pRowFields[nPos].mnDim == nDragDim)
+ {
+ bFound = true;
+ if ( nField < nPos )
+ bBeforeDrag = true;
+ else if ( nField > nPos )
+ bAfterDrag = true;
+ }
+ }
+
+ if ( bFound )
+ {
+ if (!bBeforeDrag)
+ {
+ rPosRect.AdjustRight( 1 );
+ if (bAfterDrag)
+ rPosRect.AdjustLeft( 1 );
+ }
+ }
+ else
+ {
+ if ( !bMouseLeft )
+ {
+ rPosRect.AdjustLeft( 1 );
+ rPosRect.AdjustRight( 1 );
+ ++nField;
+ }
+ }
+
+ rOrient = sheet::DataPilotFieldOrientation_ROW;
+ rDimPos = nField; //!...
+ return true;
+ }
+
+ // test for page fields
+
+ SCROW nPageStartRow = aStartPos.Row() + ( bDoFilter ? 1 : 0 );
+ if ( nCol >= aStartPos.Col() && nCol <= nTabEndCol &&
+ nRow + 1 >= nPageStartRow && o3tl::make_unsigned(nRow) < nPageStartRow + pPageFields.size() )
+ {
+ tools::Long nField = nRow - nPageStartRow;
+ if (nField < 0)
+ {
+ nField = 0;
+ bMouseTop = true;
+ }
+ //TODO: find start of dimension
+
+ rPosRect = tools::Rectangle( aStartPos.Col(), nPageStartRow + nField,
+ nTabEndCol, nPageStartRow + nField - 1 );
+
+ bool bFound = false; // is this within the same orientation?
+ bool bBeforeDrag = false;
+ bool bAfterDrag = false;
+ for (tools::Long nPos=0; o3tl::make_unsigned(nPos)<pPageFields.size() && !bFound; nPos++)
+ {
+ if (pPageFields[nPos].mnDim == nDragDim)
+ {
+ bFound = true;
+ if ( nField < nPos )
+ bBeforeDrag = true;
+ else if ( nField > nPos )
+ bAfterDrag = true;
+ }
+ }
+
+ if ( bFound )
+ {
+ if (!bBeforeDrag)
+ {
+ rPosRect.AdjustBottom( 1 );
+ if (bAfterDrag)
+ rPosRect.AdjustTop( 1 );
+ }
+ }
+ else
+ {
+ if ( !bMouseTop )
+ {
+ rPosRect.AdjustTop( 1 );
+ rPosRect.AdjustBottom( 1 );
+ ++nField;
+ }
+ }
+
+ rOrient = sheet::DataPilotFieldOrientation_PAGE;
+ rDimPos = nField; //!...
+ return true;
+ }
+
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dpoutputgeometry.cxx b/sc/source/core/data/dpoutputgeometry.cxx
new file mode 100644
index 000000000..0c5307258
--- /dev/null
+++ b/sc/source/core/data/dpoutputgeometry.cxx
@@ -0,0 +1,255 @@
+/* -*- 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 <dpoutputgeometry.hxx>
+#include <address.hxx>
+
+#include <vector>
+
+using ::std::vector;
+
+ScDPOutputGeometry::ScDPOutputGeometry(const ScRange& rOutRange, bool bShowFilter) :
+ maOutRange(rOutRange),
+ mnRowFields(0),
+ mnColumnFields(0),
+ mnPageFields(0),
+ mnDataFields(0),
+ meDataLayoutType(None),
+ mbShowFilter(bShowFilter),
+ mbHeaderLayout (false),
+ mbCompactMode (false)
+{
+}
+
+void ScDPOutputGeometry::setRowFieldCount(sal_uInt32 nCount)
+{
+ mnRowFields = nCount;
+}
+
+void ScDPOutputGeometry::setColumnFieldCount(sal_uInt32 nCount)
+{
+ mnColumnFields = nCount;
+}
+
+void ScDPOutputGeometry::setPageFieldCount(sal_uInt32 nCount)
+{
+ mnPageFields = nCount;
+}
+
+void ScDPOutputGeometry::setDataFieldCount(sal_uInt32 nCount)
+{
+ mnDataFields = nCount;
+}
+
+void ScDPOutputGeometry::setDataLayoutType(FieldType eType)
+{
+ meDataLayoutType = eType;
+}
+
+void ScDPOutputGeometry::setHeaderLayout(bool bHeaderLayout)
+{
+ mbHeaderLayout = bHeaderLayout;
+}
+
+void ScDPOutputGeometry::setCompactMode(bool bCompactMode)
+{
+ mbCompactMode = bCompactMode;
+}
+
+void ScDPOutputGeometry::getColumnFieldPositions(vector<ScAddress>& rAddrs) const
+{
+ sal_uInt32 nColumnFields, nRowFields;
+ adjustFieldsForDataLayout(nColumnFields, nRowFields);
+
+ vector<ScAddress> aAddrs;
+ if (!nColumnFields)
+ {
+ rAddrs.swap(aAddrs);
+ return;
+ }
+
+ SCROW nCurRow = maOutRange.aStart.Row();
+
+ if (mnPageFields)
+ {
+ SCROW nRowStart = maOutRange.aStart.Row() + int(mbShowFilter);
+ SCROW nRowEnd = nRowStart + static_cast<SCCOL>(mnPageFields-1);
+ nCurRow = nRowEnd + 2;
+ }
+ else if (mbShowFilter)
+ nCurRow += 2;
+
+ SCROW nRow = nCurRow;
+ SCTAB nTab = maOutRange.aStart.Tab();
+ SCCOL nColStart = static_cast<SCCOL>(maOutRange.aStart.Col() + nRowFields);
+ if(mbCompactMode)
+ nColStart = static_cast<SCCOL>(maOutRange.aStart.Col() + 1); // We have only one row in compact mode
+ SCCOL nColEnd = nColStart + static_cast<SCCOL>(nColumnFields-1);
+
+ for (SCCOL nCol = nColStart; nCol <= nColEnd; ++nCol)
+ aAddrs.emplace_back(nCol, nRow, nTab);
+ rAddrs.swap(aAddrs);
+}
+
+void ScDPOutputGeometry::getRowFieldPositions(vector<ScAddress>& rAddrs) const
+{
+ sal_uInt32 nColumnFields, nRowFields;
+ adjustFieldsForDataLayout(nColumnFields, nRowFields);
+
+ vector<ScAddress> aAddrs;
+ if (!nRowFields)
+ {
+ rAddrs.swap(aAddrs);
+ return;
+ }
+
+ SCROW nRow = getRowFieldHeaderRow();
+ SCTAB nTab = maOutRange.aStart.Tab();
+ SCCOL nColStart = maOutRange.aStart.Col();
+ SCCOL nColEnd = nColStart + static_cast<SCCOL>(nRowFields-1);
+
+ if(mbCompactMode)
+ nColEnd = nColStart; // We have only one row in compact mode
+
+ for (SCCOL nCol = nColStart; nCol <= nColEnd; ++nCol)
+ aAddrs.emplace_back(nCol, nRow, nTab);
+ rAddrs.swap(aAddrs);
+}
+
+void ScDPOutputGeometry::getPageFieldPositions(vector<ScAddress>& rAddrs) const
+{
+ vector<ScAddress> aAddrs;
+ if (!mnPageFields)
+ {
+ rAddrs.swap(aAddrs);
+ return;
+ }
+
+ SCTAB nTab = maOutRange.aStart.Tab();
+ SCCOL nCol = maOutRange.aStart.Col();
+
+ SCROW nRowStart = maOutRange.aStart.Row() + int(mbShowFilter);
+ SCROW nRowEnd = nRowStart + static_cast<SCCOL>(mnPageFields-1);
+
+ for (SCROW nRow = nRowStart; nRow <= nRowEnd; ++nRow)
+ aAddrs.emplace_back(nCol, nRow, nTab);
+ rAddrs.swap(aAddrs);
+}
+
+SCROW ScDPOutputGeometry::getRowFieldHeaderRow() const
+{
+ SCROW nCurRow = maOutRange.aStart.Row();
+ sal_uInt32 nColumnFields, nRowFields;
+ adjustFieldsForDataLayout(nColumnFields, nRowFields);
+
+ if (mnPageFields)
+ {
+ SCROW nRowStart = maOutRange.aStart.Row() + int(mbShowFilter);
+ SCROW nRowEnd = nRowStart + static_cast<SCCOL>(mnPageFields-1);
+ nCurRow = nRowEnd + 2;
+ }
+ else if (mbShowFilter)
+ nCurRow += 2;
+
+ if (nColumnFields)
+ nCurRow += static_cast<SCROW>(nColumnFields);
+ else if (nRowFields && mbHeaderLayout)
+ ++nCurRow;
+
+ return nCurRow;
+}
+
+void ScDPOutputGeometry::adjustFieldsForDataLayout(sal_uInt32& rColumnFields, sal_uInt32& rRowFields) const
+{
+ rRowFields = mnRowFields;
+ rColumnFields = mnColumnFields;
+
+ if (mnDataFields >= 2)
+ return;
+
+ // Data layout field can be either row or column field, never page field.
+ switch (meDataLayoutType)
+ {
+ case Column:
+ if (rColumnFields > 0)
+ rColumnFields -= 1;
+ break;
+ case Row:
+ if (rRowFields > 0)
+ rRowFields -= 1;
+ break;
+ default:
+ ;
+ }
+}
+
+std::pair<ScDPOutputGeometry::FieldType, size_t>
+ScDPOutputGeometry::getFieldButtonType(const ScAddress& rPos) const
+{
+ SCROW nCurRow = maOutRange.aStart.Row();
+ sal_uInt32 nColumnFields, nRowFields;
+ adjustFieldsForDataLayout(nColumnFields, nRowFields);
+
+ if (mnPageFields)
+ {
+ SCCOL nCol = maOutRange.aStart.Col();
+ SCROW nRowStart = maOutRange.aStart.Row() + int(mbShowFilter);
+ SCROW nRowEnd = nRowStart + static_cast<SCCOL>(mnPageFields-1);
+ if (rPos.Col() == nCol && nRowStart <= rPos.Row() && rPos.Row() <= nRowEnd)
+ {
+ size_t nPos = static_cast<size_t>(rPos.Row() - nRowStart);
+ return std::pair<FieldType, size_t>(Page, nPos);
+ }
+
+ nCurRow = nRowEnd + 2;
+ }
+ else if (mbShowFilter)
+ nCurRow += 2;
+
+ if (nColumnFields)
+ {
+ SCROW nRow = nCurRow;
+ SCCOL nColStart = static_cast<SCCOL>(maOutRange.aStart.Col() + nRowFields);
+ SCCOL nColEnd = nColStart + static_cast<SCCOL>(nColumnFields-1);
+ if (rPos.Row() == nRow && nColStart <= rPos.Col() && rPos.Col() <= nColEnd)
+ {
+ size_t nPos = static_cast<size_t>(rPos.Col() - nColStart);
+ return std::pair<FieldType, size_t>(Column, nPos);
+ }
+
+ nCurRow += static_cast<SCROW>(nColumnFields);
+ }
+ else if (mbHeaderLayout)
+ ++nCurRow;
+
+ if (nRowFields)
+ {
+ SCCOL nColStart = maOutRange.aStart.Col();
+ SCCOL nColEnd = nColStart + static_cast<SCCOL>(nRowFields-1);
+ if (rPos.Row() == nCurRow && nColStart <= rPos.Col() && rPos.Col() <= nColEnd)
+ {
+ size_t nPos = static_cast<size_t>(rPos.Col() - nColStart);
+ return std::pair<FieldType, size_t>(Row, nPos);
+ }
+ }
+
+ return std::pair<FieldType, size_t>(None, 0);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dpresfilter.cxx b/sc/source/core/data/dpresfilter.cxx
new file mode 100644
index 000000000..4cef11b76
--- /dev/null
+++ b/sc/source/core/data/dpresfilter.cxx
@@ -0,0 +1,267 @@
+/* -*- 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 <dpresfilter.hxx>
+#include <global.hxx>
+
+#include <unotools/charclass.hxx>
+#include <sal/log.hxx>
+#include <o3tl/hash_combine.hxx>
+
+#include <com/sun/star/sheet/DataPilotFieldFilter.hpp>
+#include <com/sun/star/uno/Sequence.hxx>
+
+#include <limits>
+
+using namespace com::sun::star;
+
+ScDPResultFilter::ScDPResultFilter(const OUString& rDimName, bool bDataLayout) :
+ maDimName(rDimName), mbHasValue(false), mbDataLayout(bDataLayout) {}
+
+ScDPResultFilterContext::ScDPResultFilterContext() :
+ mnCol(0), mnRow(0) {}
+
+size_t ScDPResultTree::NamePairHash::operator() (const NamePairType& rPair) const
+{
+ std::size_t seed = 0;
+ o3tl::hash_combine(seed, rPair.first.hashCode());
+ o3tl::hash_combine(seed, rPair.second.hashCode());
+ return seed;
+}
+
+#if DEBUG_PIVOT_TABLE
+void ScDPResultTree::DimensionNode::dump(int nLevel) const
+{
+ string aIndent(nLevel*2, ' ');
+ MembersType::const_iterator it = maChildMembersValueNames.begin(), itEnd = maChildMembersValueNames.end();
+ for (; it != itEnd; ++it)
+ {
+ cout << aIndent << "member: ";
+ const ScDPItemData& rVal = it->first;
+ if (rVal.IsValue())
+ cout << rVal.GetValue();
+ else
+ cout << rVal.GetString();
+ cout << endl;
+
+ it->second->dump(nLevel+1);
+ }
+}
+#endif
+
+ScDPResultTree::MemberNode::MemberNode() {}
+
+ScDPResultTree::MemberNode::~MemberNode() {}
+
+#if DEBUG_PIVOT_TABLE
+void ScDPResultTree::MemberNode::dump(int nLevel) const
+{
+ string aIndent(nLevel*2, ' ');
+ for (const auto& rValue : maValues)
+ cout << aIndent << "value: " << rValue << endl;
+
+ for (const auto& [rName, rxDim] : maChildDimensions)
+ {
+ cout << aIndent << "dimension: " << rName << endl;
+ rxDim->dump(nLevel+1);
+ }
+}
+#endif
+
+ScDPResultTree::ScDPResultTree() : mpRoot(new MemberNode) {}
+ScDPResultTree::~ScDPResultTree()
+{
+}
+
+void ScDPResultTree::add(
+ const std::vector<ScDPResultFilter>& rFilters, double fVal)
+{
+ // TODO: I'll work on the col / row to value node mapping later.
+
+ const OUString* pDimName = nullptr;
+ const OUString* pMemName = nullptr;
+ MemberNode* pMemNode = mpRoot.get();
+
+ for (const ScDPResultFilter& filter : rFilters)
+ {
+ if (filter.mbDataLayout)
+ continue;
+
+ if (maPrimaryDimName.isEmpty())
+ maPrimaryDimName = filter.maDimName;
+
+ // See if this dimension exists.
+ auto& rDims = pMemNode->maChildDimensions;
+ OUString aUpperName = ScGlobal::getCharClass().uppercase(filter.maDimName);
+ auto itDim = rDims.find(aUpperName);
+ if (itDim == rDims.end())
+ {
+ // New dimension. Insert it.
+ auto r = rDims.emplace(aUpperName, DimensionNode());
+ assert(r.second);
+ itDim = r.first;
+ }
+
+ pDimName = &itDim->first;
+
+ // Now, see if this dimension member exists.
+ DimensionNode& rDim = itDim->second;
+ MembersType& rMembersValueNames = rDim.maChildMembersValueNames;
+ aUpperName = ScGlobal::getCharClass().uppercase(filter.maValueName);
+ MembersType::iterator itMem = rMembersValueNames.find(aUpperName);
+ if (itMem == rMembersValueNames.end())
+ {
+ // New member. Insert it.
+ auto pNode = std::make_shared<MemberNode>();
+ std::pair<MembersType::iterator, bool> r =
+ rMembersValueNames.emplace(aUpperName, pNode);
+
+ if (!r.second)
+ // Insertion failed!
+ return;
+
+ itMem = r.first;
+
+ // If the locale independent value string isn't any different it
+ // makes no sense to add it to the separate mapping.
+ if (!filter.maValue.isEmpty() && filter.maValue != filter.maValueName)
+ {
+ MembersType& rMembersValues = rDim.maChildMembersValues;
+ aUpperName = ScGlobal::getCharClass().uppercase(filter.maValue);
+ MembersType::iterator itMemVal = rMembersValues.find(aUpperName);
+ if (itMemVal == rMembersValues.end())
+ {
+ // New member. Insert it.
+ std::pair<MembersType::iterator, bool> it =
+ rMembersValues.emplace(aUpperName, pNode);
+ // If insertion failed do not bail out anymore.
+ SAL_WARN_IF( !it.second, "sc.core", "ScDPResultTree::add - rMembersValues.insert failed");
+ }
+ }
+ }
+
+ pMemName = &itMem->first;
+ pMemNode = itMem->second.get();
+ }
+
+ if (pDimName && pMemName)
+ {
+ NamePairType aNames(
+ ScGlobal::getCharClass().uppercase(*pDimName),
+ ScGlobal::getCharClass().uppercase(*pMemName));
+
+ LeafValuesType::iterator it = maLeafValues.find(aNames);
+ if (it == maLeafValues.end())
+ {
+ // This name pair doesn't exist. Associate a new value for it.
+ maLeafValues.emplace(aNames, fVal);
+ }
+ else
+ {
+ // This name pair already exists. Set the value to NaN.
+ it->second = std::numeric_limits<double>::quiet_NaN();
+ }
+ }
+
+ pMemNode->maValues.push_back(fVal);
+}
+
+void ScDPResultTree::swap(ScDPResultTree& rOther)
+{
+ std::swap(maPrimaryDimName, rOther.maPrimaryDimName);
+ std::swap(mpRoot, rOther.mpRoot);
+ maLeafValues.swap(rOther.maLeafValues);
+}
+
+bool ScDPResultTree::empty() const
+{
+ return mpRoot->maChildDimensions.empty();
+}
+
+void ScDPResultTree::clear()
+{
+ maPrimaryDimName.clear();
+ mpRoot.reset( new MemberNode );
+}
+
+const ScDPResultTree::ValuesType* ScDPResultTree::getResults(
+ const uno::Sequence<sheet::DataPilotFieldFilter>& rFilters) const
+{
+ const MemberNode* pMember = mpRoot.get();
+ for (const sheet::DataPilotFieldFilter& rFilter : rFilters)
+ {
+ auto itDim = pMember->maChildDimensions.find(
+ ScGlobal::getCharClass().uppercase(rFilter.FieldName));
+
+ if (itDim == pMember->maChildDimensions.end())
+ // Specified dimension not found.
+ return nullptr;
+
+ const DimensionNode& rDim = itDim->second;
+ MembersType::const_iterator itMem( rDim.maChildMembersValueNames.find(
+ ScGlobal::getCharClass().uppercase( rFilter.MatchValueName)));
+
+ if (itMem == rDim.maChildMembersValueNames.end())
+ {
+ // Specified member name not found, try locale independent value.
+ itMem = rDim.maChildMembersValues.find( ScGlobal::getCharClass().uppercase( rFilter.MatchValue));
+
+ if (itMem == rDim.maChildMembersValues.end())
+ // Specified member not found.
+ return nullptr;
+ }
+
+ pMember = itMem->second.get();
+ }
+
+ if (pMember->maValues.empty())
+ {
+ // Descend into dimension member children while there is no result and
+ // exactly one dimension field with exactly one member item, for which
+ // no further constraint (filter) has to match.
+ const MemberNode* pFieldMember = pMember;
+ while (pFieldMember->maChildDimensions.size() == 1)
+ {
+ auto itDim( pFieldMember->maChildDimensions.begin());
+ const DimensionNode& rDim = itDim->second;
+ if (rDim.maChildMembersValueNames.size() != 1)
+ break; // while
+ pFieldMember = rDim.maChildMembersValueNames.begin()->second.get();
+ if (!pFieldMember->maValues.empty())
+ return &pFieldMember->maValues;
+ }
+ }
+
+ return &pMember->maValues;
+}
+
+double ScDPResultTree::getLeafResult(const css::sheet::DataPilotFieldFilter& rFilter) const
+{
+ NamePairType aPair(
+ ScGlobal::getCharClass().uppercase(rFilter.FieldName),
+ ScGlobal::getCharClass().uppercase(rFilter.MatchValueName));
+
+ LeafValuesType::const_iterator it = maLeafValues.find(aPair);
+ if (it != maLeafValues.end())
+ // Found!
+ return it->second;
+
+ // Not found. Return an NaN.
+ return std::numeric_limits<double>::quiet_NaN();
+}
+
+#if DEBUG_PIVOT_TABLE
+void ScDPResultTree::dump() const
+{
+ cout << "primary dimension name: " << maPrimaryDimName << endl;
+ mpRoot->dump(0);
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dpsave.cxx b/sc/source/core/data/dpsave.cxx
new file mode 100644
index 000000000..16de535bd
--- /dev/null
+++ b/sc/source/core/data/dpsave.cxx
@@ -0,0 +1,1383 @@
+/* -*- 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 <dpsave.hxx>
+#include <dpdimsave.hxx>
+#include <miscuno.hxx>
+#include <unonames.hxx>
+#include <dputil.hxx>
+#include <generalfunction.hxx>
+#include <dptabdat.hxx>
+
+#include <sal/types.h>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+#include <comphelper/stl_types.hxx>
+#include <unotools/charclass.hxx>
+
+#include <com/sun/star/sheet/XDimensionsSupplier.hpp>
+#include <com/sun/star/sheet/DataPilotFieldAutoShowInfo.hpp>
+#include <com/sun/star/sheet/DataPilotFieldLayoutInfo.hpp>
+#include <com/sun/star/sheet/DataPilotFieldReference.hpp>
+#include <com/sun/star/sheet/DataPilotFieldSortInfo.hpp>
+#include <com/sun/star/sheet/DataPilotFieldSortMode.hpp>
+#include <com/sun/star/sheet/XHierarchiesSupplier.hpp>
+#include <com/sun/star/sheet/XLevelsSupplier.hpp>
+#include <com/sun/star/sheet/XMembersSupplier.hpp>
+#include <com/sun/star/container/XNamed.hpp>
+#include <com/sun/star/util/XCloneable.hpp>
+#include <tools/diagnose_ex.h>
+
+#include <unordered_map>
+#include <algorithm>
+
+using namespace com::sun::star;
+using namespace com::sun::star::sheet;
+using ::std::unique_ptr;
+
+#define SC_DPSAVEMODE_DONTKNOW 2
+
+static void lcl_SetBoolProperty( const uno::Reference<beans::XPropertySet>& xProp,
+ const OUString& rName, bool bValue )
+{
+ //TODO: move to ScUnoHelpFunctions?
+
+ xProp->setPropertyValue( rName, uno::Any( bValue ) );
+}
+
+ScDPSaveMember::ScDPSaveMember(const OUString& rName) :
+ aName( rName ),
+ nVisibleMode( SC_DPSAVEMODE_DONTKNOW ),
+ nShowDetailsMode( SC_DPSAVEMODE_DONTKNOW )
+{
+}
+
+ScDPSaveMember::ScDPSaveMember(const ScDPSaveMember& r) :
+ aName( r.aName ),
+ mpLayoutName( r.mpLayoutName ),
+ nVisibleMode( r.nVisibleMode ),
+ nShowDetailsMode( r.nShowDetailsMode )
+{
+}
+
+ScDPSaveMember::~ScDPSaveMember()
+{
+}
+
+bool ScDPSaveMember::operator== ( const ScDPSaveMember& r ) const
+{
+ return aName == r.aName &&
+ nVisibleMode == r.nVisibleMode &&
+ nShowDetailsMode == r.nShowDetailsMode;
+}
+
+bool ScDPSaveMember::HasIsVisible() const
+{
+ return nVisibleMode != SC_DPSAVEMODE_DONTKNOW;
+}
+
+void ScDPSaveMember::SetIsVisible(bool bSet)
+{
+ nVisibleMode = sal_uInt16(bSet);
+}
+
+bool ScDPSaveMember::HasShowDetails() const
+{
+ return nShowDetailsMode != SC_DPSAVEMODE_DONTKNOW;
+}
+
+void ScDPSaveMember::SetShowDetails(bool bSet)
+{
+ nShowDetailsMode = sal_uInt16(bSet);
+}
+
+void ScDPSaveMember::SetName( const OUString& rNew )
+{
+ // Used only if the source member was renamed (groups).
+ // For UI renaming of members, a layout name must be used.
+
+ aName = rNew;
+}
+
+void ScDPSaveMember::SetLayoutName( const OUString& rName )
+{
+ mpLayoutName = rName;
+}
+
+const std::optional<OUString> & ScDPSaveMember::GetLayoutName() const
+{
+ return mpLayoutName;
+}
+
+void ScDPSaveMember::RemoveLayoutName()
+{
+ mpLayoutName.reset();
+}
+
+void ScDPSaveMember::WriteToSource( const uno::Reference<uno::XInterface>& xMember, sal_Int32 nPosition )
+{
+ uno::Reference<beans::XPropertySet> xMembProp( xMember, uno::UNO_QUERY );
+ OSL_ENSURE( xMembProp.is(), "no properties at member" );
+ if ( !xMembProp.is() )
+ return;
+
+ // exceptions are caught at ScDPSaveData::WriteToSource
+
+ if ( nVisibleMode != SC_DPSAVEMODE_DONTKNOW )
+ lcl_SetBoolProperty( xMembProp,
+ SC_UNO_DP_ISVISIBLE, static_cast<bool>(nVisibleMode) );
+
+ if ( nShowDetailsMode != SC_DPSAVEMODE_DONTKNOW )
+ lcl_SetBoolProperty( xMembProp,
+ SC_UNO_DP_SHOWDETAILS, static_cast<bool>(nShowDetailsMode) );
+
+ if (mpLayoutName)
+ ScUnoHelpFunctions::SetOptionalPropertyValue(xMembProp, SC_UNO_DP_LAYOUTNAME, *mpLayoutName);
+
+ if ( nPosition >= 0 )
+ ScUnoHelpFunctions::SetOptionalPropertyValue(xMembProp, SC_UNO_DP_POSITION, nPosition);
+}
+
+#if DUMP_PIVOT_TABLE
+
+void ScDPSaveMember::Dump(int nIndent) const
+{
+ std::string aIndent(nIndent*4, ' ');
+ cout << aIndent << "* member name: '" << aName << "'" << endl;
+
+ cout << aIndent << " + layout name: ";
+ if (mpLayoutName)
+ cout << "'" << *mpLayoutName << "'";
+ else
+ cout << "(none)";
+ cout << endl;
+
+ cout << aIndent << " + visibility: ";
+ if (nVisibleMode == SC_DPSAVEMODE_DONTKNOW)
+ cout << "(unknown)";
+ else
+ cout << (nVisibleMode ? "visible" : "hidden");
+ cout << endl;
+}
+
+#endif
+
+ScDPSaveDimension::ScDPSaveDimension(const OUString& rName, bool bDataLayout) :
+ aName( rName ),
+ bIsDataLayout( bDataLayout ),
+ bDupFlag( false ),
+ nOrientation( sheet::DataPilotFieldOrientation_HIDDEN ),
+ nFunction( ScGeneralFunction::AUTO ),
+ nUsedHierarchy( -1 ),
+ nShowEmptyMode( SC_DPSAVEMODE_DONTKNOW ),
+ bRepeatItemLabels( false ),
+ bSubTotalDefault( true )
+{
+}
+
+ScDPSaveDimension::ScDPSaveDimension(const ScDPSaveDimension& r) :
+ aName( r.aName ),
+ mpLayoutName( r.mpLayoutName ),
+ mpSubtotalName( r.mpSubtotalName ),
+ bIsDataLayout( r.bIsDataLayout ),
+ bDupFlag( r.bDupFlag ),
+ nOrientation( r.nOrientation ),
+ nFunction( r.nFunction ),
+ nUsedHierarchy( r.nUsedHierarchy ),
+ nShowEmptyMode( r.nShowEmptyMode ),
+ bRepeatItemLabels( r.bRepeatItemLabels ),
+ bSubTotalDefault( r.bSubTotalDefault ),
+ maSubTotalFuncs( r.maSubTotalFuncs )
+{
+ for (const ScDPSaveMember* pMem : r.maMemberList)
+ {
+ const OUString& rName = pMem->GetName();
+ std::unique_ptr<ScDPSaveMember> pNew(new ScDPSaveMember( *pMem ));
+ maMemberList.push_back( pNew.get() );
+ maMemberHash[rName] = std::move(pNew);
+ }
+ if (r.pReferenceValue)
+ pReferenceValue.reset( new sheet::DataPilotFieldReference( *(r.pReferenceValue) ) );
+ if (r.pSortInfo)
+ pSortInfo.reset( new sheet::DataPilotFieldSortInfo( *(r.pSortInfo) ) );
+ if (r.pAutoShowInfo)
+ pAutoShowInfo.reset( new sheet::DataPilotFieldAutoShowInfo( *(r.pAutoShowInfo) ) );
+ if (r.pLayoutInfo)
+ pLayoutInfo.reset(new sheet::DataPilotFieldLayoutInfo( *(r.pLayoutInfo) ));
+}
+
+ScDPSaveDimension::~ScDPSaveDimension()
+{
+ maMemberHash.clear();
+ pReferenceValue.reset();
+ pSortInfo.reset();
+ pAutoShowInfo.reset();
+ pLayoutInfo.reset();
+}
+
+bool ScDPSaveDimension::operator== ( const ScDPSaveDimension& r ) const
+{
+ if ( aName != r.aName ||
+ bIsDataLayout != r.bIsDataLayout ||
+ bDupFlag != r.bDupFlag ||
+ nOrientation != r.nOrientation ||
+ nFunction != r.nFunction ||
+ nUsedHierarchy != r.nUsedHierarchy ||
+ nShowEmptyMode != r.nShowEmptyMode ||
+ bRepeatItemLabels!= r.bRepeatItemLabels||
+ bSubTotalDefault != r.bSubTotalDefault ||
+ maSubTotalFuncs != r.maSubTotalFuncs )
+ return false;
+
+ if (maMemberHash.size() != r.maMemberHash.size() )
+ return false;
+
+ if (!std::equal(maMemberList.begin(), maMemberList.end(), r.maMemberList.begin(), r.maMemberList.end(),
+ [](const ScDPSaveMember* a, const ScDPSaveMember* b) { return *a == *b; }))
+ return false;
+
+ if( pReferenceValue && r.pReferenceValue )
+ {
+ if ( *pReferenceValue != *r.pReferenceValue )
+ {
+ return false;
+ }
+ }
+ else if ( pReferenceValue || r.pReferenceValue )
+ {
+ return false;
+ }
+ if( this->pSortInfo && r.pSortInfo )
+ {
+ if ( *this->pSortInfo != *r.pSortInfo )
+ {
+ return false;
+ }
+ }
+ else if ( this->pSortInfo || r.pSortInfo )
+ {
+ return false;
+ }
+ if( this->pAutoShowInfo && r.pAutoShowInfo )
+ {
+ if ( *this->pAutoShowInfo != *r.pAutoShowInfo )
+ {
+ return false;
+ }
+ }
+ else if ( this->pAutoShowInfo || r.pAutoShowInfo )
+ {
+ return false;
+ }
+
+ return true;
+}
+
+void ScDPSaveDimension::AddMember(std::unique_ptr<ScDPSaveMember> pMember)
+{
+ const OUString & rName = pMember->GetName();
+ auto aExisting = maMemberHash.find( rName );
+ auto tmp = pMember.get();
+ if ( aExisting == maMemberHash.end() )
+ {
+ maMemberHash[rName] = std::move(pMember);
+ }
+ else
+ {
+ maMemberList.erase(std::remove(maMemberList.begin(), maMemberList.end(), aExisting->second.get()), maMemberList.end());
+ aExisting->second = std::move(pMember);
+ }
+ maMemberList.push_back( tmp );
+}
+
+void ScDPSaveDimension::SetName( const OUString& rNew )
+{
+ // Used only if the source dim was renamed (groups).
+ // For UI renaming of dimensions, the layout name must be used.
+
+ aName = rNew;
+}
+
+void ScDPSaveDimension::SetOrientation(css::sheet::DataPilotFieldOrientation nNew)
+{
+ nOrientation = nNew;
+}
+
+void ScDPSaveDimension::SetSubTotals(std::vector<ScGeneralFunction> && rFuncs)
+{
+ maSubTotalFuncs = std::move(rFuncs);
+ bSubTotalDefault = false;
+}
+
+bool ScDPSaveDimension::HasShowEmpty() const
+{
+ return nShowEmptyMode != SC_DPSAVEMODE_DONTKNOW;
+}
+
+void ScDPSaveDimension::SetShowEmpty(bool bSet)
+{
+ nShowEmptyMode = sal_uInt16(bSet);
+}
+
+void ScDPSaveDimension::SetRepeatItemLabels(bool bSet)
+{
+ bRepeatItemLabels = bSet;
+}
+
+void ScDPSaveDimension::SetFunction(ScGeneralFunction nNew)
+{
+ nFunction = nNew;
+}
+
+void ScDPSaveDimension::SetUsedHierarchy(tools::Long nNew)
+{
+ nUsedHierarchy = nNew;
+}
+
+void ScDPSaveDimension::SetSubtotalName(const OUString& rName)
+{
+ mpSubtotalName = rName;
+}
+
+const std::optional<OUString> & ScDPSaveDimension::GetSubtotalName() const
+{
+ return mpSubtotalName;
+}
+
+void ScDPSaveDimension::RemoveSubtotalName()
+{
+ mpSubtotalName.reset();
+}
+
+bool ScDPSaveDimension::IsMemberNameInUse(const OUString& rName) const
+{
+ return std::any_of(maMemberList.begin(), maMemberList.end(), [&rName](const ScDPSaveMember* pMem) {
+ if (rName.equalsIgnoreAsciiCase(pMem->GetName()))
+ return true;
+
+ const std::optional<OUString> & pLayoutName = pMem->GetLayoutName();
+ return pLayoutName && rName.equalsIgnoreAsciiCase(*pLayoutName);
+ });
+}
+
+void ScDPSaveDimension::SetLayoutName(const OUString& rName)
+{
+ mpLayoutName = rName;
+}
+
+const std::optional<OUString> & ScDPSaveDimension::GetLayoutName() const
+{
+ return mpLayoutName;
+}
+
+void ScDPSaveDimension::RemoveLayoutName()
+{
+ mpLayoutName.reset();
+}
+
+void ScDPSaveDimension::SetReferenceValue(const sheet::DataPilotFieldReference* pNew)
+{
+ if (pNew)
+ pReferenceValue.reset( new sheet::DataPilotFieldReference(*pNew) );
+ else
+ pReferenceValue.reset();
+}
+
+void ScDPSaveDimension::SetSortInfo(const sheet::DataPilotFieldSortInfo* pNew)
+{
+ if (pNew)
+ pSortInfo.reset( new sheet::DataPilotFieldSortInfo(*pNew) );
+ else
+ pSortInfo.reset();
+}
+
+void ScDPSaveDimension::SetAutoShowInfo(const sheet::DataPilotFieldAutoShowInfo* pNew)
+{
+ if (pNew)
+ pAutoShowInfo.reset( new sheet::DataPilotFieldAutoShowInfo(*pNew) );
+ else
+ pAutoShowInfo.reset();
+}
+
+void ScDPSaveDimension::SetLayoutInfo(const sheet::DataPilotFieldLayoutInfo* pNew)
+{
+ if (pNew)
+ pLayoutInfo.reset( new sheet::DataPilotFieldLayoutInfo(*pNew) );
+ else
+ pLayoutInfo.reset();
+}
+
+void ScDPSaveDimension::SetCurrentPage( const OUString* pPage )
+{
+ // We use member's visibility attribute to filter by page dimension.
+
+ // pPage == nullptr -> all members visible.
+ for (ScDPSaveMember* pMem : maMemberList)
+ {
+ bool bVisible = !pPage || pMem->GetName() == *pPage;
+ pMem->SetIsVisible(bVisible);
+ }
+}
+
+OUString ScDPSaveDimension::GetCurrentPage() const
+{
+ MemberList::const_iterator it = std::find_if(maMemberList.begin(), maMemberList.end(),
+ [](const ScDPSaveMember* pMem) { return pMem->GetIsVisible(); });
+ if (it != maMemberList.end())
+ return (*it)->GetName();
+
+ return OUString();
+}
+
+ScDPSaveMember* ScDPSaveDimension::GetExistingMemberByName(const OUString& rName)
+{
+ auto res = maMemberHash.find (rName);
+ if (res != maMemberHash.end())
+ return res->second.get();
+ return nullptr;
+}
+
+ScDPSaveMember* ScDPSaveDimension::GetMemberByName(const OUString& rName)
+{
+ auto res = maMemberHash.find (rName);
+ if (res != maMemberHash.end())
+ return res->second.get();
+
+ ScDPSaveMember* pNew = new ScDPSaveMember( rName );
+ maMemberHash[rName] = std::unique_ptr<ScDPSaveMember>(pNew);
+ maMemberList.push_back( pNew );
+ return pNew;
+}
+
+void ScDPSaveDimension::SetMemberPosition( const OUString& rName, sal_Int32 nNewPos )
+{
+ ScDPSaveMember* pMember = GetMemberByName( rName ); // make sure it exists and is in the hash
+
+ maMemberList.erase(std::remove( maMemberList.begin(), maMemberList.end(), pMember), maMemberList.end() );
+
+ maMemberList.insert( maMemberList.begin() + nNewPos, pMember );
+}
+
+void ScDPSaveDimension::WriteToSource( const uno::Reference<uno::XInterface>& xDim )
+{
+ uno::Reference<beans::XPropertySet> xDimProp( xDim, uno::UNO_QUERY );
+ OSL_ENSURE( xDimProp.is(), "no properties at dimension" );
+ if ( xDimProp.is() )
+ {
+ // exceptions are caught at ScDPSaveData::WriteToSource
+
+ sheet::DataPilotFieldOrientation eOrient = nOrientation;
+ xDimProp->setPropertyValue( SC_UNO_DP_ORIENTATION, uno::Any(eOrient) );
+
+ sal_Int16 eFunc = static_cast<sal_Int16>(nFunction);
+ xDimProp->setPropertyValue( SC_UNO_DP_FUNCTION2, uno::Any(eFunc) );
+
+ if ( nUsedHierarchy >= 0 )
+ {
+ xDimProp->setPropertyValue( SC_UNO_DP_USEDHIERARCHY, uno::Any(static_cast<sal_Int32>(nUsedHierarchy)) );
+ }
+
+ if ( pReferenceValue )
+ {
+ ;
+ xDimProp->setPropertyValue( SC_UNO_DP_REFVALUE, uno::Any(*pReferenceValue) );
+ }
+
+ if (mpLayoutName)
+ ScUnoHelpFunctions::SetOptionalPropertyValue(xDimProp, SC_UNO_DP_LAYOUTNAME, *mpLayoutName);
+
+ const std::optional<OUString> & pSubTotalName = GetSubtotalName();
+ if (pSubTotalName)
+ // Custom subtotal name, with '?' being replaced by the visible field name later.
+ ScUnoHelpFunctions::SetOptionalPropertyValue(xDimProp, SC_UNO_DP_FIELD_SUBTOTALNAME, *pSubTotalName);
+ }
+
+ // Level loop outside of maMemberList loop
+ // because SubTotals have to be set independently of known members
+
+ tools::Long nCount = maMemberHash.size();
+
+ tools::Long nHierCount = 0;
+ uno::Reference<container::XIndexAccess> xHiers;
+ uno::Reference<sheet::XHierarchiesSupplier> xHierSupp( xDim, uno::UNO_QUERY );
+ if ( xHierSupp.is() )
+ {
+ uno::Reference<container::XNameAccess> xHiersName = xHierSupp->getHierarchies();
+ xHiers = new ScNameToIndexAccess( xHiersName );
+ nHierCount = xHiers->getCount();
+ }
+
+ bool bHasHiddenMember = false;
+
+ for (tools::Long nHier=0; nHier<nHierCount; nHier++)
+ {
+ tools::Long nLevCount = 0;
+ uno::Reference<container::XIndexAccess> xLevels;
+ uno::Reference<sheet::XLevelsSupplier> xLevSupp(xHiers->getByIndex(nHier), uno::UNO_QUERY);
+ if ( xLevSupp.is() )
+ {
+ uno::Reference<container::XNameAccess> xLevelsName = xLevSupp->getLevels();
+ xLevels = new ScNameToIndexAccess( xLevelsName );
+ nLevCount = xLevels->getCount();
+ }
+
+ for (tools::Long nLev=0; nLev<nLevCount; nLev++)
+ {
+ uno::Reference<uno::XInterface> xLevel(xLevels->getByIndex(nLev), uno::UNO_QUERY);
+ uno::Reference<beans::XPropertySet> xLevProp( xLevel, uno::UNO_QUERY );
+ OSL_ENSURE( xLevProp.is(), "no properties at level" );
+ if ( xLevProp.is() )
+ {
+ if ( !bSubTotalDefault )
+ {
+ uno::Sequence<sal_Int16> aSeq(maSubTotalFuncs.size());
+ for(size_t i = 0; i < maSubTotalFuncs.size(); ++i)
+ aSeq.getArray()[i] = static_cast<sal_Int16>(maSubTotalFuncs[i]);
+ xLevProp->setPropertyValue( SC_UNO_DP_SUBTOTAL2, uno::Any(aSeq) );
+ }
+ if ( nShowEmptyMode != SC_DPSAVEMODE_DONTKNOW )
+ lcl_SetBoolProperty( xLevProp,
+ SC_UNO_DP_SHOWEMPTY, static_cast<bool>(nShowEmptyMode) );
+
+ lcl_SetBoolProperty( xLevProp,
+ SC_UNO_DP_REPEATITEMLABELS, bRepeatItemLabels );
+
+ if ( pSortInfo )
+ ScUnoHelpFunctions::SetOptionalPropertyValue(xLevProp, SC_UNO_DP_SORTING, *pSortInfo);
+
+ if ( pAutoShowInfo )
+ ScUnoHelpFunctions::SetOptionalPropertyValue(xLevProp, SC_UNO_DP_AUTOSHOW, *pAutoShowInfo);
+
+ if ( pLayoutInfo )
+ ScUnoHelpFunctions::SetOptionalPropertyValue(xLevProp, SC_UNO_DP_LAYOUT, *pLayoutInfo);
+
+ // exceptions are caught at ScDPSaveData::WriteToSource
+ }
+
+ if ( nCount > 0 )
+ {
+ uno::Reference<sheet::XMembersSupplier> xMembSupp( xLevel, uno::UNO_QUERY );
+ if ( xMembSupp.is() )
+ {
+ uno::Reference<sheet::XMembersAccess> xMembers = xMembSupp->getMembers();
+ if ( xMembers.is() )
+ {
+ sal_Int32 nPosition = -1; // set position only in manual mode
+ if ( !pSortInfo || pSortInfo->Mode == sheet::DataPilotFieldSortMode::MANUAL )
+ nPosition = 0;
+
+ for (ScDPSaveMember* pMember : maMemberList)
+ {
+ if (!pMember->GetIsVisible())
+ bHasHiddenMember = true;
+ OUString aMemberName = pMember->GetName();
+ if ( xMembers->hasByName( aMemberName ) )
+ {
+ uno::Reference<uno::XInterface> xMemberInt(
+ xMembers->getByName(aMemberName), uno::UNO_QUERY);
+ pMember->WriteToSource( xMemberInt, nPosition );
+
+ if ( nPosition >= 0 )
+ ++nPosition; // increase if initialized
+ }
+ // missing member is no error
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (xDimProp.is())
+ ScUnoHelpFunctions::SetOptionalPropertyValue(xDimProp, SC_UNO_DP_HAS_HIDDEN_MEMBER, bHasHiddenMember);
+}
+
+void ScDPSaveDimension::UpdateMemberVisibility(const std::unordered_map<OUString, bool>& rData)
+{
+ for (ScDPSaveMember* pMem : maMemberList)
+ {
+ const OUString& rMemName = pMem->GetName();
+ auto itr = rData.find(rMemName);
+ if (itr != rData.end())
+ pMem->SetIsVisible(itr->second);
+ }
+}
+
+bool ScDPSaveDimension::HasInvisibleMember() const
+{
+ return std::any_of(maMemberList.begin(), maMemberList.end(),
+ [](const ScDPSaveMember* pMem) { return !pMem->GetIsVisible(); });
+}
+
+void ScDPSaveDimension::RemoveObsoleteMembers(const MemberSetType& rMembers)
+{
+ MemberList aNew;
+ for (ScDPSaveMember* pMem : maMemberList)
+ {
+ if (rMembers.count(pMem->GetName()))
+ {
+ // This member still exists.
+ aNew.push_back(pMem);
+ }
+ else
+ {
+ maMemberHash.erase(pMem->GetName());
+ }
+ }
+
+ maMemberList.swap(aNew);
+}
+
+#if DUMP_PIVOT_TABLE
+
+void ScDPSaveDimension::Dump(int nIndent) const
+{
+ static const char* pOrientNames[] = { "hidden", "column", "row", "page", "data" };
+ std::string aIndent(nIndent*4, ' ');
+
+ cout << aIndent << "* dimension name: '" << aName << "'" << endl;
+
+ cout << aIndent << " + orientation: ";
+ if (nOrientation <= DataPilotFieldOrientation_DATA)
+ cout << pOrientNames[static_cast<int>(nOrientation)];
+ else
+ cout << "(invalid)";
+ cout << endl;
+
+ cout << aIndent << " + layout name: ";
+ if (mpLayoutName)
+ cout << "'" << *mpLayoutName << "'";
+ else
+ cout << "(none)";
+ cout << endl;
+
+ cout << aIndent << " + subtotal name: ";
+ if (mpSubtotalName)
+ cout << "'" << *mpSubtotalName << "'";
+ else
+ cout << "(none)";
+ cout << endl;
+
+ cout << aIndent << " + is data layout: " << (bIsDataLayout ? "yes" : "no") << endl;
+ cout << aIndent << " + is duplicate: " << (bDupFlag ? "yes" : "no") << endl;
+
+ for (ScDPSaveMember* pMem : maMemberList)
+ {
+ pMem->Dump(nIndent+1);
+ }
+
+ cout << endl; // blank line
+}
+
+#endif
+
+ScDPSaveData::ScDPSaveData() :
+ nColumnGrandMode( SC_DPSAVEMODE_DONTKNOW ),
+ nRowGrandMode( SC_DPSAVEMODE_DONTKNOW ),
+ nIgnoreEmptyMode( SC_DPSAVEMODE_DONTKNOW ),
+ nRepeatEmptyMode( SC_DPSAVEMODE_DONTKNOW ),
+ bFilterButton( true ),
+ bDrillDown( true ),
+ mbDimensionMembersBuilt(false)
+{
+}
+
+ScDPSaveData::ScDPSaveData(const ScDPSaveData& r) :
+ nColumnGrandMode( r.nColumnGrandMode ),
+ nRowGrandMode( r.nRowGrandMode ),
+ nIgnoreEmptyMode( r.nIgnoreEmptyMode ),
+ nRepeatEmptyMode( r.nRepeatEmptyMode ),
+ bFilterButton( r.bFilterButton ),
+ bDrillDown( r.bDrillDown ),
+ mbDimensionMembersBuilt(r.mbDimensionMembersBuilt),
+ mpGrandTotalName(r.mpGrandTotalName)
+{
+ if ( r.pDimensionData )
+ pDimensionData.reset( new ScDPDimensionSaveData( *r.pDimensionData ) );
+
+ for (auto const& it : r.m_DimList)
+ {
+ m_DimList.push_back(std::make_unique<ScDPSaveDimension>(*it));
+ }
+}
+
+ScDPSaveData& ScDPSaveData::operator= ( const ScDPSaveData& r )
+{
+ if ( &r != this )
+ {
+ this->~ScDPSaveData();
+ new( this ) ScDPSaveData ( r );
+ }
+ return *this;
+}
+
+bool ScDPSaveData::operator== ( const ScDPSaveData& r ) const
+{
+ if ( nColumnGrandMode != r.nColumnGrandMode ||
+ nRowGrandMode != r.nRowGrandMode ||
+ nIgnoreEmptyMode != r.nIgnoreEmptyMode ||
+ nRepeatEmptyMode != r.nRepeatEmptyMode ||
+ bFilterButton != r.bFilterButton ||
+ bDrillDown != r.bDrillDown ||
+ mbDimensionMembersBuilt != r.mbDimensionMembersBuilt)
+ return false;
+
+ if ( pDimensionData || r.pDimensionData )
+ if ( !pDimensionData || !r.pDimensionData || !( *pDimensionData == *r.pDimensionData ) )
+ return false;
+
+ if (!(::comphelper::ContainerUniquePtrEquals(m_DimList, r.m_DimList)))
+ return false;
+
+ if (mpGrandTotalName)
+ {
+ if (!r.mpGrandTotalName)
+ return false;
+ if (*mpGrandTotalName != *r.mpGrandTotalName)
+ return false;
+ }
+ else if (r.mpGrandTotalName)
+ return false;
+
+ return true;
+}
+
+ScDPSaveData::~ScDPSaveData()
+{
+}
+
+void ScDPSaveData::SetGrandTotalName(const OUString& rName)
+{
+ mpGrandTotalName = rName;
+}
+
+const std::optional<OUString> & ScDPSaveData::GetGrandTotalName() const
+{
+ return mpGrandTotalName;
+}
+
+namespace {
+
+class DimOrderInserter
+{
+ ScDPSaveData::DimOrderType& mrNames;
+public:
+ explicit DimOrderInserter(ScDPSaveData::DimOrderType& rNames) : mrNames(rNames) {}
+
+ void operator() (const ScDPSaveDimension* pDim)
+ {
+ size_t nRank = mrNames.size();
+ mrNames.emplace(ScGlobal::getCharClass().uppercase(pDim->GetName()), nRank);
+ }
+};
+
+}
+
+const ScDPSaveData::DimOrderType& ScDPSaveData::GetDimensionSortOrder() const
+{
+ if (!mpDimOrder)
+ {
+ mpDimOrder.reset(new DimOrderType);
+ std::vector<const ScDPSaveDimension*> aRowDims, aColDims;
+ GetAllDimensionsByOrientation(sheet::DataPilotFieldOrientation_ROW, aRowDims);
+ GetAllDimensionsByOrientation(sheet::DataPilotFieldOrientation_COLUMN, aColDims);
+
+ std::for_each(aRowDims.begin(), aRowDims.end(), DimOrderInserter(*mpDimOrder));
+ std::for_each(aColDims.begin(), aColDims.end(), DimOrderInserter(*mpDimOrder));
+ }
+ return *mpDimOrder;
+}
+
+void ScDPSaveData::GetAllDimensionsByOrientation(
+ sheet::DataPilotFieldOrientation eOrientation, std::vector<const ScDPSaveDimension*>& rDims) const
+{
+ std::vector<const ScDPSaveDimension*> aDims;
+ for (auto const& it : m_DimList)
+ {
+ const ScDPSaveDimension& rDim = *it;
+ if (rDim.GetOrientation() != eOrientation)
+ continue;
+
+ aDims.push_back(&rDim);
+ }
+
+ rDims.swap(aDims);
+}
+
+void ScDPSaveData::AddDimension(ScDPSaveDimension* pDim)
+{
+ if (!pDim)
+ return;
+
+ CheckDuplicateName(*pDim);
+ m_DimList.push_back(std::unique_ptr<ScDPSaveDimension>(pDim));
+
+ DimensionsChanged();
+}
+
+ScDPSaveDimension* ScDPSaveData::GetDimensionByName(const OUString& rName)
+{
+ for (auto const& iter : m_DimList)
+ {
+ if (iter->GetName() == rName && !iter->IsDataLayout() )
+ return &(*iter);
+ }
+
+ return AppendNewDimension(rName, false);
+}
+
+ScDPSaveDimension* ScDPSaveData::GetExistingDimensionByName(std::u16string_view rName) const
+{
+ for (auto const& iter : m_DimList)
+ {
+ if (iter->GetName() == rName && !iter->IsDataLayout() )
+ return &(*iter);
+ }
+ return nullptr; // don't create new
+}
+
+ScDPSaveDimension* ScDPSaveData::GetNewDimensionByName(const OUString& rName)
+{
+ for (auto const& iter : m_DimList)
+ {
+ if (iter->GetName() == rName && !iter->IsDataLayout() )
+ return DuplicateDimension(rName);
+ }
+
+ return AppendNewDimension(rName, false);
+}
+
+ScDPSaveDimension* ScDPSaveData::GetDataLayoutDimension()
+{
+ ScDPSaveDimension* pDim = GetExistingDataLayoutDimension();
+ if (pDim)
+ return pDim;
+
+ return AppendNewDimension(OUString(), true);
+}
+
+ScDPSaveDimension* ScDPSaveData::GetExistingDataLayoutDimension() const
+{
+ for (auto const& iter : m_DimList)
+ {
+ if ( iter->IsDataLayout() )
+ return &(*iter);
+ }
+ return nullptr;
+}
+
+ScDPSaveDimension* ScDPSaveData::DuplicateDimension(std::u16string_view rName)
+{
+ // always insert new
+
+ ScDPSaveDimension* pOld = GetExistingDimensionByName(rName);
+ if (!pOld)
+ return nullptr;
+
+ ScDPSaveDimension* pNew = new ScDPSaveDimension( *pOld );
+ AddDimension(pNew);
+ return pNew;
+}
+
+void ScDPSaveData::RemoveDimensionByName(const OUString& rName)
+{
+ auto iter = std::find_if(m_DimList.begin(), m_DimList.end(),
+ [&rName](const std::unique_ptr<ScDPSaveDimension>& rxDim) {
+ return rxDim->GetName() == rName && !rxDim->IsDataLayout(); });
+ if (iter != m_DimList.end())
+ {
+ m_DimList.erase(iter);
+ RemoveDuplicateNameCount(rName);
+ DimensionsChanged();
+ }
+}
+
+ScDPSaveDimension& ScDPSaveData::DuplicateDimension( const ScDPSaveDimension& rDim )
+{
+ ScDPSaveDimension* pNew = new ScDPSaveDimension( rDim );
+ AddDimension(pNew);
+ return *pNew;
+}
+
+ScDPSaveDimension* ScDPSaveData::GetInnermostDimension(DataPilotFieldOrientation nOrientation)
+{
+ // return the innermost dimension for the given orientation,
+ // excluding data layout dimension
+
+ auto iter = std::find_if(m_DimList.rbegin(), m_DimList.rend(),
+ [&nOrientation](const std::unique_ptr<ScDPSaveDimension>& rxDim) {
+ return rxDim->GetOrientation() == nOrientation && !rxDim->IsDataLayout(); });
+ if (iter != m_DimList.rend())
+ return iter->get();
+
+ return nullptr;
+}
+
+ScDPSaveDimension* ScDPSaveData::GetFirstDimension(sheet::DataPilotFieldOrientation eOrientation)
+{
+ for (auto const& iter : m_DimList)
+ {
+ if (iter->GetOrientation() == eOrientation && !iter->IsDataLayout())
+ return &(*iter);
+ }
+ return nullptr;
+}
+
+tools::Long ScDPSaveData::GetDataDimensionCount() const
+{
+ tools::Long nDataCount = 0;
+
+ for (auto const& iter : m_DimList)
+ {
+ if (iter->GetOrientation() == sheet::DataPilotFieldOrientation_DATA)
+ ++nDataCount;
+ }
+
+ return nDataCount;
+}
+
+void ScDPSaveData::SetPosition( ScDPSaveDimension* pDim, tools::Long nNew )
+{
+ // position (nNew) is counted within dimensions of the same orientation
+
+ DataPilotFieldOrientation nOrient = pDim->GetOrientation();
+
+ auto it = std::find_if(m_DimList.begin(), m_DimList.end(),
+ [&pDim](const std::unique_ptr<ScDPSaveDimension>& rxDim) { return pDim == rxDim.get(); });
+ if (it != m_DimList.end())
+ {
+ // Tell vector<unique_ptr> to give up ownership of this element.
+ // Don't delete this instance as it is re-inserted into the
+ // container later.
+ it->release();
+ m_DimList.erase(it);
+ }
+
+ auto iterInsert = std::find_if(m_DimList.begin(), m_DimList.end(),
+ [&nOrient, &nNew](const std::unique_ptr<ScDPSaveDimension>& rxDim) {
+ if (rxDim->GetOrientation() == nOrient )
+ --nNew;
+ return nNew <= 0;
+ });
+
+ m_DimList.insert(iterInsert, std::unique_ptr<ScDPSaveDimension>(pDim));
+ DimensionsChanged();
+}
+
+void ScDPSaveData::SetColumnGrand(bool bSet)
+{
+ nColumnGrandMode = sal_uInt16(bSet);
+}
+
+void ScDPSaveData::SetRowGrand(bool bSet)
+{
+ nRowGrandMode = sal_uInt16(bSet);
+}
+
+void ScDPSaveData::SetIgnoreEmptyRows(bool bSet)
+{
+ nIgnoreEmptyMode = sal_uInt16(bSet);
+}
+
+void ScDPSaveData::SetRepeatIfEmpty(bool bSet)
+{
+ nRepeatEmptyMode = sal_uInt16(bSet);
+}
+
+void ScDPSaveData::SetFilterButton(bool bSet)
+{
+ bFilterButton = bSet;
+}
+
+void ScDPSaveData::SetDrillDown(bool bSet)
+{
+ bDrillDown = bSet;
+}
+
+static void lcl_ResetOrient( const uno::Reference<sheet::XDimensionsSupplier>& xSource )
+{
+ uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions();
+ uno::Reference<container::XIndexAccess> xIntDims = new ScNameToIndexAccess( xDimsName );
+ tools::Long nIntCount = xIntDims->getCount();
+ for (tools::Long nIntDim=0; nIntDim<nIntCount; nIntDim++)
+ {
+ uno::Reference<beans::XPropertySet> xDimProp(xIntDims->getByIndex(nIntDim), uno::UNO_QUERY);
+ if (xDimProp.is())
+ {
+ xDimProp->setPropertyValue( SC_UNO_DP_ORIENTATION, uno::Any(sheet::DataPilotFieldOrientation_HIDDEN) );
+ }
+ }
+}
+
+void ScDPSaveData::WriteToSource( const uno::Reference<sheet::XDimensionsSupplier>& xSource )
+{
+ if (!xSource.is())
+ return;
+
+ // source options must be first!
+
+ uno::Reference<beans::XPropertySet> xSourceProp( xSource, uno::UNO_QUERY );
+ SAL_WARN_IF( !xSourceProp.is(), "sc.core", "no properties at source" );
+ if ( xSourceProp.is() )
+ {
+ // source options are not available for external sources
+ //TODO: use XPropertySetInfo to test for availability?
+
+ try
+ {
+ if ( nIgnoreEmptyMode != SC_DPSAVEMODE_DONTKNOW )
+ lcl_SetBoolProperty( xSourceProp,
+ SC_UNO_DP_IGNOREEMPTY, static_cast<bool>(nIgnoreEmptyMode) );
+ if ( nRepeatEmptyMode != SC_DPSAVEMODE_DONTKNOW )
+ lcl_SetBoolProperty( xSourceProp,
+ SC_UNO_DP_REPEATEMPTY, static_cast<bool>(nRepeatEmptyMode) );
+ }
+ catch(uno::Exception&)
+ {
+ // no error
+ }
+
+ const std::optional<OUString> & pGrandTotalName = GetGrandTotalName();
+ if (pGrandTotalName)
+ ScUnoHelpFunctions::SetOptionalPropertyValue(xSourceProp, SC_UNO_DP_GRANDTOTAL_NAME, *pGrandTotalName);
+ }
+
+ // exceptions in the other calls are errors
+ try
+ {
+ // reset all orientations
+ //TODO: "forgetSettings" or similar at source ?????
+ //TODO: reset all duplicated dimensions, or reuse them below !!!
+ SAL_INFO("sc.core", "ScDPSaveData::WriteToSource");
+
+ lcl_ResetOrient( xSource );
+
+ uno::Reference<container::XNameAccess> xDimsName = xSource->getDimensions();
+ uno::Reference<container::XIndexAccess> xIntDims = new ScNameToIndexAccess( xDimsName );
+ tools::Long nIntCount = xIntDims->getCount();
+
+ for (const auto& rxDim : m_DimList)
+ {
+ OUString aName = rxDim->GetName();
+ OUString aCoreName = ScDPUtil::getSourceDimensionName(aName);
+
+ SAL_INFO("sc.core", aName);
+
+ bool bData = rxDim->IsDataLayout();
+
+ //TODO: getByName for ScDPSource, including DataLayoutDimension !!!!!!!!
+
+ bool bFound = false;
+ for (tools::Long nIntDim=0; nIntDim<nIntCount && !bFound; nIntDim++)
+ {
+ uno::Reference<uno::XInterface> xIntDim(xIntDims->getByIndex(nIntDim),
+ uno::UNO_QUERY);
+ if ( bData )
+ {
+ uno::Reference<beans::XPropertySet> xDimProp( xIntDim, uno::UNO_QUERY );
+ if ( xDimProp.is() )
+ {
+ bFound = ScUnoHelpFunctions::GetBoolProperty( xDimProp,
+ SC_UNO_DP_ISDATALAYOUT );
+ //TODO: error checking -- is "IsDataLayoutDimension" property required??
+ }
+ }
+ else
+ {
+ uno::Reference<container::XNamed> xDimName( xIntDim, uno::UNO_QUERY );
+ if (xDimName.is() && xDimName->getName() == aCoreName)
+ bFound = true;
+ }
+
+ if (bFound)
+ {
+ if (rxDim->GetDupFlag())
+ {
+ uno::Reference<util::XCloneable> xCloneable(xIntDim, uno::UNO_QUERY);
+ SAL_WARN_IF(!xCloneable.is(), "sc.core", "cannot clone dimension");
+ if (xCloneable.is())
+ {
+ uno::Reference<util::XCloneable> xNew = xCloneable->createClone();
+ uno::Reference<container::XNamed> xNewName(xNew, uno::UNO_QUERY);
+ if (xNewName.is())
+ {
+ xNewName->setName(aName);
+ rxDim->WriteToSource(xNew);
+ }
+ }
+ }
+ else
+ rxDim->WriteToSource( xIntDim );
+ }
+ }
+ SAL_WARN_IF(!bFound, "sc.core", "WriteToSource: Dimension not found: " + aName + ".");
+ }
+
+ if ( xSourceProp.is() )
+ {
+ if ( nColumnGrandMode != SC_DPSAVEMODE_DONTKNOW )
+ lcl_SetBoolProperty( xSourceProp,
+ SC_UNO_DP_COLGRAND, static_cast<bool>(nColumnGrandMode) );
+ if ( nRowGrandMode != SC_DPSAVEMODE_DONTKNOW )
+ lcl_SetBoolProperty( xSourceProp,
+ SC_UNO_DP_ROWGRAND, static_cast<bool>(nRowGrandMode) );
+ }
+ }
+ catch(uno::Exception const &)
+ {
+ TOOLS_WARN_EXCEPTION("sc.core", "WriteToSource");
+ }
+}
+
+bool ScDPSaveData::IsEmpty() const
+{
+ for (auto const& iter : m_DimList)
+ {
+ if (iter->GetOrientation() != sheet::DataPilotFieldOrientation_HIDDEN && !iter->IsDataLayout())
+ return false;
+ }
+ return true; // no entries that are not hidden
+}
+
+void ScDPSaveData::RemoveAllGroupDimensions( const OUString& rSrcDimName, std::vector<OUString>* pDeletedNames )
+{
+ if (!pDimensionData)
+ // No group dimensions exist. Nothing to do.
+ return;
+
+ // Remove numeric group dimension (exists once at most). No need to delete
+ // anything in save data (grouping was done inplace in an existing base
+ // dimension).
+ pDimensionData->RemoveNumGroupDimension(rSrcDimName);
+
+ // Remove named group dimension(s). Dimensions have to be removed from
+ // dimension save data and from save data too.
+ const ScDPSaveGroupDimension* pExistingGroup = pDimensionData->GetGroupDimForBase(rSrcDimName);
+ while ( pExistingGroup )
+ {
+ OUString aGroupDimName = pExistingGroup->GetGroupDimName();
+ pDimensionData->RemoveGroupDimension(aGroupDimName); // pExistingGroup is deleted
+
+ // also remove SaveData settings for the dimension that no longer exists
+ RemoveDimensionByName(aGroupDimName);
+
+ if (pDeletedNames)
+ pDeletedNames->push_back(aGroupDimName);
+
+ // see if there are more group dimensions
+ pExistingGroup = pDimensionData->GetGroupDimForBase(rSrcDimName);
+
+ if ( pExistingGroup && pExistingGroup->GetGroupDimName() == aGroupDimName )
+ {
+ // still get the same group dimension?
+ OSL_FAIL("couldn't remove group dimension");
+ pExistingGroup = nullptr; // avoid endless loop
+ }
+ }
+}
+
+ScDPDimensionSaveData* ScDPSaveData::GetDimensionData()
+{
+ if (!pDimensionData)
+ pDimensionData.reset( new ScDPDimensionSaveData );
+ return pDimensionData.get();
+}
+
+void ScDPSaveData::SetDimensionData( const ScDPDimensionSaveData* pNew )
+{
+ if ( pNew )
+ pDimensionData.reset( new ScDPDimensionSaveData( *pNew ) );
+ else
+ pDimensionData.reset();
+}
+
+void ScDPSaveData::BuildAllDimensionMembers(ScDPTableData* pData)
+{
+ if (mbDimensionMembersBuilt)
+ return;
+
+ // First, build a dimension name-to-index map.
+ typedef std::unordered_map<OUString, tools::Long> NameIndexMap;
+ NameIndexMap aMap;
+ tools::Long nColCount = pData->GetColumnCount();
+ for (tools::Long i = 0; i < nColCount; ++i)
+ aMap.emplace(pData->getDimensionName(i), i);
+
+ NameIndexMap::const_iterator itrEnd = aMap.end();
+
+ for (auto const& iter : m_DimList)
+ {
+ const OUString& rDimName = iter->GetName();
+ if (rDimName.isEmpty())
+ // empty dimension name. It must be data layout.
+ continue;
+
+ NameIndexMap::const_iterator itr = aMap.find(rDimName);
+ if (itr == itrEnd)
+ // dimension name not in the data. This should never happen!
+ continue;
+
+ tools::Long nDimIndex = itr->second;
+ const std::vector<SCROW>& rMembers = pData->GetColumnEntries(nDimIndex);
+ size_t nMemberCount = rMembers.size();
+ for (size_t j = 0; j < nMemberCount; ++j)
+ {
+ const ScDPItemData* pMemberData = pData->GetMemberById( nDimIndex, rMembers[j] );
+ OUString aMemName = pData->GetFormattedString(nDimIndex, *pMemberData, false);
+ if (iter->GetExistingMemberByName(aMemName))
+ // this member instance already exists. nothing to do.
+ continue;
+
+ unique_ptr<ScDPSaveMember> pNewMember(new ScDPSaveMember(aMemName));
+ pNewMember->SetIsVisible(true);
+ iter->AddMember(std::move(pNewMember));
+ }
+ }
+
+ mbDimensionMembersBuilt = true;
+}
+
+void ScDPSaveData::SyncAllDimensionMembers(ScDPTableData* pData)
+{
+ typedef std::unordered_map<OUString, tools::Long> NameIndexMap;
+
+ // First, build a dimension name-to-index map.
+ NameIndexMap aMap;
+ tools::Long nColCount = pData->GetColumnCount();
+ for (tools::Long i = 0; i < nColCount; ++i)
+ aMap.emplace(pData->getDimensionName(i), i);
+
+ NameIndexMap::const_iterator itMapEnd = aMap.end();
+
+ for (auto const& it : m_DimList)
+ {
+ const OUString& rDimName = it->GetName();
+ if (rDimName.isEmpty())
+ // empty dimension name. It must be data layout.
+ continue;
+
+ NameIndexMap::const_iterator itMap = aMap.find(rDimName);
+ if (itMap == itMapEnd)
+ // dimension name not in the data. This should never happen!
+ continue;
+
+ ScDPSaveDimension::MemberSetType aMemNames;
+ tools::Long nDimIndex = itMap->second;
+ const std::vector<SCROW>& rMembers = pData->GetColumnEntries(nDimIndex);
+ size_t nMemberCount = rMembers.size();
+ for (size_t j = 0; j < nMemberCount; ++j)
+ {
+ const ScDPItemData* pMemberData = pData->GetMemberById(nDimIndex, rMembers[j]);
+ // ScDPCache::GetItemDataById() (via
+ // ScDPTableData::GetMemberById(),
+ // ScDPGroupTableData::GetMemberById() through
+ // GetCacheTable().getCache()) may return nullptr.
+ if (pMemberData)
+ {
+ OUString aMemName = pData->GetFormattedString(nDimIndex, *pMemberData, false);
+ aMemNames.insert(aMemName);
+ }
+ else
+ {
+ SAL_WARN("sc.core", "No pMemberData for nDimIndex " << nDimIndex << ", rMembers[j] " << rMembers[j]
+ << ", j " << j);
+ }
+ }
+
+ it->RemoveObsoleteMembers(aMemNames);
+ }
+}
+
+bool ScDPSaveData::HasInvisibleMember(std::u16string_view rDimName) const
+{
+ ScDPSaveDimension* pDim = GetExistingDimensionByName(rDimName);
+ if (!pDim)
+ return false;
+
+ return pDim->HasInvisibleMember();
+}
+
+#if DUMP_PIVOT_TABLE
+
+void ScDPSaveData::Dump() const
+{
+ for (auto const& itDim : m_DimList)
+ {
+ const ScDPSaveDimension& rDim = *itDim;
+ rDim.Dump();
+ }
+}
+
+#endif
+
+void ScDPSaveData::CheckDuplicateName(ScDPSaveDimension& rDim)
+{
+ const OUString aName = ScDPUtil::getSourceDimensionName(rDim.GetName());
+ DupNameCountType::iterator it = maDupNameCounts.find(aName);
+ if (it != maDupNameCounts.end())
+ {
+ rDim.SetName(ScDPUtil::createDuplicateDimensionName(aName, ++it->second));
+ rDim.SetDupFlag(true);
+ }
+ else
+ // New name.
+ maDupNameCounts.emplace(aName, 0);
+}
+
+void ScDPSaveData::RemoveDuplicateNameCount(const OUString& rName)
+{
+ OUString aCoreName = rName;
+ if (ScDPUtil::isDuplicateDimension(rName))
+ aCoreName = ScDPUtil::getSourceDimensionName(rName);
+
+ DupNameCountType::iterator it = maDupNameCounts.find(aCoreName);
+ if (it == maDupNameCounts.end())
+ return;
+
+ if (!it->second)
+ {
+ maDupNameCounts.erase(it);
+ return;
+ }
+
+ --it->second;
+}
+
+ScDPSaveDimension* ScDPSaveData::AppendNewDimension(const OUString& rName, bool bDataLayout)
+{
+ if (ScDPUtil::isDuplicateDimension(rName))
+ // This call is for original dimensions only.
+ return nullptr;
+
+ ScDPSaveDimension* pNew = new ScDPSaveDimension(rName, bDataLayout);
+ m_DimList.push_back(std::unique_ptr<ScDPSaveDimension>(pNew));
+ if (!maDupNameCounts.count(rName))
+ maDupNameCounts.emplace(rName, 0);
+
+ DimensionsChanged();
+ return pNew;
+}
+
+void ScDPSaveData::DimensionsChanged()
+{
+ mpDimOrder.reset();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dpsdbtab.cxx b/sc/source/core/data/dpsdbtab.cxx
new file mode 100644
index 000000000..b56217c27
--- /dev/null
+++ b/sc/source/core/data/dpsdbtab.cxx
@@ -0,0 +1,169 @@
+/* -*- 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 <dpsdbtab.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <dpfilteredcache.hxx>
+#include <document.hxx>
+#include <dpobject.hxx>
+
+#include <com/sun/star/sdb/CommandType.hpp>
+
+using namespace com::sun::star;
+
+using ::std::vector;
+using ::com::sun::star::uno::Sequence;
+using ::com::sun::star::uno::Any;
+
+sal_Int32 ScImportSourceDesc::GetCommandType() const
+{
+ sal_Int32 nSdbType = -1;
+
+ switch ( nType )
+ {
+ case sheet::DataImportMode_SQL: nSdbType = sdb::CommandType::COMMAND; break;
+ case sheet::DataImportMode_TABLE: nSdbType = sdb::CommandType::TABLE; break;
+ case sheet::DataImportMode_QUERY: nSdbType = sdb::CommandType::QUERY; break;
+ default:
+ ;
+ }
+ return nSdbType;
+}
+
+const ScDPCache* ScImportSourceDesc::CreateCache(const ScDPDimensionSaveData* pDimData) const
+{
+ if (!mpDoc)
+ return nullptr;
+
+ sal_Int32 nSdbType = GetCommandType();
+ if (nSdbType < 0)
+ return nullptr;
+
+ ScDPCollection::DBCaches& rCaches = mpDoc->GetDPCollection()->GetDBCaches();
+ return rCaches.getCache(nSdbType, aDBName, aObject, pDimData);
+}
+
+ScDatabaseDPData::ScDatabaseDPData(
+ const ScDocument* pDoc, const ScDPCache& rCache) :
+ ScDPTableData(pDoc),
+ aCacheTable(rCache)
+{
+}
+
+ScDatabaseDPData::~ScDatabaseDPData()
+{
+}
+
+void ScDatabaseDPData::DisposeData()
+{
+ //TODO: use OpenDatabase here?
+ aCacheTable.clear();
+}
+
+sal_Int32 ScDatabaseDPData::GetColumnCount()
+{
+ CreateCacheTable();
+ return GetCacheTable().getColSize();
+}
+
+OUString ScDatabaseDPData::getDimensionName(sal_Int32 nColumn)
+{
+ if (getIsDataLayoutDimension(nColumn))
+ {
+ //TODO: different internal and display names?
+ //return "Data";
+ return ScResId(STR_PIVOT_DATA);
+ }
+
+ CreateCacheTable();
+ return aCacheTable.getFieldName(static_cast<SCCOL>(nColumn));
+}
+
+bool ScDatabaseDPData::getIsDataLayoutDimension(sal_Int32 nColumn)
+{
+ return ( nColumn == GetCacheTable().getColSize());
+}
+
+bool ScDatabaseDPData::IsDateDimension(sal_Int32 /* nDim */)
+{
+ //TODO: later...
+ return false;
+}
+
+void ScDatabaseDPData::SetEmptyFlags( bool /* bIgnoreEmptyRows */, bool /* bRepeatIfEmpty */ )
+{
+ // not used for database data
+ //TODO: disable flags
+}
+
+void ScDatabaseDPData::CreateCacheTable()
+{
+ if (!aCacheTable.empty())
+ // cache table already created.
+ return;
+
+ aCacheTable.fillTable();
+}
+
+void ScDatabaseDPData::FilterCacheTable(std::vector<ScDPFilteredCache::Criterion>&& rCriteria, std::unordered_set<sal_Int32>&& rCatDims)
+{
+ CreateCacheTable();
+ aCacheTable.filterByPageDimension(
+ rCriteria, (IsRepeatIfEmpty() ? std::move(rCatDims) : std::unordered_set<sal_Int32>()));
+}
+
+void ScDatabaseDPData::GetDrillDownData(std::vector<ScDPFilteredCache::Criterion>&& rCriteria, std::unordered_set<sal_Int32>&& rCatDims, Sequence< Sequence<Any> >& rData)
+{
+ CreateCacheTable();
+ sal_Int32 nRowSize = aCacheTable.getRowSize();
+ if (!nRowSize)
+ return;
+
+ aCacheTable.filterTable(
+ rCriteria, rData, IsRepeatIfEmpty() ? std::move(rCatDims) : std::unordered_set<sal_Int32>());
+}
+
+void ScDatabaseDPData::CalcResults(CalcInfo& rInfo, bool bAutoShow)
+{
+ CreateCacheTable();
+ CalcResultsFromCacheTable( aCacheTable, rInfo, bAutoShow);
+}
+
+const ScDPFilteredCache& ScDatabaseDPData::GetCacheTable() const
+{
+ return aCacheTable;
+}
+
+void ScDatabaseDPData::ReloadCacheTable()
+{
+ aCacheTable.clear();
+ CreateCacheTable();
+}
+
+#if DUMP_PIVOT_TABLE
+
+void ScDatabaseDPData::Dump() const
+{
+ // TODO : Implement this.
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dpshttab.cxx b/sc/source/core/data/dpshttab.cxx
new file mode 100644
index 000000000..a5fd1bd01
--- /dev/null
+++ b/sc/source/core/data/dpshttab.cxx
@@ -0,0 +1,323 @@
+/* -*- 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 <unotools/charclass.hxx>
+
+#include <dpcache.hxx>
+#include <dpshttab.hxx>
+#include <document.hxx>
+#include <dpfilteredcache.hxx>
+#include <dpobject.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <rangenam.hxx>
+#include <queryentry.hxx>
+
+#include <osl/diagnose.h>
+
+#include <vector>
+
+using namespace ::com::sun::star;
+using ::com::sun::star::uno::Any;
+using ::com::sun::star::uno::Sequence;
+using ::std::vector;
+
+ScSheetDPData::ScSheetDPData(const ScDocument* pD, const ScSheetSourceDesc& rDesc, const ScDPCache& rCache) :
+ ScDPTableData(pD),
+ aQuery ( rDesc.GetQueryParam() ),
+ bIgnoreEmptyRows( false ),
+ bRepeatIfEmpty(false),
+ aCacheTable(rCache)
+{
+ SCSIZE nEntryCount( aQuery.GetEntryCount());
+ for (SCSIZE j = 0; j < nEntryCount; ++j)
+ {
+ ScQueryEntry& rEntry = aQuery.GetEntry(j);
+ if (rEntry.bDoQuery)
+ {
+ ScQueryEntry::Item& rItem = rEntry.GetQueryItem();
+ if (rItem.meType == ScQueryEntry::ByString)
+ {
+ sal_uInt32 nIndex = 0;
+ bool bNumber = pD->GetFormatTable()->IsNumberFormat(
+ rItem.maString.getString(), nIndex, rItem.mfVal);
+ rItem.meType = bNumber ? ScQueryEntry::ByValue : ScQueryEntry::ByString;
+ }
+ }
+ }
+}
+
+ScSheetDPData::~ScSheetDPData()
+{
+}
+
+void ScSheetDPData::DisposeData()
+{
+ aCacheTable.clear();
+}
+
+sal_Int32 ScSheetDPData::GetColumnCount()
+{
+ CreateCacheTable();
+ return aCacheTable.getColSize();
+}
+
+OUString ScSheetDPData::getDimensionName(sal_Int32 nColumn)
+{
+ CreateCacheTable();
+ if (getIsDataLayoutDimension(nColumn))
+ {
+ //TODO: different internal and display names?
+ //return "Data";
+ return ScResId(STR_PIVOT_DATA);
+ }
+ else if (nColumn >= aCacheTable.getColSize())
+ {
+ OSL_FAIL("getDimensionName: invalid dimension");
+ return OUString();
+ }
+ else
+ {
+ return aCacheTable.getFieldName(static_cast<SCCOL>(nColumn));
+ }
+}
+
+bool ScSheetDPData::IsDateDimension(sal_Int32 nDim)
+{
+ CreateCacheTable();
+ tools::Long nColCount = aCacheTable.getColSize();
+ if (getIsDataLayoutDimension(nDim))
+ {
+ return false;
+ }
+ else if (nDim >= nColCount)
+ {
+ OSL_FAIL("IsDateDimension: invalid dimension");
+ return false;
+ }
+ else
+ {
+ return GetCacheTable().getCache().IsDateDimension( nDim);
+ }
+}
+
+sal_uInt32 ScSheetDPData::GetNumberFormat(sal_Int32 nDim)
+{
+ CreateCacheTable();
+ if (getIsDataLayoutDimension(nDim))
+ {
+ return 0;
+ }
+ else if (nDim >= GetCacheTable().getColSize())
+ {
+ OSL_FAIL("GetNumberFormat: invalid dimension");
+ return 0;
+ }
+ else
+ {
+ return GetCacheTable().getCache().GetNumberFormat( nDim );
+ }
+}
+sal_uInt32 ScDPTableData::GetNumberFormatByIdx( NfIndexTableOffset eIdx )
+{
+ if( !mpDoc )
+ return 0;
+
+ if ( SvNumberFormatter* pFormatter = mpDoc->GetFormatTable() )
+ return pFormatter->GetFormatIndex( eIdx, LANGUAGE_SYSTEM );
+
+ return 0;
+}
+
+bool ScSheetDPData::getIsDataLayoutDimension(sal_Int32 nColumn)
+{
+ CreateCacheTable();
+ return (nColumn ==static_cast<tools::Long>( aCacheTable.getColSize()));
+}
+
+void ScSheetDPData::SetEmptyFlags( bool bIgnoreEmptyRowsP, bool bRepeatIfEmptyP )
+{
+ bIgnoreEmptyRows = bIgnoreEmptyRowsP;
+ bRepeatIfEmpty = bRepeatIfEmptyP;
+}
+
+bool ScSheetDPData::IsRepeatIfEmpty()
+{
+ return bRepeatIfEmpty;
+}
+
+void ScSheetDPData::CreateCacheTable()
+{
+ // Scan and store the data from the source range.
+ if (!aCacheTable.empty())
+ // already cached.
+ return;
+
+ aCacheTable.fillTable(aQuery, bIgnoreEmptyRows, bRepeatIfEmpty);
+}
+
+void ScSheetDPData::FilterCacheTable(std::vector<ScDPFilteredCache::Criterion>&& rCriteria, std::unordered_set<sal_Int32>&& rCatDims)
+{
+ CreateCacheTable();
+ aCacheTable.filterByPageDimension(
+ rCriteria, (IsRepeatIfEmpty() ? rCatDims : std::unordered_set<sal_Int32>()));
+}
+
+void ScSheetDPData::GetDrillDownData(std::vector<ScDPFilteredCache::Criterion>&& rCriteria, std::unordered_set<sal_Int32>&& rCatDims, Sequence< Sequence<Any> >& rData)
+{
+ CreateCacheTable();
+ sal_Int32 nRowSize = aCacheTable.getRowSize();
+ if (!nRowSize)
+ return;
+
+ aCacheTable.filterTable(
+ rCriteria, rData, IsRepeatIfEmpty() ? rCatDims : std::unordered_set<sal_Int32>());
+}
+
+void ScSheetDPData::CalcResults(CalcInfo& rInfo, bool bAutoShow)
+{
+ CreateCacheTable();
+ CalcResultsFromCacheTable(aCacheTable, rInfo, bAutoShow);
+}
+
+const ScDPFilteredCache& ScSheetDPData::GetCacheTable() const
+{
+ return aCacheTable;
+}
+
+void ScSheetDPData::ReloadCacheTable()
+{
+ aCacheTable.clear();
+ CreateCacheTable();
+}
+
+#if DUMP_PIVOT_TABLE
+
+void ScSheetDPData::Dump() const
+{
+ // TODO : Implement this.
+}
+
+#endif
+
+ScSheetSourceDesc::ScSheetSourceDesc(ScDocument* pDoc) :
+ mpDoc(pDoc) {}
+
+void ScSheetSourceDesc::SetSourceRange(const ScRange& rRange)
+{
+ maSourceRange = rRange;
+ maRangeName.clear(); // overwrite existing range name if any.
+}
+
+const ScRange& ScSheetSourceDesc::GetSourceRange() const
+{
+ if (!maRangeName.isEmpty())
+ {
+ // Obtain the source range from the range name first.
+ maSourceRange = ScRange();
+ ScRangeName* pRangeName = mpDoc->GetRangeName();
+ do
+ {
+ if (!pRangeName)
+ break;
+
+ OUString aUpper = ScGlobal::getCharClass().uppercase(maRangeName);
+ const ScRangeData* pData = pRangeName->findByUpperName(aUpper);
+ if (!pData)
+ break;
+
+ // range name found. Fow now, we only use the first token and
+ // ignore the rest.
+ ScRange aRange;
+ if (!pData->IsReference(aRange))
+ break;
+
+ maSourceRange = aRange;
+ }
+ while (false);
+ }
+ return maSourceRange;
+}
+
+void ScSheetSourceDesc::SetRangeName(const OUString& rName)
+{
+ maRangeName = rName;
+}
+
+bool ScSheetSourceDesc::HasRangeName() const
+{
+ return !maRangeName.isEmpty();
+}
+
+void ScSheetSourceDesc::SetQueryParam(const ScQueryParam& rParam)
+{
+ maQueryParam = rParam;
+}
+
+bool ScSheetSourceDesc::operator== (const ScSheetSourceDesc& rOther) const
+{
+ return maSourceRange == rOther.maSourceRange &&
+ maRangeName == rOther.maRangeName &&
+ maQueryParam == rOther.maQueryParam;
+}
+
+const ScDPCache* ScSheetSourceDesc::CreateCache(const ScDPDimensionSaveData* pDimData) const
+{
+ if (!mpDoc)
+ return nullptr;
+
+ TranslateId pErrId = CheckSourceRange();
+ if (pErrId)
+ {
+ OSL_FAIL( "Error Create Cache" );
+ return nullptr;
+ }
+
+ // All cache instances are managed centrally by ScDPCollection.
+ ScDPCollection* pDPs = mpDoc->GetDPCollection();
+ if (HasRangeName())
+ {
+ // Name-based data source.
+ ScDPCollection::NameCaches& rCaches = pDPs->GetNameCaches();
+ return rCaches.getCache(GetRangeName(), GetSourceRange(), pDimData);
+ }
+
+ ScDPCollection::SheetCaches& rCaches = pDPs->GetSheetCaches();
+ return rCaches.getCache(GetSourceRange(), pDimData);
+}
+
+TranslateId ScSheetSourceDesc::CheckSourceRange() const
+{
+ if (!mpDoc)
+ return STR_ERR_DATAPILOTSOURCE;
+
+ // Make sure the range is valid and sane.
+ const ScRange& rSrcRange = GetSourceRange();
+ if (!rSrcRange.IsValid())
+ return STR_ERR_DATAPILOTSOURCE;
+
+ if (rSrcRange.aStart.Col() > rSrcRange.aEnd.Col() || rSrcRange.aStart.Row() > rSrcRange.aEnd.Row())
+ return STR_ERR_DATAPILOTSOURCE;
+
+ return {};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dptabdat.cxx b/sc/source/core/data/dptabdat.cxx
new file mode 100644
index 000000000..7b3c80712
--- /dev/null
+++ b/sc/source/core/data/dptabdat.cxx
@@ -0,0 +1,292 @@
+/* -*- 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 <dptabdat.hxx>
+#include <dpcache.hxx>
+#include <dpfilteredcache.hxx>
+#include <dptabres.hxx>
+
+#include <osl/diagnose.h>
+#include <tools/date.hxx>
+
+
+using namespace ::com::sun::star;
+using ::std::vector;
+
+ScDPTableData::CalcInfo::CalcInfo() :
+ pInitState( nullptr ),
+ pColRoot( nullptr ),
+ pRowRoot( nullptr )
+{
+}
+
+ScDPTableData::ScDPTableData(const ScDocument* pDoc) :
+ mpDoc(pDoc)
+{
+ nLastDateVal = nLastHier = nLastLevel = nLastRet = -1; // invalid
+
+ //TODO: reset before new calculation (in case the base date is changed)
+}
+
+ScDPTableData::~ScDPTableData()
+{
+}
+
+OUString ScDPTableData::GetFormattedString(sal_Int32 nDim, const ScDPItemData& rItem, bool bLocaleIndependent) const
+{
+ const ScDPCache& rCache = GetCacheTable().getCache();
+ return rCache.GetFormattedString(nDim, rItem, bLocaleIndependent);
+}
+
+tools::Long ScDPTableData::GetDatePart( tools::Long nDateVal, tools::Long nHierarchy, tools::Long nLevel )
+{
+ if ( nDateVal == nLastDateVal && nHierarchy == nLastHier && nLevel == nLastLevel )
+ return nLastRet;
+
+ Date aDate( 30,12,1899 ); //TODO: get from source data (and cache here)
+ aDate.AddDays( nDateVal);
+
+ tools::Long nRet = 0;
+ switch (nHierarchy)
+ {
+ case SC_DAPI_HIERARCHY_QUARTER:
+ switch (nLevel)
+ {
+ case 0: nRet = aDate.GetYear(); break;
+ case 1: nRet = (aDate.GetMonth()-1) / 3 + 1; break;
+ case 2: nRet = aDate.GetMonth(); break;
+ case 3: nRet = aDate.GetDay(); break;
+ default:
+ OSL_FAIL("GetDatePart: wrong level");
+ }
+ break;
+ case SC_DAPI_HIERARCHY_WEEK:
+ switch (nLevel)
+ {
+ //TODO: use settings for different definitions
+ case 0: nRet = aDate.GetYear(); break; //!...
+ case 1: nRet = aDate.GetWeekOfYear(); break;
+ case 2: nRet = static_cast<tools::Long>(aDate.GetDayOfWeek()); break;
+ default:
+ OSL_FAIL("GetDatePart: wrong level");
+ }
+ break;
+ default:
+ OSL_FAIL("GetDatePart: wrong hierarchy");
+ }
+
+ nLastDateVal = nDateVal;
+ nLastHier = nHierarchy;
+ nLastLevel = nLevel;
+ nLastRet = nRet;
+
+ return nRet;
+}
+
+bool ScDPTableData::IsRepeatIfEmpty()
+{
+ return false;
+}
+
+sal_uInt32 ScDPTableData::GetNumberFormat(sal_Int32)
+{
+ return 0; // default format
+}
+
+bool ScDPTableData::IsBaseForGroup(sal_Int32) const
+{
+ return false; // always false
+}
+
+sal_Int32 ScDPTableData::GetGroupBase(sal_Int32) const
+{
+ return -1; // always none
+}
+
+bool ScDPTableData::IsNumOrDateGroup(sal_Int32) const
+{
+ return false; // always false
+}
+
+bool ScDPTableData::IsInGroup( const ScDPItemData&, sal_Int32,
+ const ScDPItemData&, sal_Int32 ) const
+{
+ OSL_FAIL("IsInGroup shouldn't be called for non-group data");
+ return false;
+}
+
+bool ScDPTableData::HasCommonElement( const ScDPItemData&, sal_Int32,
+ const ScDPItemData&, sal_Int32 ) const
+{
+ OSL_FAIL("HasCommonElement shouldn't be called for non-group data");
+ return false;
+}
+void ScDPTableData::FillRowDataFromCacheTable(sal_Int32 nRow, const ScDPFilteredCache& rCacheTable,
+ const CalcInfo& rInfo, CalcRowData& rData)
+{
+ // column dimensions
+ GetItemData(rCacheTable, nRow, rInfo.aColLevelDims, rData.aColData);
+
+ // row dimensions
+ GetItemData(rCacheTable, nRow, rInfo.aRowLevelDims, rData.aRowData);
+
+ // page dimensions
+ GetItemData(rCacheTable, nRow, rInfo.aPageDims, rData.aPageData);
+
+ tools::Long nCacheColumnCount = rCacheTable.getCache().GetColumnCount();
+ sal_Int32 n = rInfo.aDataSrcCols.size();
+ for (sal_Int32 i = 0; i < n; ++i)
+ {
+ tools::Long nDim = rInfo.aDataSrcCols[i];
+ rData.aValues.emplace_back( );
+ // #i111435# GetItemData needs dimension indexes including groups,
+ // so the index must be checked here (groups aren't useful as data fields).
+ if ( nDim < nCacheColumnCount )
+ {
+ ScDPValue& rVal = rData.aValues.back();
+ rCacheTable.getValue( rVal, static_cast<SCCOL>(nDim), static_cast<SCROW>(nRow));
+ }
+ }
+}
+
+void ScDPTableData::ProcessRowData(CalcInfo& rInfo, const CalcRowData& rData, bool bAutoShow)
+{
+ if (!bAutoShow)
+ {
+ LateInitParams aColParams(rInfo.aColDims, rInfo.aColLevels, false);
+ LateInitParams aRowParams(rInfo.aRowDims, rInfo.aRowLevels, true);
+ // root always init child
+ aColParams.SetInitChild(true);
+ aColParams.SetInitAllChildren( false);
+ aRowParams.SetInitChild(true);
+ aRowParams.SetInitAllChildren( false);
+
+ rInfo.pColRoot->LateInitFrom(aColParams, rData.aColData, 0, *rInfo.pInitState);
+ rInfo.pRowRoot->LateInitFrom(aRowParams, rData.aRowData, 0, *rInfo.pInitState);
+ }
+
+ if ( ( !rInfo.pColRoot->GetChildDimension() || rInfo.pColRoot->GetChildDimension()->IsValidEntry(rData.aColData) ) &&
+ ( !rInfo.pRowRoot->GetChildDimension() || rInfo.pRowRoot->GetChildDimension()->IsValidEntry(rData.aRowData) ) )
+ {
+ //TODO: single process method with ColMembers, RowMembers and data !!!
+ if (rInfo.pColRoot->GetChildDimension())
+ {
+ vector<SCROW> aEmptyData;
+ rInfo.pColRoot->GetChildDimension()->ProcessData(rData.aColData, nullptr, aEmptyData, rData.aValues);
+ }
+
+ rInfo.pRowRoot->ProcessData(rData.aRowData, rInfo.pColRoot->GetChildDimension(),
+ rData.aColData, rData.aValues);
+ }
+}
+
+void ScDPTableData::CalcResultsFromCacheTable(const ScDPFilteredCache& rCacheTable, CalcInfo& rInfo, bool bAutoShow)
+{
+ sal_Int32 nRowSize = rCacheTable.getRowSize();
+ for (sal_Int32 nRow = 0; nRow < nRowSize; ++nRow)
+ {
+ sal_Int32 nLastRow;
+ if (!rCacheTable.isRowActive(nRow, &nLastRow))
+ {
+ nRow = nLastRow;
+ continue;
+ }
+
+ CalcRowData aData;
+ FillRowDataFromCacheTable(nRow, rCacheTable, rInfo, aData);
+ ProcessRowData(rInfo, aData, bAutoShow);
+ }
+}
+
+void ScDPTableData::GetItemData(const ScDPFilteredCache& rCacheTable, sal_Int32 nRow,
+ const vector<sal_Int32>& rDims, vector<SCROW>& rItemData)
+{
+ sal_Int32 nDimSize = rDims.size();
+ rItemData.reserve(rItemData.size() + nDimSize);
+ for (sal_Int32 i = 0; i < nDimSize; ++i)
+ {
+ sal_Int32 nDim = rDims[i];
+
+ if (getIsDataLayoutDimension(nDim))
+ {
+ rItemData.push_back( -1 );
+ continue;
+ }
+
+ nDim = GetSourceDim( nDim );
+ if ( nDim >= rCacheTable.getCache().GetColumnCount() )
+ continue;
+
+ SCROW nId= rCacheTable.getCache().GetItemDataId( static_cast<SCCOL>(nDim), static_cast<SCROW>(nRow), IsRepeatIfEmpty());
+ rItemData.push_back( nId );
+ }
+}
+
+sal_Int32 ScDPTableData::GetMembersCount( sal_Int32 nDim )
+{
+ if ( nDim > MAXCOL )
+ return 0;
+ return GetCacheTable().getFieldEntries( nDim ).size();
+}
+
+const ScDPItemData* ScDPTableData::GetMemberByIndex( sal_Int32 nDim, sal_Int32 nIndex )
+{
+ if ( nIndex >= GetMembersCount( nDim ) )
+ return nullptr;
+
+ const ::std::vector<SCROW>& nMembers = GetCacheTable().getFieldEntries( nDim );
+
+ return GetCacheTable().getCache().GetItemDataById( static_cast<SCCOL>(nDim), static_cast<SCROW>(nMembers[nIndex]) );
+}
+
+const ScDPItemData* ScDPTableData::GetMemberById( sal_Int32 nDim, sal_Int32 nId)
+{
+ return GetCacheTable().getCache().GetItemDataById(nDim, static_cast<SCROW>(nId));
+}
+
+const std::vector< SCROW >& ScDPTableData::GetColumnEntries( sal_Int32 nColumn )
+{
+ return GetCacheTable().getFieldEntries( nColumn );
+}
+
+sal_Int32 ScDPTableData::GetSourceDim( sal_Int32 nDim )
+{
+ return nDim;
+}
+
+sal_Int32 ScDPTableData::Compare( sal_Int32 nDim, sal_Int32 nDataId1, sal_Int32 nDataId2)
+{
+ if ( getIsDataLayoutDimension(nDim) )
+ return 0;
+
+ if ( nDataId1 > nDataId2 )
+ return 1;
+ else if ( nDataId1 == nDataId2 )
+ return 0;
+ else
+ return -1;
+}
+
+#if DUMP_PIVOT_TABLE
+void ScDPTableData::Dump() const
+{
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dptabres.cxx b/sc/source/core/data/dptabres.cxx
new file mode 100644
index 000000000..1037eac1c
--- /dev/null
+++ b/sc/source/core/data/dptabres.cxx
@@ -0,0 +1,4117 @@
+/* -*- 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 <dptabres.hxx>
+
+#include <dptabdat.hxx>
+#include <dptabsrc.hxx>
+#include <global.hxx>
+#include <subtotal.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <dpitemdata.hxx>
+#include <generalfunction.hxx>
+
+#include <document.hxx>
+#include <dpresfilter.hxx>
+#include <dputil.hxx>
+
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+#include <rtl/math.hxx>
+#include <sal/log.hxx>
+
+#include <math.h>
+#include <float.h>
+#include <algorithm>
+#include <limits>
+#include <memory>
+#include <unordered_map>
+
+#include <com/sun/star/sheet/DataResultFlags.hpp>
+#include <com/sun/star/sheet/MemberResultFlags.hpp>
+#include <com/sun/star/sheet/DataPilotFieldReferenceType.hpp>
+#include <com/sun/star/sheet/DataPilotFieldReferenceItemType.hpp>
+#include <com/sun/star/sheet/DataPilotFieldShowItemsMode.hpp>
+#include <com/sun/star/sheet/DataPilotFieldSortMode.hpp>
+#include <com/sun/star/sheet/GeneralFunction2.hpp>
+
+using namespace com::sun::star;
+using ::std::vector;
+using ::std::pair;
+using ::com::sun::star::uno::Sequence;
+
+namespace {
+
+const TranslateId aFuncStrIds[] = // matching enum ScSubTotalFunc
+{
+ {}, // SUBTOTAL_FUNC_NONE
+ STR_FUN_TEXT_AVG, // SUBTOTAL_FUNC_AVE
+ STR_FUN_TEXT_COUNT, // SUBTOTAL_FUNC_CNT
+ STR_FUN_TEXT_COUNT, // SUBTOTAL_FUNC_CNT2
+ STR_FUN_TEXT_MAX, // SUBTOTAL_FUNC_MAX
+ STR_FUN_TEXT_MIN, // SUBTOTAL_FUNC_MIN
+ STR_FUN_TEXT_PRODUCT, // SUBTOTAL_FUNC_PROD
+ STR_FUN_TEXT_STDDEV, // SUBTOTAL_FUNC_STD
+ STR_FUN_TEXT_STDDEV, // SUBTOTAL_FUNC_STDP
+ STR_FUN_TEXT_SUM, // SUBTOTAL_FUNC_SUM
+ STR_FUN_TEXT_VAR, // SUBTOTAL_FUNC_VAR
+ STR_FUN_TEXT_VAR, // SUBTOTAL_FUNC_VARP
+ STR_FUN_TEXT_MEDIAN, // SUBTOTAL_FUNC_MED
+ {} // SUBTOTAL_FUNC_SELECTION_COUNT - not used for pivot table
+};
+
+bool lcl_SearchMember( const std::vector<std::unique_ptr<ScDPResultMember>>& list, SCROW nOrder, SCROW& rIndex)
+{
+ bool bFound = false;
+ SCROW nLo = 0;
+ SCROW nHi = list.size() - 1;
+ SCROW nIndex;
+ while (nLo <= nHi)
+ {
+ nIndex = (nLo + nHi) / 2;
+ if ( list[nIndex]->GetOrder() < nOrder )
+ nLo = nIndex + 1;
+ else
+ {
+ nHi = nIndex - 1;
+ if ( list[nIndex]->GetOrder() == nOrder )
+ {
+ bFound = true;
+ nLo = nIndex;
+ }
+ }
+ }
+ rIndex = nLo;
+ return bFound;
+}
+
+class FilterStack
+{
+ std::vector<ScDPResultFilter>& mrFilters;
+public:
+ explicit FilterStack(std::vector<ScDPResultFilter>& rFilters) : mrFilters(rFilters) {}
+
+ void pushDimName(const OUString& rName, bool bDataLayout)
+ {
+ mrFilters.emplace_back(rName, bDataLayout);
+ }
+
+ void pushDimValue(const OUString& rValueName, const OUString& rValue)
+ {
+ ScDPResultFilter& rFilter = mrFilters.back();
+ rFilter.maValueName = rValueName;
+ rFilter.maValue = rValue;
+ rFilter.mbHasValue = true;
+ }
+
+ ~FilterStack()
+ {
+ ScDPResultFilter& rFilter = mrFilters.back();
+ if (rFilter.mbHasValue)
+ rFilter.mbHasValue = false;
+ else
+ mrFilters.pop_back();
+ }
+};
+
+// function objects for sorting of the column and row members:
+
+class ScDPRowMembersOrder
+{
+ ScDPResultDimension& rDimension;
+ tools::Long nMeasure;
+ bool bAscending;
+
+public:
+ ScDPRowMembersOrder( ScDPResultDimension& rDim, tools::Long nM, bool bAsc ) :
+ rDimension(rDim),
+ nMeasure(nM),
+ bAscending(bAsc)
+ {}
+
+ bool operator()( sal_Int32 nIndex1, sal_Int32 nIndex2 ) const;
+};
+
+class ScDPColMembersOrder
+{
+ ScDPDataDimension& rDimension;
+ tools::Long nMeasure;
+ bool bAscending;
+
+public:
+ ScDPColMembersOrder( ScDPDataDimension& rDim, tools::Long nM, bool bAsc ) :
+ rDimension(rDim),
+ nMeasure(nM),
+ bAscending(bAsc)
+ {}
+
+ bool operator()( sal_Int32 nIndex1, sal_Int32 nIndex2 ) const;
+};
+
+}
+
+static bool lcl_IsLess( const ScDPDataMember* pDataMember1, const ScDPDataMember* pDataMember2, tools::Long nMeasure, bool bAscending )
+{
+ // members can be NULL if used for rows
+
+ ScDPSubTotalState aEmptyState;
+ const ScDPAggData* pAgg1 = pDataMember1 ? pDataMember1->GetConstAggData( nMeasure, aEmptyState ) : nullptr;
+ const ScDPAggData* pAgg2 = pDataMember2 ? pDataMember2->GetConstAggData( nMeasure, aEmptyState ) : nullptr;
+
+ bool bError1 = pAgg1 && pAgg1->HasError();
+ bool bError2 = pAgg2 && pAgg2->HasError();
+ if ( bError1 )
+ return false; // errors are always sorted at the end
+ else if ( bError2 )
+ return true; // errors are always sorted at the end
+ else
+ {
+ double fVal1 = ( pAgg1 && pAgg1->HasData() ) ? pAgg1->GetResult() : 0.0; // no data is sorted as 0
+ double fVal2 = ( pAgg2 && pAgg2->HasData() ) ? pAgg2->GetResult() : 0.0;
+
+ // compare values
+ // don't have to check approxEqual, as this is the only sort criterion
+
+ return bAscending ? ( fVal1 < fVal2 ) : ( fVal1 > fVal2 );
+ }
+}
+
+static bool lcl_IsEqual( const ScDPDataMember* pDataMember1, const ScDPDataMember* pDataMember2, tools::Long nMeasure )
+{
+ // members can be NULL if used for rows
+
+ ScDPSubTotalState aEmptyState;
+ const ScDPAggData* pAgg1 = pDataMember1 ? pDataMember1->GetConstAggData( nMeasure, aEmptyState ) : nullptr;
+ const ScDPAggData* pAgg2 = pDataMember2 ? pDataMember2->GetConstAggData( nMeasure, aEmptyState ) : nullptr;
+
+ bool bError1 = pAgg1 && pAgg1->HasError();
+ bool bError2 = pAgg2 && pAgg2->HasError();
+ if ( bError1 )
+ {
+ if ( bError2 )
+ return true; // equal
+ else
+ return false;
+ }
+ else if ( bError2 )
+ return false;
+ else
+ {
+ double fVal1 = ( pAgg1 && pAgg1->HasData() ) ? pAgg1->GetResult() : 0.0; // no data is sorted as 0
+ double fVal2 = ( pAgg2 && pAgg2->HasData() ) ? pAgg2->GetResult() : 0.0;
+
+ // compare values
+ // this is used to find equal data at the end of the AutoShow range, so approxEqual must be used
+
+ return rtl::math::approxEqual( fVal1, fVal2 );
+ }
+}
+
+bool ScDPRowMembersOrder::operator()( sal_Int32 nIndex1, sal_Int32 nIndex2 ) const
+{
+ const ScDPResultMember* pMember1 = rDimension.GetMember(nIndex1);
+ const ScDPResultMember* pMember2 = rDimension.GetMember(nIndex2);
+
+// make the hide item to the largest order.
+ if ( !pMember1->IsVisible() || !pMember2->IsVisible() )
+ return pMember1->IsVisible();
+ const ScDPDataMember* pDataMember1 = pMember1->GetDataRoot() ;
+ const ScDPDataMember* pDataMember2 = pMember2->GetDataRoot();
+ // GetDataRoot can be NULL if there was no data.
+ // IsVisible == false can happen after AutoShow.
+ return lcl_IsLess( pDataMember1, pDataMember2, nMeasure, bAscending );
+}
+
+bool ScDPColMembersOrder::operator()( sal_Int32 nIndex1, sal_Int32 nIndex2 ) const
+{
+ const ScDPDataMember* pDataMember1 = rDimension.GetMember(nIndex1);
+ const ScDPDataMember* pDataMember2 = rDimension.GetMember(nIndex2);
+ bool bHide1 = pDataMember1 && !pDataMember1->IsVisible();
+ bool bHide2 = pDataMember2 && !pDataMember2->IsVisible();
+ if ( bHide1 || bHide2 )
+ return !bHide1;
+ return lcl_IsLess( pDataMember1, pDataMember2, nMeasure, bAscending );
+}
+
+ScDPInitState::Member::Member(tools::Long nSrcIndex, SCROW nNameIndex) :
+ mnSrcIndex(nSrcIndex), mnNameIndex(nNameIndex) {}
+
+void ScDPInitState::AddMember( tools::Long nSourceIndex, SCROW nMember )
+{
+ maMembers.emplace_back(nSourceIndex, nMember);
+}
+
+void ScDPInitState::RemoveMember()
+{
+ OSL_ENSURE(!maMembers.empty(), "ScDPInitState::RemoveMember: Attempt to remove member while empty.");
+ if (!maMembers.empty())
+ maMembers.pop_back();
+}
+
+namespace {
+
+#if DUMP_PIVOT_TABLE
+void dumpRow(
+ const OUString& rType, const OUString& rName, const ScDPAggData* pAggData,
+ ScDocument* pDoc, ScAddress& rPos )
+{
+ SCCOL nCol = rPos.Col();
+ SCROW nRow = rPos.Row();
+ SCTAB nTab = rPos.Tab();
+ pDoc->SetString( nCol++, nRow, nTab, rType );
+ pDoc->SetString( nCol++, nRow, nTab, rName );
+ while ( pAggData )
+ {
+ pDoc->SetValue( nCol++, nRow, nTab, pAggData->GetResult() );
+ pAggData = pAggData->GetExistingChild();
+ }
+ rPos.SetRow( nRow + 1 );
+}
+
+void indent( ScDocument* pDoc, SCROW nStartRow, const ScAddress& rPos )
+{
+ SCCOL nCol = rPos.Col();
+ SCTAB nTab = rPos.Tab();
+
+ OUString aString;
+ for (SCROW nRow = nStartRow; nRow < rPos.Row(); nRow++)
+ {
+ aString = pDoc->GetString(nCol, nRow, nTab);
+ if (!aString.isEmpty())
+ {
+ aString = " " + aString;
+ pDoc->SetString( nCol, nRow, nTab, aString );
+ }
+ }
+}
+#endif
+
+}
+
+ScDPRunningTotalState::ScDPRunningTotalState( ScDPResultMember* pColRoot, ScDPResultMember* pRowRoot ) :
+ pColResRoot(pColRoot), pRowResRoot(pRowRoot)
+{
+ // These arrays should never be empty as the terminating value must be present at all times.
+ maColVisible.push_back(-1);
+ maColSorted.push_back(-1);
+ maRowVisible.push_back(-1);
+ maRowSorted.push_back(-1);
+}
+
+void ScDPRunningTotalState::AddColIndex( sal_Int32 nVisible, tools::Long nSorted )
+{
+ maColVisible.back() = nVisible;
+ maColVisible.push_back(-1);
+
+ maColSorted.back() = nSorted;
+ maColSorted.push_back(-1);
+}
+
+void ScDPRunningTotalState::AddRowIndex( sal_Int32 nVisible, tools::Long nSorted )
+{
+ maRowVisible.back() = nVisible;
+ maRowVisible.push_back(-1);
+
+ maRowSorted.back() = nSorted;
+ maRowSorted.push_back(-1);
+}
+
+void ScDPRunningTotalState::RemoveColIndex()
+{
+ OSL_ENSURE(!maColVisible.empty() && !maColSorted.empty(), "ScDPRunningTotalState::RemoveColIndex: array is already empty!");
+ if (maColVisible.size() >= 2)
+ {
+ maColVisible.pop_back();
+ maColVisible.back() = -1;
+ }
+
+ if (maColSorted.size() >= 2)
+ {
+ maColSorted.pop_back();
+ maColSorted.back() = -1;
+ }
+}
+
+void ScDPRunningTotalState::RemoveRowIndex()
+{
+ OSL_ENSURE(!maRowVisible.empty() && !maRowSorted.empty(), "ScDPRunningTotalState::RemoveRowIndex: array is already empty!");
+ if (maRowVisible.size() >= 2)
+ {
+ maRowVisible.pop_back();
+ maRowVisible.back() = -1;
+ }
+
+ if (maRowSorted.size() >= 2)
+ {
+ maRowSorted.pop_back();
+ maRowSorted.back() = -1;
+ }
+}
+
+ScDPRelativePos::ScDPRelativePos( tools::Long nBase, tools::Long nDir ) :
+ nBasePos( nBase ),
+ nDirection( nDir )
+{
+}
+
+void ScDPAggData::Update( const ScDPValue& rNext, ScSubTotalFunc eFunc, const ScDPSubTotalState& rSubState )
+{
+ if (nCount<0) // error?
+ return; // nothing more...
+
+ if (rNext.meType == ScDPValue::Empty)
+ return;
+
+ if ( rSubState.eColForce != SUBTOTAL_FUNC_NONE && rSubState.eRowForce != SUBTOTAL_FUNC_NONE &&
+ rSubState.eColForce != rSubState.eRowForce )
+ return;
+ if ( rSubState.eColForce != SUBTOTAL_FUNC_NONE ) eFunc = rSubState.eColForce;
+ if ( rSubState.eRowForce != SUBTOTAL_FUNC_NONE ) eFunc = rSubState.eRowForce;
+
+ if ( eFunc == SUBTOTAL_FUNC_NONE )
+ return;
+
+ if ( eFunc != SUBTOTAL_FUNC_CNT2 ) // CNT2 counts everything, incl. strings and errors
+ {
+ if (rNext.meType == ScDPValue::Error)
+ {
+ nCount = -1; // -1 for error (not for CNT2)
+ return;
+ }
+ if (rNext.meType == ScDPValue::String)
+ return; // ignore
+ }
+
+ ++nCount; // for all functions
+
+ switch (eFunc)
+ {
+ case SUBTOTAL_FUNC_SUM:
+ case SUBTOTAL_FUNC_AVE:
+ if ( !SubTotal::SafePlus( fVal, rNext.mfValue ) )
+ nCount = -1; // -1 for error
+ break;
+ case SUBTOTAL_FUNC_PROD:
+ if ( nCount == 1 ) // copy first value (fVal is initialized to 0)
+ fVal = rNext.mfValue;
+ else if ( !SubTotal::SafeMult( fVal, rNext.mfValue ) )
+ nCount = -1; // -1 for error
+ break;
+ case SUBTOTAL_FUNC_CNT:
+ case SUBTOTAL_FUNC_CNT2:
+ // nothing more than incrementing nCount
+ break;
+ case SUBTOTAL_FUNC_MAX:
+ if ( nCount == 1 || rNext.mfValue > fVal )
+ fVal = rNext.mfValue;
+ break;
+ case SUBTOTAL_FUNC_MIN:
+ if ( nCount == 1 || rNext.mfValue < fVal )
+ fVal = rNext.mfValue;
+ break;
+ case SUBTOTAL_FUNC_STD:
+ case SUBTOTAL_FUNC_STDP:
+ case SUBTOTAL_FUNC_VAR:
+ case SUBTOTAL_FUNC_VARP:
+ maWelford.update( rNext.mfValue);
+ break;
+ case SUBTOTAL_FUNC_MED:
+ {
+ auto aIter = std::upper_bound(mSortedValues.begin(), mSortedValues.end(), rNext.mfValue);
+ if (aIter == mSortedValues.end())
+ mSortedValues.push_back(rNext.mfValue);
+ else
+ mSortedValues.insert(aIter, rNext.mfValue);
+ }
+ break;
+ default:
+ OSL_FAIL("invalid function");
+ }
+}
+
+void ScDPAggData::Calculate( ScSubTotalFunc eFunc, const ScDPSubTotalState& rSubState )
+{
+ // calculate the original result
+ // (without reference value, used as the basis for reference value calculation)
+
+ // called several times at the cross-section of several subtotals - don't calculate twice then
+ if ( IsCalculated() )
+ return;
+
+ if ( rSubState.eColForce != SUBTOTAL_FUNC_NONE ) eFunc = rSubState.eColForce;
+ if ( rSubState.eRowForce != SUBTOTAL_FUNC_NONE ) eFunc = rSubState.eRowForce;
+
+ if ( eFunc == SUBTOTAL_FUNC_NONE ) // this happens when there is no data dimension
+ {
+ nCount = SC_DPAGG_RESULT_EMPTY; // make sure there's a valid state for HasData etc.
+ return;
+ }
+
+ // check the error conditions for the selected function
+
+ bool bError = false;
+ switch (eFunc)
+ {
+ case SUBTOTAL_FUNC_SUM:
+ case SUBTOTAL_FUNC_PROD:
+ case SUBTOTAL_FUNC_CNT:
+ case SUBTOTAL_FUNC_CNT2:
+ bError = ( nCount < 0 ); // only real errors
+ break;
+
+ case SUBTOTAL_FUNC_AVE:
+ case SUBTOTAL_FUNC_MED:
+ case SUBTOTAL_FUNC_MAX:
+ case SUBTOTAL_FUNC_MIN:
+ bError = ( nCount <= 0 ); // no data is an error
+ break;
+
+ case SUBTOTAL_FUNC_STDP:
+ case SUBTOTAL_FUNC_VARP:
+ bError = ( nCount <= 0 ); // no data is an error
+ assert(bError || nCount == static_cast<sal_Int64>(maWelford.getCount()));
+ break;
+
+ case SUBTOTAL_FUNC_STD:
+ case SUBTOTAL_FUNC_VAR:
+ bError = ( nCount < 2 ); // need at least 2 values
+ assert(bError || nCount == static_cast<sal_Int64>(maWelford.getCount()));
+ break;
+
+ default:
+ OSL_FAIL("invalid function");
+ }
+
+ // calculate the selected function
+
+ double fResult = 0.0;
+ if ( !bError )
+ {
+ switch (eFunc)
+ {
+ case SUBTOTAL_FUNC_MAX:
+ case SUBTOTAL_FUNC_MIN:
+ case SUBTOTAL_FUNC_SUM:
+ case SUBTOTAL_FUNC_PROD:
+ // different error conditions are handled above
+ fResult = fVal;
+ break;
+
+ case SUBTOTAL_FUNC_CNT:
+ case SUBTOTAL_FUNC_CNT2:
+ fResult = nCount;
+ break;
+
+ case SUBTOTAL_FUNC_AVE:
+ if ( nCount > 0 )
+ fResult = fVal / static_cast<double>(nCount);
+ break;
+
+ case SUBTOTAL_FUNC_STD:
+ if ( nCount >= 2 )
+ {
+ fResult = maWelford.getVarianceSample();
+ if (fResult < 0.0)
+ bError = true;
+ else
+ fResult = sqrt( fResult);
+ }
+ break;
+ case SUBTOTAL_FUNC_VAR:
+ if ( nCount >= 2 )
+ fResult = maWelford.getVarianceSample();
+ break;
+ case SUBTOTAL_FUNC_STDP:
+ if ( nCount > 0 )
+ {
+ fResult = maWelford.getVariancePopulation();
+ if (fResult < 0.0)
+ bError = true;
+ else
+ fResult = sqrt( fResult);
+ }
+ break;
+ case SUBTOTAL_FUNC_VARP:
+ if ( nCount > 0 )
+ fResult = maWelford.getVariancePopulation();
+ break;
+ case SUBTOTAL_FUNC_MED:
+ {
+ size_t nSize = mSortedValues.size();
+ if (nSize > 0)
+ {
+ assert(nSize == static_cast<size_t>(nCount));
+ if ((nSize % 2) == 1)
+ fResult = mSortedValues[nSize / 2];
+ else
+ fResult = (mSortedValues[nSize / 2 - 1] + mSortedValues[nSize / 2]) / 2.0;
+ }
+ }
+ break;
+ default:
+ OSL_FAIL("invalid function");
+ }
+ }
+
+ bool bEmpty = ( nCount == 0 ); // no data
+
+ // store the result
+ // Empty is checked first, so empty results are shown empty even for "average" etc.
+ // If these results should be treated as errors in reference value calculations,
+ // a separate state value (EMPTY_ERROR) is needed.
+ // Now, for compatibility, empty "average" results are counted as 0.
+
+ if ( bEmpty )
+ nCount = SC_DPAGG_RESULT_EMPTY;
+ else if ( bError )
+ nCount = SC_DPAGG_RESULT_ERROR;
+ else
+ nCount = SC_DPAGG_RESULT_VALID;
+
+ if ( bEmpty || bError )
+ fResult = 0.0; // default, in case the state is later modified
+
+ fVal = fResult; // used directly from now on
+ fAux = 0.0; // used for running total or original result of reference value
+}
+
+bool ScDPAggData::IsCalculated() const
+{
+ return ( nCount <= SC_DPAGG_RESULT_EMPTY );
+}
+
+double ScDPAggData::GetResult() const
+{
+ assert( IsCalculated() && "ScDPAggData not calculated" );
+
+ return fVal; // use calculated value
+}
+
+bool ScDPAggData::HasError() const
+{
+ assert( IsCalculated() && "ScDPAggData not calculated" );
+
+ return ( nCount == SC_DPAGG_RESULT_ERROR );
+}
+
+bool ScDPAggData::HasData() const
+{
+ assert( IsCalculated() && "ScDPAggData not calculated" );
+
+ return ( nCount != SC_DPAGG_RESULT_EMPTY ); // values or error
+}
+
+void ScDPAggData::SetResult( double fNew )
+{
+ assert( IsCalculated() && "ScDPAggData not calculated" );
+
+ fVal = fNew; // don't reset error flag
+}
+
+void ScDPAggData::SetError()
+{
+ assert( IsCalculated() && "ScDPAggData not calculated" );
+
+ nCount = SC_DPAGG_RESULT_ERROR;
+}
+
+void ScDPAggData::SetEmpty( bool bSet )
+{
+ assert( IsCalculated() && "ScDPAggData not calculated" );
+
+ if ( bSet )
+ nCount = SC_DPAGG_RESULT_EMPTY;
+ else
+ nCount = SC_DPAGG_RESULT_VALID;
+}
+
+double ScDPAggData::GetAuxiliary() const
+{
+ // after Calculate, fAux is used as auxiliary value for running totals and reference values
+ assert( IsCalculated() && "ScDPAggData not calculated" );
+
+ return fAux;
+}
+
+void ScDPAggData::SetAuxiliary( double fNew )
+{
+ // after Calculate, fAux is used as auxiliary value for running totals and reference values
+ assert( IsCalculated() && "ScDPAggData not calculated" );
+
+ fAux = fNew;
+}
+
+ScDPAggData* ScDPAggData::GetChild()
+{
+ if (!pChild)
+ pChild.reset( new ScDPAggData );
+ return pChild.get();
+}
+
+void ScDPAggData::Reset()
+{
+ maWelford = WelfordRunner();
+ fVal = 0.0;
+ fAux = 0.0;
+ nCount = SC_DPAGG_EMPTY;
+ pChild.reset();
+}
+
+#if DUMP_PIVOT_TABLE
+void ScDPAggData::Dump(int nIndent) const
+{
+ std::string aIndent(nIndent*2, ' ');
+ std::cout << aIndent << "* ";
+ if (IsCalculated())
+ std::cout << GetResult();
+ else
+ std::cout << "not calculated";
+
+ std::cout << " [val=" << fVal << "; aux=" << fAux << "; count=" << nCount << "]" << std::endl;
+}
+#endif
+
+ScDPRowTotals::ScDPRowTotals() :
+ bIsInColRoot( false )
+{
+}
+
+ScDPRowTotals::~ScDPRowTotals()
+{
+}
+
+static ScDPAggData* lcl_GetChildTotal( ScDPAggData* pFirst, tools::Long nMeasure )
+{
+ OSL_ENSURE( nMeasure >= 0, "GetColTotal: no measure" );
+
+ ScDPAggData* pAgg = pFirst;
+ tools::Long nSkip = nMeasure;
+
+ // subtotal settings are ignored - column/row totals exist once per measure
+
+ for ( tools::Long nPos=0; nPos<nSkip; nPos++ )
+ pAgg = pAgg->GetChild(); // column total is constructed empty - children need to be created
+
+ if ( !pAgg->IsCalculated() )
+ {
+ // for first use, simulate an empty calculation
+ ScDPSubTotalState aEmptyState;
+ pAgg->Calculate( SUBTOTAL_FUNC_SUM, aEmptyState );
+ }
+
+ return pAgg;
+}
+
+ScDPAggData* ScDPRowTotals::GetRowTotal( tools::Long nMeasure )
+{
+ return lcl_GetChildTotal( &aRowTotal, nMeasure );
+}
+
+ScDPAggData* ScDPRowTotals::GetGrandTotal( tools::Long nMeasure )
+{
+ return lcl_GetChildTotal( &aGrandTotal, nMeasure );
+}
+
+static ScSubTotalFunc lcl_GetForceFunc( const ScDPLevel* pLevel, tools::Long nFuncNo )
+{
+ ScSubTotalFunc eRet = SUBTOTAL_FUNC_NONE;
+ if ( pLevel )
+ {
+ //TODO: direct access via ScDPLevel
+
+ uno::Sequence<sal_Int16> aSeq = pLevel->getSubTotals();
+ tools::Long nSequence = aSeq.getLength();
+ if ( nSequence && aSeq[0] != sheet::GeneralFunction2::AUTO )
+ {
+ // For manual subtotals, "automatic" is added as first function.
+ // ScDPResultMember::GetSubTotalCount adds to the count, here NONE has to be
+ // returned as the first function then.
+
+ --nFuncNo; // keep NONE for first (check below), move the other entries
+ }
+
+ if ( nFuncNo >= 0 && nFuncNo < nSequence )
+ {
+ ScGeneralFunction eUser = static_cast<ScGeneralFunction>(aSeq.getConstArray()[nFuncNo]);
+ if (eUser != ScGeneralFunction::AUTO)
+ eRet = ScDPUtil::toSubTotalFunc(eUser);
+ }
+ }
+ return eRet;
+}
+
+ScDPResultData::ScDPResultData( ScDPSource& rSrc ) :
+ mrSource(rSrc),
+ bLateInit( false ),
+ bDataAtCol( false ),
+ bDataAtRow( false )
+{
+}
+
+ScDPResultData::~ScDPResultData()
+{
+}
+
+void ScDPResultData::SetMeasureData(
+ std::vector<ScSubTotalFunc>& rFunctions, std::vector<sheet::DataPilotFieldReference>& rRefs,
+ std::vector<sheet::DataPilotFieldOrientation>& rRefOrient, std::vector<OUString>& rNames )
+{
+ // We need to have at least one measure data at all times.
+
+ maMeasureFuncs.swap(rFunctions);
+ if (maMeasureFuncs.empty())
+ maMeasureFuncs.push_back(SUBTOTAL_FUNC_NONE);
+
+ maMeasureRefs.swap(rRefs);
+ if (maMeasureRefs.empty())
+ maMeasureRefs.emplace_back(); // default ctor is ok.
+
+ maMeasureRefOrients.swap(rRefOrient);
+ if (maMeasureRefOrients.empty())
+ maMeasureRefOrients.push_back(sheet::DataPilotFieldOrientation_HIDDEN);
+
+ maMeasureNames.swap(rNames);
+ if (maMeasureNames.empty())
+ maMeasureNames.push_back(ScResId(STR_EMPTYDATA));
+}
+
+void ScDPResultData::SetDataLayoutOrientation( sheet::DataPilotFieldOrientation nOrient )
+{
+ bDataAtCol = ( nOrient == sheet::DataPilotFieldOrientation_COLUMN );
+ bDataAtRow = ( nOrient == sheet::DataPilotFieldOrientation_ROW );
+}
+
+void ScDPResultData::SetLateInit( bool bSet )
+{
+ bLateInit = bSet;
+}
+
+tools::Long ScDPResultData::GetColStartMeasure() const
+{
+ if (maMeasureFuncs.size() == 1)
+ return 0;
+
+ return bDataAtCol ? SC_DPMEASURE_ALL : SC_DPMEASURE_ANY;
+}
+
+tools::Long ScDPResultData::GetRowStartMeasure() const
+{
+ if (maMeasureFuncs.size() == 1)
+ return 0;
+
+ return bDataAtRow ? SC_DPMEASURE_ALL : SC_DPMEASURE_ANY;
+}
+
+ScSubTotalFunc ScDPResultData::GetMeasureFunction(tools::Long nMeasure) const
+{
+ OSL_ENSURE(o3tl::make_unsigned(nMeasure) < maMeasureFuncs.size(), "bumm");
+ return maMeasureFuncs[nMeasure];
+}
+
+const sheet::DataPilotFieldReference& ScDPResultData::GetMeasureRefVal(tools::Long nMeasure) const
+{
+ OSL_ENSURE(o3tl::make_unsigned(nMeasure) < maMeasureRefs.size(), "bumm");
+ return maMeasureRefs[nMeasure];
+}
+
+sheet::DataPilotFieldOrientation ScDPResultData::GetMeasureRefOrient(tools::Long nMeasure) const
+{
+ OSL_ENSURE(o3tl::make_unsigned(nMeasure) < maMeasureRefOrients.size(), "bumm");
+ return maMeasureRefOrients[nMeasure];
+}
+
+OUString ScDPResultData::GetMeasureString(tools::Long nMeasure, bool bForce, ScSubTotalFunc eForceFunc, bool& rbTotalResult) const
+{
+ // with bForce==true, return function instead of "result" for single measure
+ // with eForceFunc != SUBTOTAL_FUNC_NONE, always use eForceFunc
+ rbTotalResult = false;
+ if ( nMeasure < 0 || (maMeasureFuncs.size() == 1 && !bForce && eForceFunc == SUBTOTAL_FUNC_NONE) )
+ {
+ // for user-specified subtotal function with all measures,
+ // display only function name
+ assert(unsigned(eForceFunc) < SAL_N_ELEMENTS(aFuncStrIds));
+ if ( eForceFunc != SUBTOTAL_FUNC_NONE )
+ return ScResId(aFuncStrIds[eForceFunc]);
+
+ rbTotalResult = true;
+ return ScResId(STR_TABLE_ERGEBNIS);
+ }
+ else
+ {
+ OSL_ENSURE(o3tl::make_unsigned(nMeasure) < maMeasureFuncs.size(), "bumm");
+ const ScDPDimension* pDataDim = mrSource.GetDataDimension(nMeasure);
+ if (pDataDim)
+ {
+ const std::optional<OUString> & pLayoutName = pDataDim->GetLayoutName();
+ if (pLayoutName)
+ return *pLayoutName;
+ }
+
+ ScSubTotalFunc eFunc = ( eForceFunc == SUBTOTAL_FUNC_NONE ) ?
+ GetMeasureFunction(nMeasure) : eForceFunc;
+
+ return ScDPUtil::getDisplayedMeasureName(maMeasureNames[nMeasure], eFunc);
+ }
+}
+
+OUString ScDPResultData::GetMeasureDimensionName(tools::Long nMeasure) const
+{
+ if ( nMeasure < 0 )
+ {
+ OSL_FAIL("GetMeasureDimensionName: negative");
+ return "***";
+ }
+
+ return mrSource.GetDataDimName(nMeasure);
+}
+
+bool ScDPResultData::IsBaseForGroup( tools::Long nDim ) const
+{
+ return mrSource.GetData()->IsBaseForGroup(nDim);
+}
+
+tools::Long ScDPResultData::GetGroupBase( tools::Long nGroupDim ) const
+{
+ return mrSource.GetData()->GetGroupBase(nGroupDim);
+}
+
+bool ScDPResultData::IsNumOrDateGroup( tools::Long nDim ) const
+{
+ return mrSource.GetData()->IsNumOrDateGroup(nDim);
+}
+
+bool ScDPResultData::IsInGroup( SCROW nGroupDataId, tools::Long nGroupIndex,
+ const ScDPItemData& rBaseData, tools::Long nBaseIndex ) const
+{
+ const ScDPItemData* pGroupData = mrSource.GetItemDataById(nGroupIndex , nGroupDataId);
+ if ( pGroupData )
+ return mrSource.GetData()->IsInGroup(*pGroupData, nGroupIndex, rBaseData, nBaseIndex);
+ else
+ return false;
+}
+
+bool ScDPResultData::HasCommonElement( SCROW nFirstDataId, tools::Long nFirstIndex,
+ const ScDPItemData& rSecondData, tools::Long nSecondIndex ) const
+{
+ const ScDPItemData* pFirstData = mrSource.GetItemDataById(nFirstIndex , nFirstDataId);
+ if ( pFirstData )
+ return mrSource.GetData()->HasCommonElement(*pFirstData, nFirstIndex, rSecondData, nSecondIndex);
+ else
+ return false;
+}
+
+ResultMembers& ScDPResultData::GetDimResultMembers(tools::Long nDim, const ScDPDimension* pDim, ScDPLevel* pLevel) const
+{
+ if (nDim < static_cast<tools::Long>(maDimMembers.size()) && maDimMembers[nDim])
+ return *maDimMembers[nDim];
+
+ if (nDim >= static_cast<tools::Long>(maDimMembers.size()))
+ maDimMembers.resize(nDim+1);
+
+ std::unique_ptr<ResultMembers> pResultMembers(new ResultMembers());
+ // global order is used to initialize aMembers, so it doesn't have to be looked at later
+ const ScMemberSortOrder& rGlobalOrder = pLevel->GetGlobalOrder();
+
+ ScDPMembers* pMembers = pLevel->GetMembersObject();
+ tools::Long nMembCount = pMembers->getCount();
+ for (tools::Long i = 0; i < nMembCount; ++i)
+ {
+ tools::Long nSorted = rGlobalOrder.empty() ? i : rGlobalOrder[i];
+ ScDPMember* pMember = pMembers->getByIndex(nSorted);
+ if (!pResultMembers->FindMember(pMember->GetItemDataId()))
+ {
+ ScDPParentDimData aNew(i, pDim, pLevel, pMember);
+ pResultMembers->InsertMember(aNew);
+ }
+ }
+
+ maDimMembers[nDim] = std::move(pResultMembers);
+ return *maDimMembers[nDim];
+}
+
+ScDPResultMember::ScDPResultMember(
+ const ScDPResultData* pData, const ScDPParentDimData& rParentDimData ) :
+ pResultData( pData ),
+ aParentDimData( rParentDimData ),
+ bHasElements( false ),
+ bForceSubTotal( false ),
+ bHasHiddenDetails( false ),
+ bInitialized( false ),
+ bAutoHidden( false ),
+ nMemberStep( 1 )
+{
+ // pParentLevel/pMemberDesc is 0 for root members
+}
+
+ScDPResultMember::ScDPResultMember(
+ const ScDPResultData* pData, bool bForceSub ) :
+ pResultData( pData ),
+ bHasElements( false ),
+ bForceSubTotal( bForceSub ),
+ bHasHiddenDetails( false ),
+ bInitialized( false ),
+ bAutoHidden( false ),
+ nMemberStep( 1 )
+{
+}
+ScDPResultMember::~ScDPResultMember()
+{
+}
+
+OUString ScDPResultMember::GetName() const
+{
+ const ScDPMember* pMemberDesc = GetDPMember();
+ if (pMemberDesc)
+ return pMemberDesc->GetNameStr( false );
+ else
+ return ScResId(STR_PIVOT_TOTAL); // root member
+}
+
+OUString ScDPResultMember::GetDisplayName( bool bLocaleIndependent ) const
+{
+ const ScDPMember* pDPMember = GetDPMember();
+ if (!pDPMember)
+ return OUString();
+
+ ScDPItemData aItem(pDPMember->FillItemData());
+ if (aParentDimData.mpParentDim)
+ {
+ tools::Long nDim = aParentDimData.mpParentDim->GetDimension();
+ return pResultData->GetSource().GetData()->GetFormattedString(nDim, aItem, bLocaleIndependent);
+ }
+
+ return aItem.GetString();
+}
+
+ScDPItemData ScDPResultMember::FillItemData() const
+{
+ const ScDPMember* pMemberDesc = GetDPMember();
+ if (pMemberDesc)
+ return pMemberDesc->FillItemData();
+ return ScDPItemData(ScResId(STR_PIVOT_TOTAL)); // root member
+}
+
+bool ScDPResultMember::IsNamedItem( SCROW nIndex ) const
+{
+ //TODO: store ScDPMember pointer instead of ScDPMember ???
+ const ScDPMember* pMemberDesc = GetDPMember();
+ if (pMemberDesc)
+ return pMemberDesc->IsNamedItem(nIndex);
+ return false;
+}
+
+bool ScDPResultMember::IsValidEntry( const vector< SCROW >& aMembers ) const
+{
+ if ( !IsValid() )
+ return false;
+
+ const ScDPResultDimension* pChildDim = GetChildDimension();
+ if (pChildDim)
+ {
+ if (aMembers.size() < 2)
+ return false;
+
+ vector<SCROW>::const_iterator itr = aMembers.begin();
+ vector<SCROW> aChildMembers(++itr, aMembers.end());
+ return pChildDim->IsValidEntry(aChildMembers);
+ }
+ else
+ return true;
+}
+
+void ScDPResultMember::InitFrom( const vector<ScDPDimension*>& ppDim, const vector<ScDPLevel*>& ppLev,
+ size_t nPos, ScDPInitState& rInitState ,
+ bool bInitChild )
+{
+ // with LateInit, initialize only those members that have data
+ if ( pResultData->IsLateInit() )
+ return;
+
+ bInitialized = true;
+
+ if (nPos >= ppDim.size())
+ return;
+
+ // skip child dimension if details are not shown
+ if ( GetDPMember() && !GetDPMember()->getShowDetails() )
+ {
+ // Show DataLayout dimension
+ nMemberStep = 1;
+ while ( nPos < ppDim.size() )
+ {
+ if ( ppDim[nPos]->getIsDataLayoutDimension() )
+ {
+ if ( !pChildDimension )
+ pChildDimension.reset( new ScDPResultDimension( pResultData ) );
+ pChildDimension->InitFrom( ppDim, ppLev, nPos, rInitState , false );
+ return;
+ }
+ else
+ { //find next dim
+ nPos ++;
+ nMemberStep ++;
+ }
+ }
+ bHasHiddenDetails = true; // only if there is a next dimension
+ return;
+ }
+
+ if ( bInitChild )
+ {
+ pChildDimension.reset( new ScDPResultDimension( pResultData ) );
+ pChildDimension->InitFrom(ppDim, ppLev, nPos, rInitState);
+ }
+}
+
+void ScDPResultMember::LateInitFrom(
+ LateInitParams& rParams, const vector<SCROW>& pItemData, size_t nPos, ScDPInitState& rInitState)
+{
+ // without LateInit, everything has already been initialized
+ if ( !pResultData->IsLateInit() )
+ return;
+
+ bInitialized = true;
+
+ if ( rParams.IsEnd( nPos ) /*nPos >= ppDim.size()*/)
+ // No next dimension. Bail out.
+ return;
+
+ // skip child dimension if details are not shown
+ if ( GetDPMember() && !GetDPMember()->getShowDetails() )
+ {
+ // Show DataLayout dimension
+ nMemberStep = 1;
+ while ( !rParams.IsEnd( nPos ) )
+ {
+ if ( rParams.GetDim( nPos )->getIsDataLayoutDimension() )
+ {
+ if ( !pChildDimension )
+ pChildDimension.reset( new ScDPResultDimension( pResultData ) );
+
+ // #i111462# reset InitChild flag only for this child dimension's LateInitFrom call,
+ // not for following members of parent dimensions
+ bool bWasInitChild = rParams.GetInitChild();
+ rParams.SetInitChild( false );
+ pChildDimension->LateInitFrom( rParams, pItemData, nPos, rInitState );
+ rParams.SetInitChild( bWasInitChild );
+ return;
+ }
+ else
+ { //find next dim
+ nPos ++;
+ nMemberStep ++;
+ }
+ }
+ bHasHiddenDetails = true; // only if there is a next dimension
+ return;
+ }
+
+ // LateInitFrom is called several times...
+ if ( rParams.GetInitChild() )
+ {
+ if ( !pChildDimension )
+ pChildDimension.reset( new ScDPResultDimension( pResultData ) );
+ pChildDimension->LateInitFrom( rParams, pItemData, nPos, rInitState );
+ }
+}
+
+bool ScDPResultMember::IsSubTotalInTitle(tools::Long nMeasure) const
+{
+ bool bRet = false;
+ if ( pChildDimension && /*pParentLevel*/GetParentLevel() &&
+ /*pParentLevel*/GetParentLevel()->IsOutlineLayout() && /*pParentLevel*/GetParentLevel()->IsSubtotalsAtTop() )
+ {
+ tools::Long nUserSubStart;
+ tools::Long nSubTotals = GetSubTotalCount( &nUserSubStart );
+ nSubTotals -= nUserSubStart; // visible count
+ if ( nSubTotals )
+ {
+ if ( nMeasure == SC_DPMEASURE_ALL )
+ nSubTotals *= pResultData->GetMeasureCount(); // number of subtotals that will be inserted
+
+ // only a single subtotal row will be shown in the outline title row
+ if ( nSubTotals == 1 )
+ bRet = true;
+ }
+ }
+ return bRet;
+}
+
+tools::Long ScDPResultMember::GetSize(tools::Long nMeasure) const
+{
+ if ( !IsVisible() )
+ return 0;
+ const ScDPLevel* pParentLevel = GetParentLevel();
+ tools::Long nExtraSpace = 0;
+ if ( pParentLevel && pParentLevel->IsAddEmpty() )
+ ++nExtraSpace;
+
+ if ( pChildDimension )
+ {
+ // outline layout takes up an extra row for the title only if subtotals aren't shown in that row
+ if ( pParentLevel && pParentLevel->IsOutlineLayout() && !IsSubTotalInTitle( nMeasure ) )
+ ++nExtraSpace;
+
+ tools::Long nSize = pChildDimension->GetSize(nMeasure);
+ tools::Long nUserSubStart;
+ tools::Long nUserSubCount = GetSubTotalCount( &nUserSubStart );
+ nUserSubCount -= nUserSubStart; // for output size, use visible count
+ if ( nUserSubCount )
+ {
+ if ( nMeasure == SC_DPMEASURE_ALL )
+ nSize += pResultData->GetMeasureCount() * nUserSubCount;
+ else
+ nSize += nUserSubCount;
+ }
+ return nSize + nExtraSpace;
+ }
+ else
+ {
+ if ( nMeasure == SC_DPMEASURE_ALL )
+ return pResultData->GetMeasureCount() + nExtraSpace;
+ else
+ return 1 + nExtraSpace;
+ }
+}
+
+bool ScDPResultMember::IsVisible() const
+{
+ if (!bInitialized)
+ return false;
+
+ if (!IsValid())
+ return false;
+
+ if (bHasElements)
+ return true;
+
+ // not initialized -> shouldn't be there at all
+ // (allocated only to preserve ordering)
+ const ScDPLevel* pParentLevel = GetParentLevel();
+
+ return (pParentLevel && pParentLevel->getShowEmpty());
+}
+
+bool ScDPResultMember::IsValid() const
+{
+ // non-Valid members are left out of calculation
+
+ // was member set no invisible at the DataPilotSource?
+ const ScDPMember* pMemberDesc = GetDPMember();
+ if ( pMemberDesc && !pMemberDesc->isVisible() )
+ return false;
+
+ if ( bAutoHidden )
+ return false;
+
+ return true;
+}
+
+tools::Long ScDPResultMember::GetSubTotalCount( tools::Long* pUserSubStart ) const
+{
+ if ( pUserSubStart )
+ *pUserSubStart = 0; // default
+
+ const ScDPLevel* pParentLevel = GetParentLevel();
+
+ if ( bForceSubTotal ) // set if needed for root members
+ return 1; // grand total is always "automatic"
+ else if ( pParentLevel )
+ {
+ //TODO: direct access via ScDPLevel
+
+ uno::Sequence<sal_Int16> aSeq = pParentLevel->getSubTotals();
+ tools::Long nSequence = aSeq.getLength();
+ if ( nSequence && aSeq[0] != sheet::GeneralFunction2::AUTO )
+ {
+ // For manual subtotals, always add "automatic" as first function
+ // (used for calculation, but not for display, needed for sorting, see lcl_GetForceFunc)
+
+ ++nSequence;
+ if ( pUserSubStart )
+ *pUserSubStart = 1; // visible subtotals start at 1
+ }
+ return nSequence;
+ }
+ else
+ return 0;
+}
+
+void ScDPResultMember::ProcessData( const vector< SCROW >& aChildMembers, const ScDPResultDimension* pDataDim,
+ const vector< SCROW >& aDataMembers, const vector<ScDPValue>& aValues )
+{
+ SetHasElements();
+
+ if (pChildDimension)
+ pChildDimension->ProcessData( aChildMembers, pDataDim, aDataMembers, aValues );
+
+ if ( !pDataRoot )
+ {
+ pDataRoot.reset( new ScDPDataMember( pResultData, nullptr ) );
+ if ( pDataDim )
+ pDataRoot->InitFrom( pDataDim ); // recursive
+ }
+
+ ScDPSubTotalState aSubState; // initial state
+
+ tools::Long nUserSubCount = GetSubTotalCount();
+
+ // Calculate at least automatic if no subtotals are selected,
+ // show only own values if there's no child dimension (innermost).
+ if ( !nUserSubCount || !pChildDimension )
+ nUserSubCount = 1;
+
+ const ScDPLevel* pParentLevel = GetParentLevel();
+
+ for (tools::Long nUserPos=0; nUserPos<nUserSubCount; nUserPos++) // including hidden "automatic"
+ {
+ // #i68338# if nUserSubCount is 1 (automatic only), don't set nRowSubTotalFunc
+ if ( pChildDimension && nUserSubCount > 1 )
+ {
+ aSubState.nRowSubTotalFunc = nUserPos;
+ aSubState.eRowForce = lcl_GetForceFunc( pParentLevel, nUserPos );
+ }
+
+ pDataRoot->ProcessData( aDataMembers, aValues, aSubState );
+ }
+}
+
+/**
+ * Parse subtotal string and replace all occurrences of '?' with the caption
+ * string. Do ensure that escaped characters are not translated.
+ */
+static OUString lcl_parseSubtotalName(const OUString& rSubStr, std::u16string_view rCaption)
+{
+ OUStringBuffer aNewStr;
+ sal_Int32 n = rSubStr.getLength();
+ bool bEscaped = false;
+ for (sal_Int32 i = 0; i < n; ++i)
+ {
+ sal_Unicode c = rSubStr[i];
+ if (!bEscaped && c == '\\')
+ {
+ bEscaped = true;
+ continue;
+ }
+
+ if (!bEscaped && c == '?')
+ aNewStr.append(rCaption);
+ else
+ aNewStr.append(c);
+ bEscaped = false;
+ }
+ return aNewStr.makeStringAndClear();
+}
+
+void ScDPResultMember::FillMemberResults(
+ uno::Sequence<sheet::MemberResult>* pSequences, tools::Long& rPos, tools::Long nMeasure, bool bRoot,
+ const OUString* pMemberName, const OUString* pMemberCaption )
+{
+ // IsVisible() test is in ScDPResultDimension::FillMemberResults
+ // (not on data layout dimension)
+
+ if (!pSequences->hasElements())
+ // empty sequence. Bail out.
+ return;
+
+ tools::Long nSize = GetSize(nMeasure);
+ sheet::MemberResult* pArray = pSequences->getArray();
+ OSL_ENSURE( rPos+nSize <= pSequences->getLength(), "bumm" );
+
+ bool bIsNumeric = false;
+ double fValue = std::numeric_limits<double>::quiet_NaN();
+ OUString aName;
+ if ( pMemberName ) // if pMemberName != NULL, use instead of real member name
+ {
+ aName = *pMemberName;
+ }
+ else
+ {
+ ScDPItemData aItemData(FillItemData());
+ if (aParentDimData.mpParentDim)
+ {
+ tools::Long nDim = aParentDimData.mpParentDim->GetDimension();
+ aName = pResultData->GetSource().GetData()->GetFormattedString(nDim, aItemData, false);
+ }
+ else
+ {
+ tools::Long nDim = -1;
+ const ScDPMember* pMem = GetDPMember();
+ if (pMem)
+ nDim = pMem->GetDim();
+ aName = pResultData->GetSource().GetData()->GetFormattedString(nDim, aItemData, false);
+ }
+
+ ScDPItemData::Type eType = aItemData.GetType();
+ bIsNumeric = eType == ScDPItemData::Value || eType == ScDPItemData::GroupValue;
+ // IsValue() is not identical to bIsNumeric, i.e.
+ // ScDPItemData::GroupValue is excluded and not stored in the double,
+ // so even if the item is numeric the Value may be NaN.
+ if (aItemData.IsValue())
+ fValue = aItemData.GetValue();
+ }
+
+ const ScDPDimension* pParentDim = GetParentDim();
+ if ( bIsNumeric && pParentDim && pResultData->IsNumOrDateGroup( pParentDim->GetDimension() ) )
+ {
+ // Numeric group dimensions use numeric entries for proper sorting,
+ // but the group titles must be output as text.
+ bIsNumeric = false;
+ }
+
+ OUString aCaption = aName;
+ const ScDPMember* pMemberDesc = GetDPMember();
+ if (pMemberDesc)
+ {
+ const std::optional<OUString> & pLayoutName = pMemberDesc->GetLayoutName();
+ if (pLayoutName)
+ {
+ aCaption = *pLayoutName;
+ bIsNumeric = false; // layout name is always non-numeric.
+ }
+ }
+
+ if ( pMemberCaption ) // use pMemberCaption if != NULL
+ aCaption = *pMemberCaption;
+ if (aCaption.isEmpty())
+ aCaption = ScResId(STR_EMPTYDATA);
+
+ if (bIsNumeric)
+ pArray[rPos].Flags |= sheet::MemberResultFlags::NUMERIC;
+ else
+ pArray[rPos].Flags &= ~sheet::MemberResultFlags::NUMERIC;
+
+ const ScDPLevel* pParentLevel = GetParentLevel();
+ if ( nSize && !bRoot ) // root is overwritten by first dimension
+ {
+ pArray[rPos].Name = aName;
+ pArray[rPos].Caption = aCaption;
+ pArray[rPos].Flags |= sheet::MemberResultFlags::HASMEMBER;
+ pArray[rPos].Value = fValue;
+
+ // set "continue" flag (removed for subtotals later)
+ for (tools::Long i=1; i<nSize; i++)
+ {
+ pArray[rPos+i].Flags |= sheet::MemberResultFlags::CONTINUE;
+ // tdf#113002 - add numeric flag to recurring data fields
+ if (bIsNumeric)
+ pArray[rPos + i].Flags |= sheet::MemberResultFlags::NUMERIC;
+ }
+
+ if ( pParentLevel && pParentLevel->getRepeatItemLabels() )
+ {
+ tools::Long nSizeNonEmpty = nSize;
+ if ( pParentLevel->IsAddEmpty() )
+ --nSizeNonEmpty;
+ for (tools::Long i=1; i<nSizeNonEmpty; i++)
+ {
+ pArray[rPos+i].Name = aName;
+ pArray[rPos+i].Caption = aCaption;
+ pArray[rPos+i].Flags |= sheet::MemberResultFlags::HASMEMBER;
+ pArray[rPos+i].Value = fValue;
+ }
+ }
+ }
+
+ tools::Long nExtraSpace = 0;
+ if ( pParentLevel && pParentLevel->IsAddEmpty() )
+ ++nExtraSpace;
+
+ bool bTitleLine = false;
+ if ( pParentLevel && pParentLevel->IsOutlineLayout() )
+ bTitleLine = true;
+
+ // if the subtotals are shown at the top (title row) in outline layout,
+ // no extra row for the subtotals is needed
+ bool bSubTotalInTitle = IsSubTotalInTitle( nMeasure );
+
+ bool bHasChild = ( pChildDimension != nullptr );
+ if (bHasChild)
+ {
+ if ( bTitleLine ) // in tabular layout the title is on a separate row
+ ++rPos; // -> fill child dimension one row below
+
+ if (bRoot) // same sequence for root member
+ pChildDimension->FillMemberResults( pSequences, rPos, nMeasure );
+ else
+ pChildDimension->FillMemberResults( pSequences + nMemberStep/*1*/, rPos, nMeasure );
+
+ if ( bTitleLine ) // title row is included in GetSize, so the following
+ --rPos; // positions are calculated with the normal values
+ }
+
+ rPos += nSize;
+
+ tools::Long nUserSubStart;
+ tools::Long nUserSubCount = GetSubTotalCount(&nUserSubStart);
+ if ( !nUserSubCount || !pChildDimension || bSubTotalInTitle )
+ return;
+
+ tools::Long nMemberMeasure = nMeasure;
+ tools::Long nSubSize = pResultData->GetCountForMeasure(nMeasure);
+
+ rPos -= nSubSize * (nUserSubCount - nUserSubStart); // GetSize includes space for SubTotal
+ rPos -= nExtraSpace; // GetSize includes the empty line
+
+ for (tools::Long nUserPos=nUserSubStart; nUserPos<nUserSubCount; nUserPos++)
+ {
+ for ( tools::Long nSubCount=0; nSubCount<nSubSize; nSubCount++ )
+ {
+ if ( nMeasure == SC_DPMEASURE_ALL )
+ nMemberMeasure = nSubCount;
+
+ ScSubTotalFunc eForce = SUBTOTAL_FUNC_NONE;
+ if (bHasChild)
+ eForce = lcl_GetForceFunc( pParentLevel, nUserPos );
+
+ bool bTotalResult = false;
+ OUString aSubStr = aCaption + " " + pResultData->GetMeasureString(nMemberMeasure, false, eForce, bTotalResult);
+
+ if (bTotalResult)
+ {
+ if (pMemberDesc)
+ {
+ // single data field layout.
+ const std::optional<OUString> & pSubtotalName = pParentDim->GetSubtotalName();
+ if (pSubtotalName)
+ aSubStr = lcl_parseSubtotalName(*pSubtotalName, aCaption);
+ pArray[rPos].Flags &= ~sheet::MemberResultFlags::GRANDTOTAL;
+ }
+ else
+ {
+ // root member - subtotal (grand total?) for multi-data field layout.
+ const std::optional<OUString> & pGrandTotalName = pResultData->GetSource().GetGrandTotalName();
+ if (pGrandTotalName)
+ aSubStr = *pGrandTotalName;
+ pArray[rPos].Flags |= sheet::MemberResultFlags::GRANDTOTAL;
+ }
+ }
+
+ fValue = std::numeric_limits<double>::quiet_NaN(); /* TODO: any numeric value to obtain? */
+ pArray[rPos].Name = aName;
+ pArray[rPos].Caption = aSubStr;
+ pArray[rPos].Flags = ( pArray[rPos].Flags |
+ ( sheet::MemberResultFlags::HASMEMBER | sheet::MemberResultFlags::SUBTOTAL) ) &
+ ~sheet::MemberResultFlags::CONTINUE;
+ pArray[rPos].Value = fValue;
+
+ if ( nMeasure == SC_DPMEASURE_ALL )
+ {
+ // data layout dimension is (direct/indirect) child of this.
+ // data layout dimension must have name for all entries.
+
+ uno::Sequence<sheet::MemberResult>* pLayoutSeq = pSequences;
+ if (!bRoot)
+ ++pLayoutSeq;
+ ScDPResultDimension* pLayoutDim = pChildDimension.get();
+ while ( pLayoutDim && !pLayoutDim->IsDataLayout() )
+ {
+ pLayoutDim = pLayoutDim->GetFirstChildDimension();
+ ++pLayoutSeq;
+ }
+ if ( pLayoutDim )
+ {
+ sheet::MemberResult* pLayoutArray = pLayoutSeq->getArray();
+ pLayoutArray[rPos].Name = pResultData->GetMeasureDimensionName(nMemberMeasure);
+ }
+ }
+
+ rPos += 1;
+ }
+ }
+
+ rPos += nExtraSpace; // add again (subtracted above)
+}
+
+void ScDPResultMember::FillDataResults(
+ const ScDPResultMember* pRefMember,
+ ScDPResultFilterContext& rFilterCxt, uno::Sequence<uno::Sequence<sheet::DataResult> >& rSequence,
+ tools::Long nMeasure) const
+{
+ std::unique_ptr<FilterStack> pFilterStack;
+ const ScDPMember* pDPMember = GetDPMember();
+ if (pDPMember)
+ {
+ // Root result has no corresponding DP member. Only take the non-root results.
+ pFilterStack.reset(new FilterStack(rFilterCxt.maFilters));
+ pFilterStack->pushDimValue( GetDisplayName( false), GetDisplayName( true));
+ }
+
+ // IsVisible() test is in ScDPResultDimension::FillDataResults
+ // (not on data layout dimension)
+ const ScDPLevel* pParentLevel = GetParentLevel();
+ sal_Int32 nStartRow = rFilterCxt.mnRow;
+
+ tools::Long nExtraSpace = 0;
+ if ( pParentLevel && pParentLevel->IsAddEmpty() )
+ ++nExtraSpace;
+
+ bool bTitleLine = false;
+ if ( pParentLevel && pParentLevel->IsOutlineLayout() )
+ bTitleLine = true;
+
+ bool bSubTotalInTitle = IsSubTotalInTitle( nMeasure );
+
+ bool bHasChild = ( pChildDimension != nullptr );
+ if (bHasChild)
+ {
+ if ( bTitleLine ) // in tabular layout the title is on a separate row
+ ++rFilterCxt.mnRow; // -> fill child dimension one row below
+
+ sal_Int32 nOldRow = rFilterCxt.mnRow;
+ pChildDimension->FillDataResults(pRefMember, rFilterCxt, rSequence, nMeasure);
+ rFilterCxt.mnRow = nOldRow; // Revert to the original row before the call.
+
+ rFilterCxt.mnRow += GetSize( nMeasure );
+
+ if ( bTitleLine ) // title row is included in GetSize, so the following
+ --rFilterCxt.mnRow; // positions are calculated with the normal values
+ }
+
+ tools::Long nUserSubStart;
+ tools::Long nUserSubCount = GetSubTotalCount(&nUserSubStart);
+ if ( !nUserSubCount && bHasChild )
+ return;
+
+ // Calculate at least automatic if no subtotals are selected,
+ // show only own values if there's no child dimension (innermost).
+ if ( !nUserSubCount || !bHasChild )
+ {
+ nUserSubCount = 1;
+ nUserSubStart = 0;
+ }
+
+ tools::Long nMemberMeasure = nMeasure;
+ tools::Long nSubSize = pResultData->GetCountForMeasure(nMeasure);
+ if (bHasChild)
+ {
+ rFilterCxt.mnRow -= nSubSize * ( nUserSubCount - nUserSubStart ); // GetSize includes space for SubTotal
+ rFilterCxt.mnRow -= nExtraSpace; // GetSize includes the empty line
+ }
+
+ tools::Long nMoveSubTotal = 0;
+ if ( bSubTotalInTitle )
+ {
+ nMoveSubTotal = rFilterCxt.mnRow - nStartRow; // force to first (title) row
+ rFilterCxt.mnRow = nStartRow;
+ }
+
+ if ( pDataRoot )
+ {
+ ScDPSubTotalState aSubState; // initial state
+
+ for (tools::Long nUserPos=nUserSubStart; nUserPos<nUserSubCount; nUserPos++)
+ {
+ if ( bHasChild && nUserSubCount > 1 )
+ {
+ aSubState.nRowSubTotalFunc = nUserPos;
+ aSubState.eRowForce = lcl_GetForceFunc( /*pParentLevel*/GetParentLevel() , nUserPos );
+ }
+
+ for ( tools::Long nSubCount=0; nSubCount<nSubSize; nSubCount++ )
+ {
+ if ( nMeasure == SC_DPMEASURE_ALL )
+ nMemberMeasure = nSubCount;
+ else if ( pResultData->GetColStartMeasure() == SC_DPMEASURE_ALL )
+ nMemberMeasure = SC_DPMEASURE_ALL;
+
+ OSL_ENSURE( rFilterCxt.mnRow < rSequence.getLength(), "bumm" );
+ rFilterCxt.mnCol = 0;
+ if (pRefMember->IsVisible())
+ {
+ uno::Sequence<sheet::DataResult>& rSubSeq = rSequence.getArray()[rFilterCxt.mnRow];
+ pDataRoot->FillDataRow(pRefMember, rFilterCxt, rSubSeq, nMemberMeasure, bHasChild, aSubState);
+ }
+ rFilterCxt.mnRow += 1;
+ }
+ }
+ }
+ else
+ rFilterCxt.mnRow += nSubSize * ( nUserSubCount - nUserSubStart ); // empty rows occur when ShowEmpty is true
+
+ // add extra space again if subtracted from GetSize above,
+ // add to own size if no children
+ rFilterCxt.mnRow += nExtraSpace;
+ rFilterCxt.mnRow += nMoveSubTotal;
+}
+
+void ScDPResultMember::UpdateDataResults( const ScDPResultMember* pRefMember, tools::Long nMeasure ) const
+{
+ // IsVisible() test is in ScDPResultDimension::FillDataResults
+ // (not on data layout dimension)
+
+ bool bHasChild = ( pChildDimension != nullptr );
+
+ tools::Long nUserSubCount = GetSubTotalCount();
+
+ // process subtotals even if not shown
+
+ // Calculate at least automatic if no subtotals are selected,
+ // show only own values if there's no child dimension (innermost).
+ if (!nUserSubCount || !bHasChild)
+ nUserSubCount = 1;
+
+ tools::Long nMemberMeasure = nMeasure;
+ tools::Long nSubSize = pResultData->GetCountForMeasure(nMeasure);
+
+ if (pDataRoot)
+ {
+ ScDPSubTotalState aSubState; // initial state
+
+ for (tools::Long nUserPos = 0; nUserPos < nUserSubCount; ++nUserPos) // including hidden "automatic"
+ {
+ if (bHasChild && nUserSubCount > 1)
+ {
+ aSubState.nRowSubTotalFunc = nUserPos;
+ aSubState.eRowForce = lcl_GetForceFunc(GetParentLevel(), nUserPos);
+ }
+
+ for (tools::Long nSubCount = 0; nSubCount < nSubSize; ++nSubCount)
+ {
+ if (nMeasure == SC_DPMEASURE_ALL)
+ nMemberMeasure = nSubCount;
+ else if (pResultData->GetColStartMeasure() == SC_DPMEASURE_ALL)
+ nMemberMeasure = SC_DPMEASURE_ALL;
+
+ pDataRoot->UpdateDataRow(pRefMember, nMemberMeasure, bHasChild, aSubState);
+ }
+ }
+ }
+
+ if (bHasChild) // child dimension must be processed last, so the column total is known
+ {
+ pChildDimension->UpdateDataResults( pRefMember, nMeasure );
+ }
+}
+
+void ScDPResultMember::SortMembers( ScDPResultMember* pRefMember )
+{
+ bool bHasChild = ( pChildDimension != nullptr );
+ if (bHasChild)
+ pChildDimension->SortMembers( pRefMember ); // sorting is done at the dimension
+
+ if ( IsRoot() && pDataRoot )
+ {
+ // use the row root member to sort columns
+ // sub total count is always 1
+
+ pDataRoot->SortMembers( pRefMember );
+ }
+}
+
+void ScDPResultMember::DoAutoShow( ScDPResultMember* pRefMember )
+{
+ bool bHasChild = ( pChildDimension != nullptr );
+ if (bHasChild)
+ pChildDimension->DoAutoShow( pRefMember ); // sorting is done at the dimension
+
+ if ( IsRoot()&& pDataRoot )
+ {
+ // use the row root member to sort columns
+ // sub total count is always 1
+
+ pDataRoot->DoAutoShow( pRefMember );
+ }
+}
+
+void ScDPResultMember::ResetResults()
+{
+ if (pDataRoot)
+ pDataRoot->ResetResults();
+
+ if (pChildDimension)
+ pChildDimension->ResetResults();
+}
+
+void ScDPResultMember::UpdateRunningTotals( const ScDPResultMember* pRefMember, tools::Long nMeasure,
+ ScDPRunningTotalState& rRunning, ScDPRowTotals& rTotals ) const
+{
+ // IsVisible() test is in ScDPResultDimension::FillDataResults
+ // (not on data layout dimension)
+
+ rTotals.SetInColRoot( IsRoot() );
+
+ bool bHasChild = ( pChildDimension != nullptr );
+
+ tools::Long nUserSubCount = GetSubTotalCount();
+ //if ( nUserSubCount || !bHasChild )
+ {
+ // Calculate at least automatic if no subtotals are selected,
+ // show only own values if there's no child dimension (innermost).
+ if ( !nUserSubCount || !bHasChild )
+ nUserSubCount = 1;
+
+ tools::Long nMemberMeasure = nMeasure;
+ tools::Long nSubSize = pResultData->GetCountForMeasure(nMeasure);
+
+ if ( pDataRoot )
+ {
+ ScDPSubTotalState aSubState; // initial state
+
+ for (tools::Long nUserPos=0; nUserPos<nUserSubCount; nUserPos++) // including hidden "automatic"
+ {
+ if ( bHasChild && nUserSubCount > 1 )
+ {
+ aSubState.nRowSubTotalFunc = nUserPos;
+ aSubState.eRowForce = lcl_GetForceFunc(GetParentLevel(), nUserPos);
+ }
+
+ for ( tools::Long nSubCount=0; nSubCount<nSubSize; nSubCount++ )
+ {
+ if ( nMeasure == SC_DPMEASURE_ALL )
+ nMemberMeasure = nSubCount;
+ else if ( pResultData->GetColStartMeasure() == SC_DPMEASURE_ALL )
+ nMemberMeasure = SC_DPMEASURE_ALL;
+
+ if (pRefMember->IsVisible())
+ pDataRoot->UpdateRunningTotals(
+ pRefMember, nMemberMeasure, bHasChild, aSubState, rRunning, rTotals, *this);
+ }
+ }
+ }
+ }
+
+ if (bHasChild) // child dimension must be processed last, so the column total is known
+ {
+ pChildDimension->UpdateRunningTotals( pRefMember, nMeasure, rRunning, rTotals );
+ }
+}
+
+#if DUMP_PIVOT_TABLE
+void ScDPResultMember::DumpState( const ScDPResultMember* pRefMember, ScDocument* pDoc, ScAddress& rPos ) const
+{
+ dumpRow("ScDPResultMember", GetName(), nullptr, pDoc, rPos);
+ SCROW nStartRow = rPos.Row();
+
+ if (pDataRoot)
+ pDataRoot->DumpState( pRefMember, pDoc, rPos );
+
+ if (pChildDimension)
+ pChildDimension->DumpState( pRefMember, pDoc, rPos );
+
+ indent(pDoc, nStartRow, rPos);
+}
+
+void ScDPResultMember::Dump(int nIndent) const
+{
+ std::string aIndent(nIndent*2, ' ');
+ std::cout << aIndent << "-- result member '" << GetName() << "'" << std::endl;
+
+ std::cout << aIndent << " column totals" << std::endl;
+ for (const ScDPAggData* p = &aColTotal; p; p = p->GetExistingChild())
+ p->Dump(nIndent+1);
+
+ if (pChildDimension)
+ pChildDimension->Dump(nIndent+1);
+
+ if (pDataRoot)
+ {
+ std::cout << aIndent << " data root" << std::endl;
+ pDataRoot->Dump(nIndent+1);
+ }
+}
+#endif
+
+ScDPAggData* ScDPResultMember::GetColTotal( tools::Long nMeasure ) const
+{
+ return lcl_GetChildTotal( const_cast<ScDPAggData*>(&aColTotal), nMeasure );
+}
+
+void ScDPResultMember::FillVisibilityData(ScDPResultVisibilityData& rData) const
+{
+ if (pChildDimension)
+ pChildDimension->FillVisibilityData(rData);
+}
+
+ScDPDataMember::ScDPDataMember( const ScDPResultData* pData, const ScDPResultMember* pRes ) :
+ pResultData( pData ),
+ pResultMember( pRes )
+{
+ // pResultMember is 0 for root members
+}
+
+ScDPDataMember::~ScDPDataMember()
+{
+}
+
+OUString ScDPDataMember::GetName() const
+{
+ if (pResultMember)
+ return pResultMember->GetName();
+ else
+ return OUString();
+}
+
+bool ScDPDataMember::IsVisible() const
+{
+ if (pResultMember)
+ return pResultMember->IsVisible();
+ else
+ return false;
+}
+
+bool ScDPDataMember::IsNamedItem( SCROW nRow ) const
+{
+ if (pResultMember)
+ return pResultMember->IsNamedItem(nRow);
+ else
+ return false;
+}
+
+bool ScDPDataMember::HasHiddenDetails() const
+{
+ if (pResultMember)
+ return pResultMember->HasHiddenDetails();
+ else
+ return false;
+}
+
+void ScDPDataMember::InitFrom( const ScDPResultDimension* pDim )
+{
+ if ( !pChildDimension )
+ pChildDimension.reset( new ScDPDataDimension(pResultData) );
+ pChildDimension->InitFrom(pDim);
+}
+
+const tools::Long SC_SUBTOTALPOS_AUTO = -1; // default
+const tools::Long SC_SUBTOTALPOS_SKIP = -2; // don't use
+
+static tools::Long lcl_GetSubTotalPos( const ScDPSubTotalState& rSubState )
+{
+ if ( rSubState.nColSubTotalFunc >= 0 && rSubState.nRowSubTotalFunc >= 0 &&
+ rSubState.nColSubTotalFunc != rSubState.nRowSubTotalFunc )
+ {
+ // #i68338# don't return the same index for different combinations (leading to repeated updates),
+ // return a "don't use" value instead
+
+ return SC_SUBTOTALPOS_SKIP;
+ }
+
+ tools::Long nRet = SC_SUBTOTALPOS_AUTO;
+ if ( rSubState.nColSubTotalFunc >= 0 ) nRet = rSubState.nColSubTotalFunc;
+ if ( rSubState.nRowSubTotalFunc >= 0 ) nRet = rSubState.nRowSubTotalFunc;
+ return nRet;
+}
+
+void ScDPDataMember::UpdateValues( const vector<ScDPValue>& aValues, const ScDPSubTotalState& rSubState )
+{
+ //TODO: find out how many and which subtotals are used
+
+ ScDPAggData* pAgg = &aAggregate;
+
+ tools::Long nSubPos = lcl_GetSubTotalPos(rSubState);
+ if (nSubPos == SC_SUBTOTALPOS_SKIP)
+ return;
+ if (nSubPos > 0)
+ {
+ tools::Long nSkip = nSubPos * pResultData->GetMeasureCount();
+ for (tools::Long i=0; i<nSkip; i++)
+ pAgg = pAgg->GetChild(); // created if not there
+ }
+
+ size_t nCount = aValues.size();
+ for (size_t nPos = 0; nPos < nCount; ++nPos)
+ {
+ pAgg->Update(aValues[nPos], pResultData->GetMeasureFunction(nPos), rSubState);
+ pAgg = pAgg->GetChild();
+ }
+}
+
+void ScDPDataMember::ProcessData( const vector< SCROW >& aChildMembers, const vector<ScDPValue>& aValues,
+ const ScDPSubTotalState& rSubState )
+{
+ if ( pResultData->IsLateInit() && !pChildDimension && pResultMember && pResultMember->GetChildDimension() )
+ {
+ // if this DataMember doesn't have a child dimension because the ResultMember's
+ // child dimension wasn't there yet during this DataMembers's creation,
+ // create the child dimension now
+ InitFrom( pResultMember->GetChildDimension() );
+ }
+
+ tools::Long nUserSubCount = pResultMember ? pResultMember->GetSubTotalCount() : 0;
+
+ // Calculate at least automatic if no subtotals are selected,
+ // show only own values if there's no child dimension (innermost).
+ if ( !nUserSubCount || !pChildDimension )
+ nUserSubCount = 1;
+
+ ScDPSubTotalState aLocalSubState = rSubState; // keep row state, modify column
+ for (tools::Long nUserPos=0; nUserPos<nUserSubCount; nUserPos++) // including hidden "automatic"
+ {
+ if ( pChildDimension && nUserSubCount > 1 )
+ {
+ const ScDPLevel* pForceLevel = pResultMember ? pResultMember->GetParentLevel() : nullptr;
+ aLocalSubState.nColSubTotalFunc = nUserPos;
+ aLocalSubState.eColForce = lcl_GetForceFunc( pForceLevel, nUserPos );
+ }
+
+ UpdateValues( aValues, aLocalSubState );
+ }
+
+ if (pChildDimension)
+ pChildDimension->ProcessData( aChildMembers, aValues, rSubState ); // with unmodified subtotal state
+}
+
+bool ScDPDataMember::HasData( tools::Long nMeasure, const ScDPSubTotalState& rSubState ) const
+{
+ if ( rSubState.eColForce != SUBTOTAL_FUNC_NONE && rSubState.eRowForce != SUBTOTAL_FUNC_NONE &&
+ rSubState.eColForce != rSubState.eRowForce )
+ return false;
+
+ // HasData can be different between measures!
+
+ const ScDPAggData* pAgg = GetConstAggData( nMeasure, rSubState );
+ if (!pAgg)
+ return false; //TODO: error?
+
+ return pAgg->HasData();
+}
+
+bool ScDPDataMember::HasError( tools::Long nMeasure, const ScDPSubTotalState& rSubState ) const
+{
+ const ScDPAggData* pAgg = GetConstAggData( nMeasure, rSubState );
+ if (!pAgg)
+ return true;
+
+ return pAgg->HasError();
+}
+
+double ScDPDataMember::GetAggregate( tools::Long nMeasure, const ScDPSubTotalState& rSubState ) const
+{
+ const ScDPAggData* pAgg = GetConstAggData( nMeasure, rSubState );
+ if (!pAgg)
+ return DBL_MAX; //TODO: error?
+
+ return pAgg->GetResult();
+}
+
+ScDPAggData* ScDPDataMember::GetAggData( tools::Long nMeasure, const ScDPSubTotalState& rSubState )
+{
+ OSL_ENSURE( nMeasure >= 0, "GetAggData: no measure" );
+
+ ScDPAggData* pAgg = &aAggregate;
+ tools::Long nSkip = nMeasure;
+ tools::Long nSubPos = lcl_GetSubTotalPos(rSubState);
+ if (nSubPos == SC_SUBTOTALPOS_SKIP)
+ return nullptr;
+ if (nSubPos > 0)
+ nSkip += nSubPos * pResultData->GetMeasureCount();
+
+ for ( tools::Long nPos=0; nPos<nSkip; nPos++ )
+ pAgg = pAgg->GetChild(); //TODO: need to create children here?
+
+ return pAgg;
+}
+
+const ScDPAggData* ScDPDataMember::GetConstAggData( tools::Long nMeasure, const ScDPSubTotalState& rSubState ) const
+{
+ OSL_ENSURE( nMeasure >= 0, "GetConstAggData: no measure" );
+
+ const ScDPAggData* pAgg = &aAggregate;
+ tools::Long nSkip = nMeasure;
+ tools::Long nSubPos = lcl_GetSubTotalPos(rSubState);
+ if (nSubPos == SC_SUBTOTALPOS_SKIP)
+ return nullptr;
+ if (nSubPos > 0)
+ nSkip += nSubPos * pResultData->GetMeasureCount();
+
+ for ( tools::Long nPos=0; nPos<nSkip; nPos++ )
+ {
+ pAgg = pAgg->GetExistingChild();
+ if (!pAgg)
+ return nullptr;
+ }
+
+ return pAgg;
+}
+
+void ScDPDataMember::FillDataRow(
+ const ScDPResultMember* pRefMember, ScDPResultFilterContext& rFilterCxt,
+ uno::Sequence<sheet::DataResult>& rSequence, tools::Long nMeasure, bool bIsSubTotalRow,
+ const ScDPSubTotalState& rSubState) const
+{
+ std::unique_ptr<FilterStack> pFilterStack;
+ if (pResultMember)
+ {
+ // Topmost data member (pResultMember=NULL) doesn't need to be handled
+ // since its immediate parent result member is linked to the same
+ // dimension member.
+ pFilterStack.reset(new FilterStack(rFilterCxt.maFilters));
+ pFilterStack->pushDimValue( pResultMember->GetDisplayName( false), pResultMember->GetDisplayName( true));
+ }
+
+ OSL_ENSURE( pRefMember == pResultMember || !pResultMember, "bla" );
+
+ tools::Long nStartCol = rFilterCxt.mnCol;
+
+ const ScDPDataDimension* pDataChild = GetChildDimension();
+ const ScDPResultDimension* pRefChild = pRefMember->GetChildDimension();
+
+ const ScDPLevel* pRefParentLevel = pRefMember->GetParentLevel();
+
+ tools::Long nExtraSpace = 0;
+ if ( pRefParentLevel && pRefParentLevel->IsAddEmpty() )
+ ++nExtraSpace;
+
+ bool bTitleLine = false;
+ if ( pRefParentLevel && pRefParentLevel->IsOutlineLayout() )
+ bTitleLine = true;
+
+ bool bSubTotalInTitle = pRefMember->IsSubTotalInTitle( nMeasure );
+
+ // leave space for children even if the DataMember hasn't been initialized
+ // (pDataChild is null then, this happens when no values for it are in this row)
+ bool bHasChild = ( pRefChild != nullptr );
+
+ if ( bHasChild )
+ {
+ if ( bTitleLine ) // in tabular layout the title is on a separate column
+ ++rFilterCxt.mnCol; // -> fill child dimension one column below
+
+ if ( pDataChild )
+ {
+ tools::Long nOldCol = rFilterCxt.mnCol;
+ pDataChild->FillDataRow(pRefChild, rFilterCxt, rSequence, nMeasure, bIsSubTotalRow, rSubState);
+ rFilterCxt.mnCol = nOldCol; // Revert to the old column value before the call.
+ }
+ rFilterCxt.mnCol += static_cast<sal_uInt16>(pRefMember->GetSize( nMeasure ));
+
+ if ( bTitleLine ) // title column is included in GetSize, so the following
+ --rFilterCxt.mnCol; // positions are calculated with the normal values
+ }
+
+ tools::Long nUserSubStart;
+ tools::Long nUserSubCount = pRefMember->GetSubTotalCount(&nUserSubStart);
+ if ( !nUserSubCount && bHasChild )
+ return;
+
+ // Calculate at least automatic if no subtotals are selected,
+ // show only own values if there's no child dimension (innermost).
+ if ( !nUserSubCount || !bHasChild )
+ {
+ nUserSubCount = 1;
+ nUserSubStart = 0;
+ }
+
+ ScDPSubTotalState aLocalSubState(rSubState); // keep row state, modify column
+
+ tools::Long nMemberMeasure = nMeasure;
+ tools::Long nSubSize = pResultData->GetCountForMeasure(nMeasure);
+ if (bHasChild)
+ {
+ rFilterCxt.mnCol -= nSubSize * ( nUserSubCount - nUserSubStart ); // GetSize includes space for SubTotal
+ rFilterCxt.mnCol -= nExtraSpace; // GetSize includes the empty line
+ }
+
+ tools::Long nMoveSubTotal = 0;
+ if ( bSubTotalInTitle )
+ {
+ nMoveSubTotal = rFilterCxt.mnCol - nStartCol; // force to first (title) column
+ rFilterCxt.mnCol = nStartCol;
+ }
+
+ for (tools::Long nUserPos=nUserSubStart; nUserPos<nUserSubCount; nUserPos++)
+ {
+ if ( pChildDimension && nUserSubCount > 1 )
+ {
+ const ScDPLevel* pForceLevel = pResultMember ? pResultMember->GetParentLevel() : nullptr;
+ aLocalSubState.nColSubTotalFunc = nUserPos;
+ aLocalSubState.eColForce = lcl_GetForceFunc( pForceLevel, nUserPos );
+ }
+
+ for ( tools::Long nSubCount=0; nSubCount<nSubSize; nSubCount++ )
+ {
+ if ( nMeasure == SC_DPMEASURE_ALL )
+ nMemberMeasure = nSubCount;
+
+ OSL_ENSURE( rFilterCxt.mnCol < rSequence.getLength(), "bumm" );
+ sheet::DataResult& rRes = rSequence.getArray()[rFilterCxt.mnCol];
+
+ if ( HasData( nMemberMeasure, aLocalSubState ) )
+ {
+ if ( HasError( nMemberMeasure, aLocalSubState ) )
+ {
+ rRes.Value = 0;
+ rRes.Flags |= sheet::DataResultFlags::ERROR;
+ }
+ else
+ {
+ rRes.Value = GetAggregate( nMemberMeasure, aLocalSubState );
+ rRes.Flags |= sheet::DataResultFlags::HASDATA;
+ }
+ }
+
+ if ( bHasChild || bIsSubTotalRow )
+ rRes.Flags |= sheet::DataResultFlags::SUBTOTAL;
+
+ rFilterCxt.maFilterSet.add(rFilterCxt.maFilters, rRes.Value);
+ rFilterCxt.mnCol += 1;
+ }
+ }
+
+ // add extra space again if subtracted from GetSize above,
+ // add to own size if no children
+ rFilterCxt.mnCol += nExtraSpace;
+ rFilterCxt.mnCol += nMoveSubTotal;
+}
+
+void ScDPDataMember::UpdateDataRow(
+ const ScDPResultMember* pRefMember, tools::Long nMeasure, bool bIsSubTotalRow,
+ const ScDPSubTotalState& rSubState )
+{
+ OSL_ENSURE( pRefMember == pResultMember || !pResultMember, "bla" );
+
+ // Calculate must be called even if not visible (for use as reference value)
+ const ScDPDataDimension* pDataChild = GetChildDimension();
+ const ScDPResultDimension* pRefChild = pRefMember->GetChildDimension();
+
+ // leave space for children even if the DataMember hasn't been initialized
+ // (pDataChild is null then, this happens when no values for it are in this row)
+ bool bHasChild = ( pRefChild != nullptr );
+
+ // process subtotals even if not shown
+ tools::Long nUserSubCount = pRefMember->GetSubTotalCount();
+
+ // Calculate at least automatic if no subtotals are selected,
+ // show only own values if there's no child dimension (innermost).
+ if ( !nUserSubCount || !bHasChild )
+ nUserSubCount = 1;
+
+ ScDPSubTotalState aLocalSubState(rSubState); // keep row state, modify column
+
+ tools::Long nMemberMeasure = nMeasure;
+ tools::Long nSubSize = pResultData->GetCountForMeasure(nMeasure);
+
+ for (tools::Long nUserPos=0; nUserPos<nUserSubCount; nUserPos++) // including hidden "automatic"
+ {
+ if ( pChildDimension && nUserSubCount > 1 )
+ {
+ const ScDPLevel* pForceLevel = pResultMember ? pResultMember->GetParentLevel() : nullptr;
+ aLocalSubState.nColSubTotalFunc = nUserPos;
+ aLocalSubState.eColForce = lcl_GetForceFunc( pForceLevel, nUserPos );
+ }
+
+ for ( tools::Long nSubCount=0; nSubCount<nSubSize; nSubCount++ )
+ {
+ if ( nMeasure == SC_DPMEASURE_ALL )
+ nMemberMeasure = nSubCount;
+
+ // update data...
+ ScDPAggData* pAggData = GetAggData( nMemberMeasure, aLocalSubState );
+ if (pAggData)
+ {
+ //TODO: aLocalSubState?
+ ScSubTotalFunc eFunc = pResultData->GetMeasureFunction( nMemberMeasure );
+ sheet::DataPilotFieldReference aReferenceValue = pResultData->GetMeasureRefVal( nMemberMeasure );
+ sal_Int32 eRefType = aReferenceValue.ReferenceType;
+
+ // calculate the result first - for all members, regardless of reference value
+ pAggData->Calculate( eFunc, aLocalSubState );
+
+ if ( eRefType == sheet::DataPilotFieldReferenceType::ITEM_DIFFERENCE ||
+ eRefType == sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE ||
+ eRefType == sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE_DIFFERENCE )
+ {
+ // copy the result into auxiliary value, so differences can be
+ // calculated in any order
+ pAggData->SetAuxiliary( pAggData->GetResult() );
+ }
+ // column/row percentage/index is now in UpdateRunningTotals, so it doesn't disturb sorting
+ }
+ }
+ }
+
+ if ( bHasChild ) // child dimension must be processed last, so the row total is known
+ {
+ if ( pDataChild )
+ pDataChild->UpdateDataRow( pRefChild, nMeasure, bIsSubTotalRow, rSubState );
+ }
+}
+
+void ScDPDataMember::SortMembers( ScDPResultMember* pRefMember )
+{
+ OSL_ENSURE( pRefMember == pResultMember || !pResultMember, "bla" );
+
+ if ( pRefMember->IsVisible() ) //TODO: here or in ScDPDataDimension ???
+ {
+ ScDPDataDimension* pDataChild = GetChildDimension();
+ ScDPResultDimension* pRefChild = pRefMember->GetChildDimension();
+ if ( pRefChild && pDataChild )
+ pDataChild->SortMembers( pRefChild ); // sorting is done at the dimension
+ }
+}
+
+void ScDPDataMember::DoAutoShow( ScDPResultMember* pRefMember )
+{
+ OSL_ENSURE( pRefMember == pResultMember || !pResultMember, "bla" );
+
+ if ( pRefMember->IsVisible() ) //TODO: here or in ScDPDataDimension ???
+ {
+ ScDPDataDimension* pDataChild = GetChildDimension();
+ ScDPResultDimension* pRefChild = pRefMember->GetChildDimension();
+ if ( pRefChild && pDataChild )
+ pDataChild->DoAutoShow( pRefChild ); // sorting is done at the dimension
+ }
+}
+
+void ScDPDataMember::ResetResults()
+{
+ aAggregate.Reset();
+
+ ScDPDataDimension* pDataChild = GetChildDimension();
+ if ( pDataChild )
+ pDataChild->ResetResults();
+}
+
+void ScDPDataMember::UpdateRunningTotals(
+ const ScDPResultMember* pRefMember, tools::Long nMeasure, bool bIsSubTotalRow,
+ const ScDPSubTotalState& rSubState, ScDPRunningTotalState& rRunning,
+ ScDPRowTotals& rTotals, const ScDPResultMember& rRowParent )
+{
+ OSL_ENSURE( pRefMember == pResultMember || !pResultMember, "bla" );
+
+ const ScDPDataDimension* pDataChild = GetChildDimension();
+ const ScDPResultDimension* pRefChild = pRefMember->GetChildDimension();
+
+ bool bIsRoot = ( pResultMember == nullptr || pResultMember->GetParentLevel() == nullptr );
+
+ // leave space for children even if the DataMember hasn't been initialized
+ // (pDataChild is null then, this happens when no values for it are in this row)
+ bool bHasChild = ( pRefChild != nullptr );
+
+ tools::Long nUserSubCount = pRefMember->GetSubTotalCount();
+ {
+ // Calculate at least automatic if no subtotals are selected,
+ // show only own values if there's no child dimension (innermost).
+ if ( !nUserSubCount || !bHasChild )
+ nUserSubCount = 1;
+
+ ScDPSubTotalState aLocalSubState(rSubState); // keep row state, modify column
+
+ tools::Long nMemberMeasure = nMeasure;
+ tools::Long nSubSize = pResultData->GetCountForMeasure(nMeasure);
+
+ for (tools::Long nUserPos=0; nUserPos<nUserSubCount; nUserPos++) // including hidden "automatic"
+ {
+ if ( pChildDimension && nUserSubCount > 1 )
+ {
+ const ScDPLevel* pForceLevel = pResultMember ? pResultMember->GetParentLevel() : nullptr;
+ aLocalSubState.nColSubTotalFunc = nUserPos;
+ aLocalSubState.eColForce = lcl_GetForceFunc( pForceLevel, nUserPos );
+ }
+
+ for ( tools::Long nSubCount=0; nSubCount<nSubSize; nSubCount++ )
+ {
+ if ( nMeasure == SC_DPMEASURE_ALL )
+ nMemberMeasure = nSubCount;
+
+ // update data...
+ ScDPAggData* pAggData = GetAggData( nMemberMeasure, aLocalSubState );
+ if (pAggData)
+ {
+ //TODO: aLocalSubState?
+ sheet::DataPilotFieldReference aReferenceValue = pResultData->GetMeasureRefVal( nMemberMeasure );
+ sal_Int32 eRefType = aReferenceValue.ReferenceType;
+
+ if ( eRefType == sheet::DataPilotFieldReferenceType::RUNNING_TOTAL ||
+ eRefType == sheet::DataPilotFieldReferenceType::ITEM_DIFFERENCE ||
+ eRefType == sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE ||
+ eRefType == sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE_DIFFERENCE )
+ {
+ bool bRunningTotal = ( eRefType == sheet::DataPilotFieldReferenceType::RUNNING_TOTAL );
+ bool bRelative =
+ ( aReferenceValue.ReferenceItemType != sheet::DataPilotFieldReferenceItemType::NAMED && !bRunningTotal );
+ tools::Long nRelativeDir = bRelative ?
+ ( ( aReferenceValue.ReferenceItemType == sheet::DataPilotFieldReferenceItemType::PREVIOUS ) ? -1 : 1 ) : 0;
+
+ const ScDPRunningTotalState::IndexArray& rColVisible = rRunning.GetColVisible();
+ const ScDPRunningTotalState::IndexArray& rColSorted = rRunning.GetColSorted();
+ const ScDPRunningTotalState::IndexArray& rRowVisible = rRunning.GetRowVisible();
+ const ScDPRunningTotalState::IndexArray& rRowSorted = rRunning.GetRowSorted();
+
+ OUString aRefFieldName = aReferenceValue.ReferenceField;
+
+ //TODO: aLocalSubState?
+ sheet::DataPilotFieldOrientation nRefOrient = pResultData->GetMeasureRefOrient( nMemberMeasure );
+ bool bRefDimInCol = ( nRefOrient == sheet::DataPilotFieldOrientation_COLUMN );
+ bool bRefDimInRow = ( nRefOrient == sheet::DataPilotFieldOrientation_ROW );
+
+ ScDPResultDimension* pSelectDim = nullptr;
+ sal_Int32 nRowPos = 0;
+ sal_Int32 nColPos = 0;
+
+ // find the reference field in column or row dimensions
+
+ if ( bRefDimInRow ) // look in row dimensions
+ {
+ pSelectDim = rRunning.GetRowResRoot()->GetChildDimension();
+ while ( pSelectDim && pSelectDim->GetName() != aRefFieldName )
+ {
+ tools::Long nIndex = rRowSorted[nRowPos];
+ if ( nIndex >= 0 && nIndex < pSelectDim->GetMemberCount() )
+ pSelectDim = pSelectDim->GetMember(nIndex)->GetChildDimension();
+ else
+ pSelectDim = nullptr;
+ ++nRowPos;
+ }
+ // child dimension of innermost member?
+ if ( pSelectDim && rRowSorted[nRowPos] < 0 )
+ pSelectDim = nullptr;
+ }
+
+ if ( bRefDimInCol ) // look in column dimensions
+ {
+ pSelectDim = rRunning.GetColResRoot()->GetChildDimension();
+ while ( pSelectDim && pSelectDim->GetName() != aRefFieldName )
+ {
+ tools::Long nIndex = rColSorted[nColPos];
+ if ( nIndex >= 0 && nIndex < pSelectDim->GetMemberCount() )
+ pSelectDim = pSelectDim->GetMember(nIndex)->GetChildDimension();
+ else
+ pSelectDim = nullptr;
+ ++nColPos;
+ }
+ // child dimension of innermost member?
+ if ( pSelectDim && rColSorted[nColPos] < 0 )
+ pSelectDim = nullptr;
+ }
+
+ bool bNoDetailsInRef = false;
+ if ( pSelectDim && bRunningTotal )
+ {
+ // Running totals:
+ // If details are hidden for this member in the reference dimension,
+ // don't show or sum up the value. Otherwise, for following members,
+ // the running totals of details and subtotals wouldn't match.
+
+ tools::Long nMyIndex = bRefDimInCol ? rColSorted[nColPos] : rRowSorted[nRowPos];
+ if ( nMyIndex >= 0 && nMyIndex < pSelectDim->GetMemberCount() )
+ {
+ const ScDPResultMember* pMyRefMember = pSelectDim->GetMember(nMyIndex);
+ if ( pMyRefMember && pMyRefMember->HasHiddenDetails() )
+ {
+ pSelectDim = nullptr; // don't calculate
+ bNoDetailsInRef = true; // show error, not empty
+ }
+ }
+ }
+
+ if ( bRelative )
+ {
+ // Difference/Percentage from previous/next:
+ // If details are hidden for this member in the innermost column/row
+ // dimension (the orientation of the reference dimension), show an
+ // error value.
+ // - If the no-details dimension is the reference dimension, its
+ // members will be skipped when finding the previous/next member,
+ // so there must be no results for its members.
+ // - If the no-details dimension is outside of the reference dimension,
+ // no calculation in the reference dimension is possible.
+ // - Otherwise, the error isn't strictly necessary, but shown for
+ // consistency.
+
+ bool bInnerNoDetails = bRefDimInCol ? HasHiddenDetails() :
+ ( !bRefDimInRow || rRowParent.HasHiddenDetails() );
+ if ( bInnerNoDetails )
+ {
+ pSelectDim = nullptr;
+ bNoDetailsInRef = true; // show error, not empty
+ }
+ }
+
+ if ( !bRefDimInCol && !bRefDimInRow ) // invalid dimension specified
+ bNoDetailsInRef = true; // pSelectDim is then already NULL
+
+ // get the member for the reference item and do the calculation
+
+ if ( bRunningTotal )
+ {
+ // running total in (dimension) -> find first existing member
+
+ if ( pSelectDim )
+ {
+ ScDPDataMember* pSelectMember;
+ if ( bRefDimInCol )
+ pSelectMember = ScDPResultDimension::GetColReferenceMember( nullptr, nullptr,
+ nColPos, rRunning );
+ else
+ {
+ const sal_Int32* pRowSorted = rRowSorted.data();
+ const sal_Int32* pColSorted = rColSorted.data();
+ pRowSorted += nRowPos + 1; // including the reference dimension
+ pSelectMember = pSelectDim->GetRowReferenceMember(
+ nullptr, nullptr, pRowSorted, pColSorted);
+ }
+
+ if ( pSelectMember )
+ {
+ // The running total is kept as the auxiliary value in
+ // the first available member for the reference dimension.
+ // Members are visited in final order, so each one's result
+ // can be used and then modified.
+
+ ScDPAggData* pSelectData = pSelectMember->
+ GetAggData( nMemberMeasure, aLocalSubState );
+ if ( pSelectData )
+ {
+ double fTotal = pSelectData->GetAuxiliary();
+ fTotal += pAggData->GetResult();
+ pSelectData->SetAuxiliary( fTotal );
+ pAggData->SetResult( fTotal );
+ pAggData->SetEmpty(false); // always display
+ }
+ }
+ else
+ pAggData->SetError();
+ }
+ else if (bNoDetailsInRef)
+ pAggData->SetError();
+ else
+ pAggData->SetEmpty(true); // empty (dim set to 0 above)
+ }
+ else
+ {
+ // difference/percentage -> find specified member
+
+ if ( pSelectDim )
+ {
+ OUString aRefItemName = aReferenceValue.ReferenceItemName;
+ ScDPRelativePos aRefItemPos( 0, nRelativeDir ); // nBasePos is modified later
+
+ const OUString* pRefName = nullptr;
+ const ScDPRelativePos* pRefPos = nullptr;
+ if ( bRelative )
+ pRefPos = &aRefItemPos;
+ else
+ pRefName = &aRefItemName;
+
+ ScDPDataMember* pSelectMember;
+ if ( bRefDimInCol )
+ {
+ aRefItemPos.nBasePos = rColVisible[nColPos]; // without sort order applied
+ pSelectMember = ScDPResultDimension::GetColReferenceMember( pRefPos, pRefName,
+ nColPos, rRunning );
+ }
+ else
+ {
+ aRefItemPos.nBasePos = rRowVisible[nRowPos]; // without sort order applied
+ const sal_Int32* pRowSorted = rRowSorted.data();
+ const sal_Int32* pColSorted = rColSorted.data();
+ pRowSorted += nRowPos + 1; // including the reference dimension
+ pSelectMember = pSelectDim->GetRowReferenceMember(
+ pRefPos, pRefName, pRowSorted, pColSorted);
+ }
+
+ // difference or perc.difference is empty for the reference item itself
+ if ( pSelectMember == this &&
+ eRefType != sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE )
+ {
+ pAggData->SetEmpty(true);
+ }
+ else if ( pSelectMember )
+ {
+ const ScDPAggData* pOtherAggData = pSelectMember->
+ GetConstAggData( nMemberMeasure, aLocalSubState );
+ OSL_ENSURE( pOtherAggData, "no agg data" );
+ if ( pOtherAggData )
+ {
+ // Reference member may be visited before or after this one,
+ // so the auxiliary value is used for the original result.
+
+ double fOtherResult = pOtherAggData->GetAuxiliary();
+ double fThisResult = pAggData->GetResult();
+ bool bError = false;
+ switch ( eRefType )
+ {
+ case sheet::DataPilotFieldReferenceType::ITEM_DIFFERENCE:
+ fThisResult = fThisResult - fOtherResult;
+ break;
+ case sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE:
+ if ( fOtherResult == 0.0 )
+ bError = true;
+ else
+ fThisResult = fThisResult / fOtherResult;
+ break;
+ case sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE_DIFFERENCE:
+ if ( fOtherResult == 0.0 )
+ bError = true;
+ else
+ fThisResult = ( fThisResult - fOtherResult ) / fOtherResult;
+ break;
+ default:
+ OSL_FAIL("invalid calculation type");
+ }
+ if ( bError )
+ {
+ pAggData->SetError();
+ }
+ else
+ {
+ pAggData->SetResult(fThisResult);
+ pAggData->SetEmpty(false); // always display
+ }
+ //TODO: errors in data?
+ }
+ }
+ else if (bRelative && !bNoDetailsInRef)
+ pAggData->SetEmpty(true); // empty
+ else
+ pAggData->SetError(); // error
+ }
+ else if (bNoDetailsInRef)
+ pAggData->SetError(); // error
+ else
+ pAggData->SetEmpty(true); // empty
+ }
+ }
+ else if ( eRefType == sheet::DataPilotFieldReferenceType::ROW_PERCENTAGE ||
+ eRefType == sheet::DataPilotFieldReferenceType::COLUMN_PERCENTAGE ||
+ eRefType == sheet::DataPilotFieldReferenceType::TOTAL_PERCENTAGE ||
+ eRefType == sheet::DataPilotFieldReferenceType::INDEX )
+ {
+
+ // set total values when they are encountered (always before their use)
+
+ ScDPAggData* pColTotalData = pRefMember->GetColTotal( nMemberMeasure );
+ ScDPAggData* pRowTotalData = rTotals.GetRowTotal( nMemberMeasure );
+ ScDPAggData* pGrandTotalData = rTotals.GetGrandTotal( nMemberMeasure );
+
+ double fTotalValue = pAggData->HasError() ? 0 : pAggData->GetResult();
+
+ if ( bIsRoot && rTotals.IsInColRoot() && pGrandTotalData )
+ pGrandTotalData->SetAuxiliary( fTotalValue );
+
+ if ( bIsRoot && pRowTotalData )
+ pRowTotalData->SetAuxiliary( fTotalValue );
+
+ if ( rTotals.IsInColRoot() && pColTotalData )
+ pColTotalData->SetAuxiliary( fTotalValue );
+
+ // find relation to total values
+
+ switch ( eRefType )
+ {
+ case sheet::DataPilotFieldReferenceType::ROW_PERCENTAGE:
+ case sheet::DataPilotFieldReferenceType::COLUMN_PERCENTAGE:
+ case sheet::DataPilotFieldReferenceType::TOTAL_PERCENTAGE:
+ {
+ double nTotal;
+ if ( eRefType == sheet::DataPilotFieldReferenceType::ROW_PERCENTAGE )
+ nTotal = pRowTotalData ? pRowTotalData->GetAuxiliary() : 0.0;
+ else if ( eRefType == sheet::DataPilotFieldReferenceType::COLUMN_PERCENTAGE )
+ nTotal = pColTotalData ? pColTotalData->GetAuxiliary() : 0.0;
+ else
+ nTotal = pGrandTotalData ? pGrandTotalData->GetAuxiliary() : 0.0;
+
+ if ( nTotal == 0.0 )
+ pAggData->SetError();
+ else
+ pAggData->SetResult( pAggData->GetResult() / nTotal );
+ }
+ break;
+ case sheet::DataPilotFieldReferenceType::INDEX:
+ {
+ double nColTotal = pColTotalData ? pColTotalData->GetAuxiliary() : 0.0;
+ double nRowTotal = pRowTotalData ? pRowTotalData->GetAuxiliary() : 0.0;
+ double nGrandTotal = pGrandTotalData ? pGrandTotalData->GetAuxiliary() : 0.0;
+ if ( nRowTotal == 0.0 || nColTotal == 0.0 )
+ pAggData->SetError();
+ else
+ pAggData->SetResult(
+ ( pAggData->GetResult() * nGrandTotal ) /
+ ( nRowTotal * nColTotal ) );
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if ( bHasChild ) // child dimension must be processed last, so the row total is known
+ {
+ if ( pDataChild )
+ pDataChild->UpdateRunningTotals( pRefChild, nMeasure,
+ bIsSubTotalRow, rSubState, rRunning, rTotals, rRowParent );
+ }
+}
+
+#if DUMP_PIVOT_TABLE
+void ScDPDataMember::DumpState( const ScDPResultMember* pRefMember, ScDocument* pDoc, ScAddress& rPos ) const
+{
+ dumpRow("ScDPDataMember", GetName(), &aAggregate, pDoc, rPos);
+ SCROW nStartRow = rPos.Row();
+
+ const ScDPDataDimension* pDataChild = GetChildDimension();
+ const ScDPResultDimension* pRefChild = pRefMember->GetChildDimension();
+ if ( pDataChild && pRefChild )
+ pDataChild->DumpState( pRefChild, pDoc, rPos );
+
+ indent(pDoc, nStartRow, rPos);
+}
+
+void ScDPDataMember::Dump(int nIndent) const
+{
+ std::string aIndent(nIndent*2, ' ');
+ std::cout << aIndent << "-- data member '"
+ << (pResultMember ? pResultMember->GetName() : OUString()) << "'" << std::endl;
+ for (const ScDPAggData* pAgg = &aAggregate; pAgg; pAgg = pAgg->GetExistingChild())
+ pAgg->Dump(nIndent+1);
+
+ if (pChildDimension)
+ pChildDimension->Dump(nIndent+1);
+}
+#endif
+
+// Helper class to select the members to include in
+// ScDPResultDimension::InitFrom or LateInitFrom if groups are used
+
+namespace {
+
+class ScDPGroupCompare
+{
+private:
+ const ScDPResultData* pResultData;
+ const ScDPInitState& rInitState;
+ tools::Long nDimSource;
+ bool bIncludeAll;
+ bool bIsBase;
+ tools::Long nGroupBase;
+public:
+ ScDPGroupCompare( const ScDPResultData* pData, const ScDPInitState& rState, tools::Long nDimension );
+
+ bool IsIncluded( const ScDPMember& rMember ) { return bIncludeAll || TestIncluded( rMember ); }
+ bool TestIncluded( const ScDPMember& rMember );
+};
+
+}
+
+ScDPGroupCompare::ScDPGroupCompare( const ScDPResultData* pData, const ScDPInitState& rState, tools::Long nDimension ) :
+ pResultData( pData ),
+ rInitState( rState ),
+ nDimSource( nDimension )
+{
+ bIsBase = pResultData->IsBaseForGroup( nDimSource );
+ nGroupBase = pResultData->GetGroupBase( nDimSource ); //TODO: get together in one call?
+
+ // if bIncludeAll is set, TestIncluded doesn't need to be called
+ bIncludeAll = !( bIsBase || nGroupBase >= 0 );
+}
+
+bool ScDPGroupCompare::TestIncluded( const ScDPMember& rMember )
+{
+ bool bInclude = true;
+ if ( bIsBase )
+ {
+ // need to check all previous groups
+ //TODO: get array of groups (or indexes) before loop?
+ ScDPItemData aMemberData(rMember.FillItemData());
+
+ const std::vector<ScDPInitState::Member>& rMemStates = rInitState.GetMembers();
+ bInclude = std::all_of(rMemStates.begin(), rMemStates.end(),
+ [this, &aMemberData](const ScDPInitState::Member& rMem) {
+ return (pResultData->GetGroupBase(rMem.mnSrcIndex) != nDimSource)
+ || pResultData->IsInGroup(rMem.mnNameIndex, rMem.mnSrcIndex, aMemberData, nDimSource);
+ });
+ }
+ else if ( nGroupBase >= 0 )
+ {
+ // base isn't used in preceding fields
+ // -> look for other groups using the same base
+
+ //TODO: get array of groups (or indexes) before loop?
+ ScDPItemData aMemberData(rMember.FillItemData());
+ const std::vector<ScDPInitState::Member>& rMemStates = rInitState.GetMembers();
+ bInclude = std::all_of(rMemStates.begin(), rMemStates.end(),
+ [this, &aMemberData](const ScDPInitState::Member& rMem) {
+ // coverity[copy_paste_error : FALSE] - same base (hierarchy between
+ // the two groups is irrelevant)
+ return (pResultData->GetGroupBase(rMem.mnSrcIndex) != nGroupBase)
+ || pResultData->HasCommonElement(rMem.mnNameIndex, rMem.mnSrcIndex, aMemberData, nDimSource);
+ });
+ }
+
+ return bInclude;
+}
+
+ScDPResultDimension::ScDPResultDimension( const ScDPResultData* pData ) :
+ pResultData( pData ),
+ nSortMeasure( 0 ),
+ bIsDataLayout( false ),
+ bSortByData( false ),
+ bSortAscending( false ),
+ bAutoShow( false ),
+ bAutoTopItems( false ),
+ bInitialized( false ),
+ nAutoMeasure( 0 ),
+ nAutoCount( 0 )
+{
+}
+
+ScDPResultDimension::~ScDPResultDimension()
+{
+}
+
+ScDPResultMember *ScDPResultDimension::FindMember( SCROW iData ) const
+{
+ if( bIsDataLayout )
+ {
+ SAL_WARN_IF(maMemberArray.empty(), "sc.core", "MemberArray is empty");
+ return !maMemberArray.empty() ? maMemberArray[0].get() : nullptr;
+ }
+
+ MemberHash::const_iterator aRes = maMemberHash.find( iData );
+ if( aRes != maMemberHash.end()) {
+ if ( aRes->second->IsNamedItem( iData ) )
+ return aRes->second;
+ OSL_FAIL("problem! hash result is not the same as IsNamedItem");
+ }
+
+ unsigned int i;
+ unsigned int nCount = maMemberArray.size();
+ for( i = 0; i < nCount ; i++ )
+ {
+ ScDPResultMember* pResultMember = maMemberArray[i].get();
+ if ( pResultMember->IsNamedItem( iData ) )
+ return pResultMember;
+ }
+ return nullptr;
+}
+
+void ScDPResultDimension::InitFrom(
+ const vector<ScDPDimension*>& ppDim, const vector<ScDPLevel*>& ppLev,
+ size_t nPos, ScDPInitState& rInitState, bool bInitChild )
+{
+ if (nPos >= ppDim.size() || nPos >= ppLev.size())
+ {
+ bInitialized = true;
+ return;
+ }
+
+ ScDPDimension* pThisDim = ppDim[nPos];
+ ScDPLevel* pThisLevel = ppLev[nPos];
+
+ if (!pThisDim || !pThisLevel)
+ {
+ bInitialized = true;
+ return;
+ }
+
+ bIsDataLayout = pThisDim->getIsDataLayoutDimension(); // member
+ aDimensionName = pThisDim->getName(); // member
+
+ // Check the autoshow setting. If it's enabled, store the settings.
+ const sheet::DataPilotFieldAutoShowInfo& rAutoInfo = pThisLevel->GetAutoShow();
+ if ( rAutoInfo.IsEnabled )
+ {
+ bAutoShow = true;
+ bAutoTopItems = ( rAutoInfo.ShowItemsMode == sheet::DataPilotFieldShowItemsMode::FROM_TOP );
+ nAutoMeasure = pThisLevel->GetAutoMeasure();
+ nAutoCount = rAutoInfo.ItemCount;
+ }
+
+ // Check the sort info, and store the settings if appropriate.
+ const sheet::DataPilotFieldSortInfo& rSortInfo = pThisLevel->GetSortInfo();
+ if ( rSortInfo.Mode == sheet::DataPilotFieldSortMode::DATA )
+ {
+ bSortByData = true;
+ bSortAscending = rSortInfo.IsAscending;
+ nSortMeasure = pThisLevel->GetSortMeasure();
+ }
+
+ // global order is used to initialize aMembers, so it doesn't have to be looked at later
+ const ScMemberSortOrder& rGlobalOrder = pThisLevel->GetGlobalOrder();
+
+ tools::Long nDimSource = pThisDim->GetDimension(); //TODO: check GetSourceDim?
+ ScDPGroupCompare aCompare( pResultData, rInitState, nDimSource );
+
+ // Now, go through all members and initialize them.
+ ScDPMembers* pMembers = pThisLevel->GetMembersObject();
+ tools::Long nMembCount = pMembers->getCount();
+ for ( tools::Long i=0; i<nMembCount; i++ )
+ {
+ tools::Long nSorted = rGlobalOrder.empty() ? i : rGlobalOrder[i];
+
+ ScDPMember* pMember = pMembers->getByIndex(nSorted);
+ if ( aCompare.IsIncluded( *pMember ) )
+ {
+ ScDPParentDimData aData( i, pThisDim, pThisLevel, pMember);
+ ScDPResultMember* pNew = AddMember( aData );
+
+ rInitState.AddMember(nDimSource, pNew->GetDataId());
+ pNew->InitFrom( ppDim, ppLev, nPos+1, rInitState, bInitChild );
+ rInitState.RemoveMember();
+ }
+ }
+ bInitialized = true;
+}
+
+void ScDPResultDimension::LateInitFrom(
+ LateInitParams& rParams, const vector<SCROW>& pItemData, size_t nPos, ScDPInitState& rInitState)
+{
+ if ( rParams.IsEnd( nPos ) )
+ return;
+ if (nPos >= pItemData.size())
+ {
+ SAL_WARN("sc.core", "pos " << nPos << ", but vector size is " << pItemData.size());
+ return;
+ }
+ SCROW rThisData = pItemData[nPos];
+ ScDPDimension* pThisDim = rParams.GetDim( nPos );
+ ScDPLevel* pThisLevel = rParams.GetLevel( nPos );
+
+ if (!pThisDim || !pThisLevel)
+ return;
+
+ tools::Long nDimSource = pThisDim->GetDimension(); //TODO: check GetSourceDim?
+
+ bool bShowEmpty = pThisLevel->getShowEmpty();
+
+ if ( !bInitialized )
+ { // init some values
+ // create all members at the first call (preserve order)
+ bIsDataLayout = pThisDim->getIsDataLayoutDimension();
+ aDimensionName = pThisDim->getName();
+
+ const sheet::DataPilotFieldAutoShowInfo& rAutoInfo = pThisLevel->GetAutoShow();
+ if ( rAutoInfo.IsEnabled )
+ {
+ bAutoShow = true;
+ bAutoTopItems = ( rAutoInfo.ShowItemsMode == sheet::DataPilotFieldShowItemsMode::FROM_TOP );
+ nAutoMeasure = pThisLevel->GetAutoMeasure();
+ nAutoCount = rAutoInfo.ItemCount;
+ }
+
+ const sheet::DataPilotFieldSortInfo& rSortInfo = pThisLevel->GetSortInfo();
+ if ( rSortInfo.Mode == sheet::DataPilotFieldSortMode::DATA )
+ {
+ bSortByData = true;
+ bSortAscending = rSortInfo.IsAscending;
+ nSortMeasure = pThisLevel->GetSortMeasure();
+ }
+ }
+
+ bool bLateInitAllMembers= bIsDataLayout || rParams.GetInitAllChild() || bShowEmpty;
+
+ if ( !bLateInitAllMembers )
+ {
+ ResultMembers& rMembers = pResultData->GetDimResultMembers(nDimSource, pThisDim, pThisLevel);
+ bLateInitAllMembers = rMembers.IsHasHideDetailsMembers();
+
+ SAL_INFO("sc.core", aDimensionName << (rMembers.IsHasHideDetailsMembers() ? " HasHideDetailsMembers" : ""));
+
+ rMembers.SetHasHideDetailsMembers( false );
+ }
+
+ bool bNewAllMembers = (!rParams.IsRow()) || nPos == 0 || bLateInitAllMembers;
+
+ if (bNewAllMembers )
+ {
+ // global order is used to initialize aMembers, so it doesn't have to be looked at later
+ if ( !bInitialized )
+ { //init all members
+ const ScMemberSortOrder& rGlobalOrder = pThisLevel->GetGlobalOrder();
+
+ ScDPGroupCompare aCompare( pResultData, rInitState, nDimSource );
+ ScDPMembers* pMembers = pThisLevel->GetMembersObject();
+ tools::Long nMembCount = pMembers->getCount();
+ for ( tools::Long i=0; i<nMembCount; i++ )
+ {
+ tools::Long nSorted = rGlobalOrder.empty() ? i : rGlobalOrder[i];
+
+ ScDPMember* pMember = pMembers->getByIndex(nSorted);
+ if ( aCompare.IsIncluded( *pMember ) )
+ { // add all members
+ ScDPParentDimData aData( i, pThisDim, pThisLevel, pMember );
+ AddMember( aData );
+ }
+ }
+ bInitialized = true; // don't call again, even if no members were included
+ }
+ // initialize only specific member (or all if "show empty" flag is set)
+ if ( bLateInitAllMembers )
+ {
+ tools::Long nCount = maMemberArray.size();
+ for (tools::Long i=0; i<nCount; i++)
+ {
+ ScDPResultMember* pResultMember = maMemberArray[i].get();
+
+ // check show empty
+ bool bAllChildren = false;
+ if( bShowEmpty )
+ {
+ bAllChildren = !pResultMember->IsNamedItem( rThisData );
+ }
+ rParams.SetInitAllChildren( bAllChildren );
+ rInitState.AddMember( nDimSource, pResultMember->GetDataId() );
+ pResultMember->LateInitFrom( rParams, pItemData, nPos+1, rInitState );
+ rInitState.RemoveMember();
+ }
+ }
+ else
+ {
+ ScDPResultMember* pResultMember = FindMember( rThisData );
+ if( nullptr != pResultMember )
+ {
+ rInitState.AddMember( nDimSource, pResultMember->GetDataId() );
+ pResultMember->LateInitFrom( rParams, pItemData, nPos+1, rInitState );
+ rInitState.RemoveMember();
+ }
+ }
+ }
+ else
+ InitWithMembers( rParams, pItemData, nPos, rInitState );
+}
+
+tools::Long ScDPResultDimension::GetSize(tools::Long nMeasure) const
+{
+ tools::Long nMemberCount = maMemberArray.size();
+ if (!nMemberCount)
+ return 0;
+
+ tools::Long nTotal = 0;
+ if (bIsDataLayout)
+ {
+ OSL_ENSURE(nMeasure == SC_DPMEASURE_ALL || pResultData->GetMeasureCount() == 1,
+ "DataLayout dimension twice?");
+ // repeat first member...
+ nTotal = nMemberCount * maMemberArray[0]->GetSize(0); // all measures have equal size
+ }
+ else
+ {
+ // add all members
+ for (tools::Long nMem=0; nMem<nMemberCount; nMem++)
+ nTotal += maMemberArray[nMem]->GetSize(nMeasure);
+ }
+ return nTotal;
+}
+
+bool ScDPResultDimension::IsValidEntry( const vector< SCROW >& aMembers ) const
+{
+ if (aMembers.empty())
+ return false;
+
+ const ScDPResultMember* pMember = FindMember( aMembers[0] );
+ if ( nullptr != pMember )
+ return pMember->IsValidEntry( aMembers );
+#if OSL_DEBUG_LEVEL > 0
+ SAL_INFO("sc.core", "IsValidEntry: Member not found, DimNam = " << GetName());
+#endif
+ return false;
+}
+
+void ScDPResultDimension::ProcessData( const vector< SCROW >& aMembers,
+ const ScDPResultDimension* pDataDim,
+ const vector< SCROW >& aDataMembers,
+ const vector<ScDPValue>& aValues ) const
+{
+ if (aMembers.empty())
+ return;
+
+ ScDPResultMember* pMember = FindMember( aMembers[0] );
+ if ( nullptr != pMember )
+ {
+ vector<SCROW> aChildMembers;
+ if (aMembers.size() > 1)
+ {
+ vector<SCROW>::const_iterator itr = aMembers.begin();
+ aChildMembers.insert(aChildMembers.begin(), ++itr, aMembers.end());
+ }
+ pMember->ProcessData( aChildMembers, pDataDim, aDataMembers, aValues );
+ return;
+ }
+
+ OSL_FAIL("ProcessData: Member not found");
+}
+
+void ScDPResultDimension::FillMemberResults( uno::Sequence<sheet::MemberResult>* pSequences,
+ tools::Long nStart, tools::Long nMeasure )
+{
+ tools::Long nPos = nStart;
+ tools::Long nCount = maMemberArray.size();
+
+ for (tools::Long i=0; i<nCount; i++)
+ {
+ tools::Long nSorted = aMemberOrder.empty() ? i : aMemberOrder[i];
+
+ ScDPResultMember* pMember = maMemberArray[nSorted].get();
+ // in data layout dimension, use first member with different measures/names
+ if ( bIsDataLayout )
+ {
+ bool bTotalResult = false;
+ OUString aMbrName = pResultData->GetMeasureDimensionName( nSorted );
+ OUString aMbrCapt = pResultData->GetMeasureString( nSorted, false, SUBTOTAL_FUNC_NONE, bTotalResult );
+ maMemberArray[0]->FillMemberResults( pSequences, nPos, nSorted, false, &aMbrName, &aMbrCapt );
+ }
+ else if ( pMember->IsVisible() )
+ {
+ pMember->FillMemberResults( pSequences, nPos, nMeasure, false, nullptr, nullptr );
+ }
+ // nPos is modified
+ }
+}
+
+void ScDPResultDimension::FillDataResults(
+ const ScDPResultMember* pRefMember, ScDPResultFilterContext& rFilterCxt,
+ uno::Sequence< uno::Sequence<sheet::DataResult> >& rSequence, tools::Long nMeasure) const
+{
+ FilterStack aFilterStack(rFilterCxt.maFilters);
+ aFilterStack.pushDimName(GetName(), bIsDataLayout);
+
+ tools::Long nMemberMeasure = nMeasure;
+ tools::Long nCount = maMemberArray.size();
+ for (tools::Long i=0; i<nCount; i++)
+ {
+ tools::Long nSorted = aMemberOrder.empty() ? i : aMemberOrder[i];
+
+ const ScDPResultMember* pMember;
+ if (bIsDataLayout)
+ {
+ OSL_ENSURE(nMeasure == SC_DPMEASURE_ALL || pResultData->GetMeasureCount() == 1,
+ "DataLayout dimension twice?");
+ pMember = maMemberArray[0].get();
+ nMemberMeasure = nSorted;
+ }
+ else
+ pMember = maMemberArray[nSorted].get();
+
+ if ( pMember->IsVisible() )
+ pMember->FillDataResults(pRefMember, rFilterCxt, rSequence, nMemberMeasure);
+ }
+}
+
+void ScDPResultDimension::UpdateDataResults( const ScDPResultMember* pRefMember, tools::Long nMeasure ) const
+{
+ tools::Long nMemberMeasure = nMeasure;
+ tools::Long nCount = maMemberArray.size();
+ for (tools::Long i=0; i<nCount; i++)
+ {
+ const ScDPResultMember* pMember;
+ if (bIsDataLayout)
+ {
+ OSL_ENSURE(nMeasure == SC_DPMEASURE_ALL || pResultData->GetMeasureCount() == 1,
+ "DataLayout dimension twice?");
+ pMember = maMemberArray[0].get();
+ nMemberMeasure = i;
+ }
+ else
+ pMember = maMemberArray[i].get();
+
+ if ( pMember->IsVisible() )
+ pMember->UpdateDataResults( pRefMember, nMemberMeasure );
+ }
+}
+
+void ScDPResultDimension::SortMembers( ScDPResultMember* pRefMember )
+{
+ tools::Long nCount = maMemberArray.size();
+
+ if ( bSortByData )
+ {
+ // sort members
+
+ OSL_ENSURE( aMemberOrder.empty(), "sort twice?" );
+ aMemberOrder.resize( nCount );
+ for (tools::Long nPos=0; nPos<nCount; nPos++)
+ aMemberOrder[nPos] = nPos;
+
+ ScDPRowMembersOrder aComp( *this, nSortMeasure, bSortAscending );
+ ::std::sort( aMemberOrder.begin(), aMemberOrder.end(), aComp );
+ }
+
+ // handle children
+
+ // for data layout, call only once - sorting measure is always taken from settings
+ tools::Long nLoopCount = bIsDataLayout ? std::min<tools::Long>(1, nCount) : nCount;
+ for (tools::Long i=0; i<nLoopCount; i++)
+ {
+ ScDPResultMember* pMember = maMemberArray[i].get();
+ if ( pMember->IsVisible() )
+ pMember->SortMembers( pRefMember );
+ }
+}
+
+void ScDPResultDimension::DoAutoShow( ScDPResultMember* pRefMember )
+{
+ tools::Long nCount = maMemberArray.size();
+
+ // handle children first, before changing the visible state
+
+ // for data layout, call only once - sorting measure is always taken from settings
+ tools::Long nLoopCount = bIsDataLayout ? 1 : nCount;
+ for (tools::Long i=0; i<nLoopCount; i++)
+ {
+ ScDPResultMember* pMember = maMemberArray[i].get();
+ if ( pMember->IsVisible() )
+ pMember->DoAutoShow( pRefMember );
+ }
+
+ if ( !(bAutoShow && nAutoCount > 0 && nAutoCount < nCount) )
+ return;
+
+ // establish temporary order, hide remaining members
+
+ ScMemberSortOrder aAutoOrder;
+ aAutoOrder.resize( nCount );
+ tools::Long nPos;
+ for (nPos=0; nPos<nCount; nPos++)
+ aAutoOrder[nPos] = nPos;
+
+ ScDPRowMembersOrder aComp( *this, nAutoMeasure, !bAutoTopItems );
+ ::std::sort( aAutoOrder.begin(), aAutoOrder.end(), aComp );
+
+ // look for equal values to the last included one
+
+ tools::Long nIncluded = nAutoCount;
+ const ScDPResultMember* pMember1 = maMemberArray[aAutoOrder[nIncluded - 1]].get();
+ const ScDPDataMember* pDataMember1 = pMember1->IsVisible() ? pMember1->GetDataRoot() : nullptr;
+ bool bContinue = true;
+ while ( bContinue )
+ {
+ bContinue = false;
+ if ( nIncluded < nCount )
+ {
+ const ScDPResultMember* pMember2 = maMemberArray[aAutoOrder[nIncluded]].get();
+ const ScDPDataMember* pDataMember2 = pMember2->IsVisible() ? pMember2->GetDataRoot() : nullptr;
+
+ if ( lcl_IsEqual( pDataMember1, pDataMember2, nAutoMeasure ) )
+ {
+ ++nIncluded; // include more members if values are equal
+ bContinue = true;
+ }
+ }
+ }
+
+ // hide the remaining members
+
+ for (nPos = nIncluded; nPos < nCount; nPos++)
+ {
+ ScDPResultMember* pMember = maMemberArray[aAutoOrder[nPos]].get();
+ pMember->SetAutoHidden();
+ }
+}
+
+void ScDPResultDimension::ResetResults()
+{
+ tools::Long nCount = maMemberArray.size();
+ for (tools::Long i=0; i<nCount; i++)
+ {
+ // sort order doesn't matter
+ ScDPResultMember* pMember = maMemberArray[bIsDataLayout ? 0 : i].get();
+ pMember->ResetResults();
+ }
+}
+
+tools::Long ScDPResultDimension::GetSortedIndex( tools::Long nUnsorted ) const
+{
+ return aMemberOrder.empty() ? nUnsorted : aMemberOrder[nUnsorted];
+}
+
+void ScDPResultDimension::UpdateRunningTotals( const ScDPResultMember* pRefMember, tools::Long nMeasure,
+ ScDPRunningTotalState& rRunning, ScDPRowTotals& rTotals ) const
+{
+ const ScDPResultMember* pMember;
+ tools::Long nMemberMeasure = nMeasure;
+ tools::Long nCount = maMemberArray.size();
+ for (tools::Long i=0; i<nCount; i++)
+ {
+ tools::Long nSorted = aMemberOrder.empty() ? i : aMemberOrder[i];
+
+ if (bIsDataLayout)
+ {
+ OSL_ENSURE(nMeasure == SC_DPMEASURE_ALL || pResultData->GetMeasureCount() == 1,
+ "DataLayout dimension twice?");
+ pMember = maMemberArray[0].get();
+ nMemberMeasure = nSorted;
+ }
+ else
+ pMember = maMemberArray[nSorted].get();
+
+ if ( pMember->IsVisible() )
+ {
+ if ( bIsDataLayout )
+ rRunning.AddRowIndex( 0, 0 );
+ else
+ rRunning.AddRowIndex( i, nSorted );
+ pMember->UpdateRunningTotals( pRefMember, nMemberMeasure, rRunning, rTotals );
+ rRunning.RemoveRowIndex();
+ }
+ }
+}
+
+ScDPDataMember* ScDPResultDimension::GetRowReferenceMember(
+ const ScDPRelativePos* pRelativePos, const OUString* pName,
+ const sal_Int32* pRowIndexes, const sal_Int32* pColIndexes ) const
+{
+ // get named, previous/next, or first member of this dimension (first existing if pRelativePos and pName are NULL)
+
+ OSL_ENSURE( pRelativePos == nullptr || pName == nullptr, "can't use position and name" );
+
+ ScDPDataMember* pColMember = nullptr;
+
+ bool bFirstExisting = ( pRelativePos == nullptr && pName == nullptr );
+ tools::Long nMemberCount = maMemberArray.size();
+ tools::Long nMemberIndex = 0; // unsorted
+ tools::Long nDirection = 1; // forward if no relative position is used
+ if ( pRelativePos )
+ {
+ nDirection = pRelativePos->nDirection;
+ nMemberIndex = pRelativePos->nBasePos + nDirection; // bounds are handled below
+
+ OSL_ENSURE( nDirection == 1 || nDirection == -1, "Direction must be 1 or -1" );
+ }
+ else if ( pName )
+ {
+ // search for named member
+
+ const ScDPResultMember* pRowMember = maMemberArray[GetSortedIndex(nMemberIndex)].get();
+
+ //TODO: use ScDPItemData, as in ScDPDimension::IsValidPage?
+ while ( pRowMember && pRowMember->GetName() != *pName )
+ {
+ ++nMemberIndex;
+ if ( nMemberIndex < nMemberCount )
+ pRowMember = maMemberArray[GetSortedIndex(nMemberIndex)].get();
+ else
+ pRowMember = nullptr;
+ }
+ }
+
+ bool bContinue = true;
+ while ( bContinue && nMemberIndex >= 0 && nMemberIndex < nMemberCount )
+ {
+ const ScDPResultMember* pRowMember = maMemberArray[GetSortedIndex(nMemberIndex)].get();
+
+ // get child members by given indexes
+
+ const sal_Int32* pNextRowIndex = pRowIndexes;
+ while ( *pNextRowIndex >= 0 && pRowMember )
+ {
+ const ScDPResultDimension* pRowChild = pRowMember->GetChildDimension();
+ if ( pRowChild && *pNextRowIndex < pRowChild->GetMemberCount() )
+ pRowMember = pRowChild->GetMember( *pNextRowIndex );
+ else
+ pRowMember = nullptr;
+ ++pNextRowIndex;
+ }
+
+ if ( pRowMember && pRelativePos )
+ {
+ // Skip the member if it has hidden details
+ // (because when looking for the details, it is skipped, too).
+ // Also skip if the member is invisible because it has no data,
+ // for consistent ordering.
+ if ( pRowMember->HasHiddenDetails() || !pRowMember->IsVisible() )
+ pRowMember = nullptr;
+ }
+
+ if ( pRowMember )
+ {
+ pColMember = pRowMember->GetDataRoot();
+
+ const sal_Int32* pNextColIndex = pColIndexes;
+ while ( *pNextColIndex >= 0 && pColMember )
+ {
+ ScDPDataDimension* pColChild = pColMember->GetChildDimension();
+ if ( pColChild && *pNextColIndex < pColChild->GetMemberCount() )
+ pColMember = pColChild->GetMember( *pNextColIndex );
+ else
+ pColMember = nullptr;
+ ++pNextColIndex;
+ }
+ }
+
+ // continue searching only if looking for first existing or relative position
+ bContinue = ( pColMember == nullptr && ( bFirstExisting || pRelativePos ) );
+ nMemberIndex += nDirection;
+ }
+
+ return pColMember;
+}
+
+ScDPDataMember* ScDPResultDimension::GetColReferenceMember(
+ const ScDPRelativePos* pRelativePos, const OUString* pName,
+ sal_Int32 nRefDimPos, const ScDPRunningTotalState& rRunning )
+{
+ OSL_ENSURE( pRelativePos == nullptr || pName == nullptr, "can't use position and name" );
+
+ const sal_Int32* pColIndexes = rRunning.GetColSorted().data();
+ const sal_Int32* pRowIndexes = rRunning.GetRowSorted().data();
+
+ // get own row member using all indexes
+
+ const ScDPResultMember* pRowMember = rRunning.GetRowResRoot();
+ ScDPDataMember* pColMember = nullptr;
+
+ const sal_Int32* pNextRowIndex = pRowIndexes;
+ while ( *pNextRowIndex >= 0 && pRowMember )
+ {
+ const ScDPResultDimension* pRowChild = pRowMember->GetChildDimension();
+ if ( pRowChild && *pNextRowIndex < pRowChild->GetMemberCount() )
+ pRowMember = pRowChild->GetMember( *pNextRowIndex );
+ else
+ pRowMember = nullptr;
+ ++pNextRowIndex;
+ }
+
+ // get column (data) members before the reference field
+ //TODO: pass rRowParent from ScDPDataMember::UpdateRunningTotals instead
+
+ if ( pRowMember )
+ {
+ pColMember = pRowMember->GetDataRoot();
+
+ const sal_Int32* pNextColIndex = pColIndexes;
+ sal_Int32 nColSkipped = 0;
+ while ( *pNextColIndex >= 0 && pColMember && nColSkipped < nRefDimPos )
+ {
+ ScDPDataDimension* pColChild = pColMember->GetChildDimension();
+ if ( pColChild && *pNextColIndex < pColChild->GetMemberCount() )
+ pColMember = pColChild->GetMember( *pNextColIndex );
+ else
+ pColMember = nullptr;
+ ++pNextColIndex;
+ ++nColSkipped;
+ }
+ }
+
+ // get column member for the reference field
+
+ if ( pColMember )
+ {
+ ScDPDataDimension* pReferenceDim = pColMember->GetChildDimension();
+ if ( pReferenceDim )
+ {
+ tools::Long nReferenceCount = pReferenceDim->GetMemberCount();
+
+ bool bFirstExisting = ( pRelativePos == nullptr && pName == nullptr );
+ tools::Long nMemberIndex = 0; // unsorted
+ tools::Long nDirection = 1; // forward if no relative position is used
+ pColMember = nullptr; // don't use parent dimension's member if none found
+ if ( pRelativePos )
+ {
+ nDirection = pRelativePos->nDirection;
+ nMemberIndex = pRelativePos->nBasePos + nDirection; // bounds are handled below
+ }
+ else if ( pName )
+ {
+ // search for named member
+
+ pColMember = pReferenceDim->GetMember( pReferenceDim->GetSortedIndex( nMemberIndex ) );
+
+ //TODO: use ScDPItemData, as in ScDPDimension::IsValidPage?
+ while ( pColMember && pColMember->GetName() != *pName )
+ {
+ ++nMemberIndex;
+ if ( nMemberIndex < nReferenceCount )
+ pColMember = pReferenceDim->GetMember( pReferenceDim->GetSortedIndex( nMemberIndex ) );
+ else
+ pColMember = nullptr;
+ }
+ }
+
+ bool bContinue = true;
+ while ( bContinue && nMemberIndex >= 0 && nMemberIndex < nReferenceCount )
+ {
+ pColMember = pReferenceDim->GetMember( pReferenceDim->GetSortedIndex( nMemberIndex ) );
+
+ // get column members below the reference field
+
+ const sal_Int32* pNextColIndex = pColIndexes + nRefDimPos + 1;
+ while ( *pNextColIndex >= 0 && pColMember )
+ {
+ ScDPDataDimension* pColChild = pColMember->GetChildDimension();
+ if ( pColChild && *pNextColIndex < pColChild->GetMemberCount() )
+ pColMember = pColChild->GetMember( *pNextColIndex );
+ else
+ pColMember = nullptr;
+ ++pNextColIndex;
+ }
+
+ if ( pColMember && pRelativePos )
+ {
+ // Skip the member if it has hidden details
+ // (because when looking for the details, it is skipped, too).
+ // Also skip if the member is invisible because it has no data,
+ // for consistent ordering.
+ if ( pColMember->HasHiddenDetails() || !pColMember->IsVisible() )
+ pColMember = nullptr;
+ }
+
+ // continue searching only if looking for first existing or relative position
+ bContinue = ( pColMember == nullptr && ( bFirstExisting || pRelativePos ) );
+ nMemberIndex += nDirection;
+ }
+ }
+ else
+ pColMember = nullptr;
+ }
+
+ return pColMember;
+}
+
+#if DUMP_PIVOT_TABLE
+void ScDPResultDimension::DumpState( const ScDPResultMember* pRefMember, ScDocument* pDoc, ScAddress& rPos ) const
+{
+ OUString aDimName = bIsDataLayout ? OUString("(data layout)") : GetName();
+ dumpRow("ScDPResultDimension", aDimName, nullptr, pDoc, rPos);
+
+ SCROW nStartRow = rPos.Row();
+
+ tools::Long nCount = bIsDataLayout ? 1 : maMemberArray.size();
+ for (tools::Long i=0; i<nCount; i++)
+ {
+ const ScDPResultMember* pMember = maMemberArray[i].get();
+ pMember->DumpState( pRefMember, pDoc, rPos );
+ }
+
+ indent(pDoc, nStartRow, rPos);
+}
+
+void ScDPResultDimension::Dump(int nIndent) const
+{
+ std::string aIndent(nIndent*2, ' ');
+ std::cout << aIndent << "-- dimension '" << GetName() << "'" << std::endl;
+ for (const auto& rxMember : maMemberArray)
+ {
+ const ScDPResultMember* p = rxMember.get();
+ p->Dump(nIndent+1);
+ }
+}
+#endif
+
+tools::Long ScDPResultDimension::GetMemberCount() const
+{
+ return maMemberArray.size();
+}
+
+const ScDPResultMember* ScDPResultDimension::GetMember(tools::Long n) const
+{
+ return maMemberArray[n].get();
+}
+ScDPResultMember* ScDPResultDimension::GetMember(tools::Long n)
+{
+ return maMemberArray[n].get();
+}
+
+ScDPResultDimension* ScDPResultDimension::GetFirstChildDimension() const
+{
+ if ( !maMemberArray.empty() )
+ return maMemberArray[0]->GetChildDimension();
+ else
+ return nullptr;
+}
+
+void ScDPResultDimension::FillVisibilityData(ScDPResultVisibilityData& rData) const
+{
+ if (IsDataLayout())
+ return;
+
+ for (const auto& rxMember : maMemberArray)
+ {
+ ScDPResultMember* pMember = rxMember.get();
+ if (pMember->IsValid())
+ {
+ ScDPItemData aItem(pMember->FillItemData());
+ rData.addVisibleMember(GetName(), aItem);
+ pMember->FillVisibilityData(rData);
+ }
+ }
+}
+
+ScDPDataDimension::ScDPDataDimension( const ScDPResultData* pData ) :
+ pResultData( pData ),
+ pResultDimension( nullptr ),
+ bIsDataLayout( false )
+{
+}
+
+ScDPDataDimension::~ScDPDataDimension()
+{
+}
+
+void ScDPDataDimension::InitFrom( const ScDPResultDimension* pDim )
+{
+ if (!pDim)
+ return;
+
+ pResultDimension = pDim;
+ bIsDataLayout = pDim->IsDataLayout();
+
+ // Go through all result members under the given result dimension, and
+ // create a new data member instance for each result member.
+ tools::Long nCount = pDim->GetMemberCount();
+ for (tools::Long i=0; i<nCount; i++)
+ {
+ const ScDPResultMember* pResMem = pDim->GetMember(i);
+
+ ScDPDataMember* pNew = new ScDPDataMember( pResultData, pResMem );
+ maMembers.emplace_back( pNew);
+
+ if ( !pResultData->IsLateInit() )
+ {
+ // with LateInit, pResMem hasn't necessarily been initialized yet,
+ // so InitFrom for the new result member is called from its ProcessData method
+
+ const ScDPResultDimension* pChildDim = pResMem->GetChildDimension();
+ if ( pChildDim )
+ pNew->InitFrom( pChildDim );
+ }
+ }
+}
+
+void ScDPDataDimension::ProcessData( const vector< SCROW >& aDataMembers, const vector<ScDPValue>& aValues,
+ const ScDPSubTotalState& rSubState )
+{
+ // the ScDPItemData array must contain enough entries for all dimensions - this isn't checked
+
+ tools::Long nCount = maMembers.size();
+ for (tools::Long i=0; i<nCount; i++)
+ {
+ ScDPDataMember* pMember = maMembers[static_cast<sal_uInt16>(i)].get();
+
+ // always first member for data layout dim
+ if ( bIsDataLayout || ( !aDataMembers.empty() && pMember->IsNamedItem(aDataMembers[0]) ) )
+ {
+ vector<SCROW> aChildDataMembers;
+ if (aDataMembers.size() > 1)
+ {
+ vector<SCROW>::const_iterator itr = aDataMembers.begin();
+ aChildDataMembers.insert(aChildDataMembers.begin(), ++itr, aDataMembers.end());
+ }
+ pMember->ProcessData( aChildDataMembers, aValues, rSubState );
+ return;
+ }
+ }
+
+ OSL_FAIL("ProcessData: Member not found");
+}
+
+void ScDPDataDimension::FillDataRow(
+ const ScDPResultDimension* pRefDim, ScDPResultFilterContext& rFilterCxt,
+ uno::Sequence<sheet::DataResult>& rSequence, tools::Long nMeasure, bool bIsSubTotalRow,
+ const ScDPSubTotalState& rSubState) const
+{
+ OUString aDimName;
+ bool bDataLayout = false;
+ if (pResultDimension)
+ {
+ aDimName = pResultDimension->GetName();
+ bDataLayout = pResultDimension->IsDataLayout();
+ }
+
+ FilterStack aFilterStack(rFilterCxt.maFilters);
+ aFilterStack.pushDimName(aDimName, bDataLayout);
+
+ OSL_ENSURE( pRefDim && static_cast<size_t>(pRefDim->GetMemberCount()) == maMembers.size(), "dimensions don't match" );
+ OSL_ENSURE( pRefDim == pResultDimension, "wrong dim" );
+
+ const ScMemberSortOrder& rMemberOrder = pRefDim->GetMemberOrder();
+
+ tools::Long nMemberMeasure = nMeasure;
+ tools::Long nCount = maMembers.size();
+ for (tools::Long i=0; i<nCount; i++)
+ {
+ tools::Long nSorted = rMemberOrder.empty() ? i : rMemberOrder[i];
+
+ tools::Long nMemberPos = nSorted;
+ if (bIsDataLayout)
+ {
+ OSL_ENSURE(nMeasure == SC_DPMEASURE_ALL || pResultData->GetMeasureCount() == 1,
+ "DataLayout dimension twice?");
+ nMemberPos = 0;
+ nMemberMeasure = nSorted;
+ }
+
+ const ScDPResultMember* pRefMember = pRefDim->GetMember(nMemberPos);
+ if ( pRefMember->IsVisible() ) //TODO: here or in ScDPDataMember::FillDataRow ???
+ {
+ const ScDPDataMember* pDataMember = maMembers[static_cast<sal_uInt16>(nMemberPos)].get();
+ pDataMember->FillDataRow(pRefMember, rFilterCxt, rSequence, nMemberMeasure, bIsSubTotalRow, rSubState);
+ }
+ }
+}
+
+void ScDPDataDimension::UpdateDataRow( const ScDPResultDimension* pRefDim,
+ tools::Long nMeasure, bool bIsSubTotalRow,
+ const ScDPSubTotalState& rSubState ) const
+{
+ OSL_ENSURE( pRefDim && static_cast<size_t>(pRefDim->GetMemberCount()) == maMembers.size(), "dimensions don't match" );
+ OSL_ENSURE( pRefDim == pResultDimension, "wrong dim" );
+
+ tools::Long nMemberMeasure = nMeasure;
+ tools::Long nCount = maMembers.size();
+ for (tools::Long i=0; i<nCount; i++)
+ {
+ tools::Long nMemberPos = i;
+ if (bIsDataLayout)
+ {
+ OSL_ENSURE(nMeasure == SC_DPMEASURE_ALL || pResultData->GetMeasureCount() == 1,
+ "DataLayout dimension twice?");
+ nMemberPos = 0;
+ nMemberMeasure = i;
+ }
+
+ // Calculate must be called even if the member is not visible (for use as reference value)
+ const ScDPResultMember* pRefMember = pRefDim->GetMember(nMemberPos);
+ ScDPDataMember* pDataMember = maMembers[static_cast<sal_uInt16>(nMemberPos)].get();
+ pDataMember->UpdateDataRow( pRefMember, nMemberMeasure, bIsSubTotalRow, rSubState );
+ }
+}
+
+void ScDPDataDimension::SortMembers( ScDPResultDimension* pRefDim )
+{
+ tools::Long nCount = maMembers.size();
+
+ if ( pRefDim->IsSortByData() )
+ {
+ // sort members
+
+ ScMemberSortOrder& rMemberOrder = pRefDim->GetMemberOrder();
+ OSL_ENSURE( rMemberOrder.empty(), "sort twice?" );
+ rMemberOrder.resize( nCount );
+ for (tools::Long nPos=0; nPos<nCount; nPos++)
+ rMemberOrder[nPos] = nPos;
+
+ ScDPColMembersOrder aComp( *this, pRefDim->GetSortMeasure(), pRefDim->IsSortAscending() );
+ ::std::sort( rMemberOrder.begin(), rMemberOrder.end(), aComp );
+ }
+
+ // handle children
+
+ OSL_ENSURE( pRefDim && static_cast<size_t>(pRefDim->GetMemberCount()) == maMembers.size(), "dimensions don't match" );
+ OSL_ENSURE( pRefDim == pResultDimension, "wrong dim" );
+
+ // for data layout, call only once - sorting measure is always taken from settings
+ tools::Long nLoopCount = bIsDataLayout ? 1 : nCount;
+ for (tools::Long i=0; i<nLoopCount; i++)
+ {
+ ScDPResultMember* pRefMember = pRefDim->GetMember(i);
+ if ( pRefMember->IsVisible() ) //TODO: here or in ScDPDataMember ???
+ {
+ ScDPDataMember* pDataMember = maMembers[static_cast<sal_uInt16>(i)].get();
+ pDataMember->SortMembers( pRefMember );
+ }
+ }
+}
+
+void ScDPDataDimension::DoAutoShow( ScDPResultDimension* pRefDim )
+{
+ tools::Long nCount = maMembers.size();
+
+ // handle children first, before changing the visible state
+
+ OSL_ENSURE( pRefDim && static_cast<size_t>(pRefDim->GetMemberCount()) == maMembers.size(), "dimensions don't match" );
+ OSL_ENSURE( pRefDim == pResultDimension, "wrong dim" );
+
+ // for data layout, call only once - sorting measure is always taken from settings
+ tools::Long nLoopCount = bIsDataLayout ? 1 : nCount;
+ for (tools::Long i=0; i<nLoopCount; i++)
+ {
+ ScDPResultMember* pRefMember = pRefDim->GetMember(i);
+ if ( pRefMember->IsVisible() ) //TODO: here or in ScDPDataMember ???
+ {
+ ScDPDataMember* pDataMember = maMembers[i].get();
+ pDataMember->DoAutoShow( pRefMember );
+ }
+ }
+
+ if ( !(pRefDim->IsAutoShow() && pRefDim->GetAutoCount() > 0 && pRefDim->GetAutoCount() < nCount) )
+ return;
+
+ // establish temporary order, hide remaining members
+
+ ScMemberSortOrder aAutoOrder;
+ aAutoOrder.resize( nCount );
+ tools::Long nPos;
+ for (nPos=0; nPos<nCount; nPos++)
+ aAutoOrder[nPos] = nPos;
+
+ ScDPColMembersOrder aComp( *this, pRefDim->GetAutoMeasure(), !pRefDim->IsAutoTopItems() );
+ ::std::sort( aAutoOrder.begin(), aAutoOrder.end(), aComp );
+
+ // look for equal values to the last included one
+
+ tools::Long nIncluded = pRefDim->GetAutoCount();
+ ScDPDataMember* pDataMember1 = maMembers[aAutoOrder[nIncluded - 1]].get();
+ if ( !pDataMember1->IsVisible() )
+ pDataMember1 = nullptr;
+ bool bContinue = true;
+ while ( bContinue )
+ {
+ bContinue = false;
+ if ( nIncluded < nCount )
+ {
+ ScDPDataMember* pDataMember2 = maMembers[aAutoOrder[nIncluded]].get();
+ if ( !pDataMember2->IsVisible() )
+ pDataMember2 = nullptr;
+
+ if ( lcl_IsEqual( pDataMember1, pDataMember2, pRefDim->GetAutoMeasure() ) )
+ {
+ ++nIncluded; // include more members if values are equal
+ bContinue = true;
+ }
+ }
+ }
+
+ // hide the remaining members
+
+ for (nPos = nIncluded; nPos < nCount; nPos++)
+ {
+ ScDPResultMember* pMember = pRefDim->GetMember(aAutoOrder[nPos]);
+ pMember->SetAutoHidden();
+ }
+}
+
+void ScDPDataDimension::ResetResults()
+{
+ tools::Long nCount = maMembers.size();
+ for (tools::Long i=0; i<nCount; i++)
+ {
+ // sort order doesn't matter
+
+ tools::Long nMemberPos = bIsDataLayout ? 0 : i;
+ ScDPDataMember* pDataMember = maMembers[nMemberPos].get();
+ pDataMember->ResetResults();
+ }
+}
+
+tools::Long ScDPDataDimension::GetSortedIndex( tools::Long nUnsorted ) const
+{
+ if (!pResultDimension)
+ return nUnsorted;
+
+ const ScMemberSortOrder& rMemberOrder = pResultDimension->GetMemberOrder();
+ return rMemberOrder.empty() ? nUnsorted : rMemberOrder[nUnsorted];
+}
+
+void ScDPDataDimension::UpdateRunningTotals( const ScDPResultDimension* pRefDim,
+ tools::Long nMeasure, bool bIsSubTotalRow,
+ const ScDPSubTotalState& rSubState, ScDPRunningTotalState& rRunning,
+ ScDPRowTotals& rTotals, const ScDPResultMember& rRowParent ) const
+{
+ OSL_ENSURE( pRefDim && static_cast<size_t>(pRefDim->GetMemberCount()) == maMembers.size(), "dimensions don't match" );
+ OSL_ENSURE( pRefDim == pResultDimension, "wrong dim" );
+
+ tools::Long nMemberMeasure = nMeasure;
+ tools::Long nCount = maMembers.size();
+ for (tools::Long i=0; i<nCount; i++)
+ {
+ const ScMemberSortOrder& rMemberOrder = pRefDim->GetMemberOrder();
+ tools::Long nSorted = rMemberOrder.empty() ? i : rMemberOrder[i];
+
+ tools::Long nMemberPos = nSorted;
+ if (bIsDataLayout)
+ {
+ OSL_ENSURE(nMeasure == SC_DPMEASURE_ALL || pResultData->GetMeasureCount() == 1,
+ "DataLayout dimension twice?");
+ nMemberPos = 0;
+ nMemberMeasure = nSorted;
+ }
+
+ const ScDPResultMember* pRefMember = pRefDim->GetMember(nMemberPos);
+ if ( pRefMember->IsVisible() )
+ {
+ if ( bIsDataLayout )
+ rRunning.AddColIndex( 0, 0 );
+ else
+ rRunning.AddColIndex( i, nSorted );
+
+ ScDPDataMember* pDataMember = maMembers[nMemberPos].get();
+ pDataMember->UpdateRunningTotals(
+ pRefMember, nMemberMeasure, bIsSubTotalRow, rSubState, rRunning, rTotals, rRowParent);
+
+ rRunning.RemoveColIndex();
+ }
+ }
+}
+
+#if DUMP_PIVOT_TABLE
+void ScDPDataDimension::DumpState( const ScDPResultDimension* pRefDim, ScDocument* pDoc, ScAddress& rPos ) const
+{
+ OUString aDimName = bIsDataLayout ? OUString("(data layout)") : OUString("(unknown)");
+ dumpRow("ScDPDataDimension", aDimName, nullptr, pDoc, rPos);
+
+ SCROW nStartRow = rPos.Row();
+
+ tools::Long nCount = bIsDataLayout ? 1 : maMembers.size();
+ for (tools::Long i=0; i<nCount; i++)
+ {
+ const ScDPResultMember* pRefMember = pRefDim->GetMember(i);
+ const ScDPDataMember* pDataMember = maMembers[i].get();
+ pDataMember->DumpState( pRefMember, pDoc, rPos );
+ }
+
+ indent(pDoc, nStartRow, rPos);
+}
+
+void ScDPDataDimension::Dump(int nIndent) const
+{
+ std::string aIndent(nIndent*2, ' ');
+ std::cout << aIndent << "-- data dimension '"
+ << (pResultDimension ? pResultDimension->GetName() : OUString()) << "'" << std::endl;
+ for (auto& rxMember : maMembers)
+ rxMember->Dump(nIndent+1);
+}
+#endif
+
+tools::Long ScDPDataDimension::GetMemberCount() const
+{
+ return maMembers.size();
+}
+
+const ScDPDataMember* ScDPDataDimension::GetMember(tools::Long n) const
+{
+ return maMembers[n].get();
+}
+
+ScDPDataMember* ScDPDataDimension::GetMember(tools::Long n)
+{
+ return maMembers[n].get();
+}
+
+ScDPResultVisibilityData::ScDPResultVisibilityData(
+ ScDPSource* pSource) :
+ mpSource(pSource)
+{
+}
+
+ScDPResultVisibilityData::~ScDPResultVisibilityData()
+{
+}
+
+void ScDPResultVisibilityData::addVisibleMember(const OUString& rDimName, const ScDPItemData& rMemberItem)
+{
+ DimMemberType::iterator itr = maDimensions.find(rDimName);
+ if (itr == maDimensions.end())
+ {
+ pair<DimMemberType::iterator, bool> r = maDimensions.emplace(
+ rDimName, VisibleMemberType());
+
+ if (!r.second)
+ // insertion failed.
+ return;
+
+ itr = r.first;
+ }
+ VisibleMemberType& rMem = itr->second;
+ rMem.insert(rMemberItem);
+}
+
+void ScDPResultVisibilityData::fillFieldFilters(vector<ScDPFilteredCache::Criterion>& rFilters) const
+{
+ typedef std::unordered_map<OUString, tools::Long> FieldNameMapType;
+ FieldNameMapType aFieldNames;
+ ScDPTableData* pData = mpSource->GetData();
+ sal_Int32 nColumnCount = pData->GetColumnCount();
+ for (sal_Int32 i = 0; i < nColumnCount; ++i)
+ {
+ aFieldNames.emplace(pData->getDimensionName(i), i);
+ }
+
+ const ScDPDimensions* pDims = mpSource->GetDimensionsObject();
+ for (const auto& [rDimName, rMem] : maDimensions)
+ {
+ ScDPFilteredCache::Criterion aCri;
+ FieldNameMapType::const_iterator itrField = aFieldNames.find(rDimName);
+ if (itrField == aFieldNames.end())
+ // This should never happen!
+ continue;
+
+ tools::Long nDimIndex = itrField->second;
+ aCri.mnFieldIndex = static_cast<sal_Int32>(nDimIndex);
+ aCri.mpFilter = std::make_shared<ScDPFilteredCache::GroupFilter>();
+
+ ScDPFilteredCache::GroupFilter* pGrpFilter =
+ static_cast<ScDPFilteredCache::GroupFilter*>(aCri.mpFilter.get());
+
+ for (const ScDPItemData& rMemItem : rMem)
+ {
+ pGrpFilter->addMatchItem(rMemItem);
+ }
+
+ ScDPDimension* pDim = pDims->getByIndex(nDimIndex);
+ ScDPMembers* pMembers = pDim->GetHierarchiesObject()->getByIndex(0)->
+ GetLevelsObject()->getByIndex(0)->GetMembersObject();
+ if (pGrpFilter->getMatchItemCount() < o3tl::make_unsigned(pMembers->getCount()))
+ rFilters.push_back(aCri);
+ }
+}
+
+size_t ScDPResultVisibilityData::MemberHash::operator() (const ScDPItemData& r) const
+{
+ if (r.IsValue())
+ return static_cast<size_t>(::rtl::math::approxFloor(r.GetValue()));
+ else
+ return r.GetString().hashCode();
+}
+SCROW ScDPResultMember::GetDataId( ) const
+{
+ const ScDPMember* pMemberDesc = GetDPMember();
+ if (pMemberDesc)
+ return pMemberDesc->GetItemDataId();
+ return -1;
+}
+
+ScDPResultMember* ScDPResultDimension::AddMember(const ScDPParentDimData &aData )
+{
+ ScDPResultMember* pMember = new ScDPResultMember( pResultData, aData );
+ SCROW nDataIndex = pMember->GetDataId();
+ maMemberArray.emplace_back( pMember );
+
+ maMemberHash.emplace( nDataIndex, pMember );
+ return pMember;
+}
+
+ScDPResultMember* ScDPResultDimension::InsertMember(const ScDPParentDimData *pMemberData)
+{
+ SCROW nInsert = 0;
+ if ( !lcl_SearchMember( maMemberArray, pMemberData->mnOrder , nInsert ) )
+ {
+ ScDPResultMember* pNew = new ScDPResultMember( pResultData, *pMemberData );
+ maMemberArray.emplace( maMemberArray.begin()+nInsert, pNew );
+
+ SCROW nDataIndex = pMemberData->mpMemberDesc->GetItemDataId();
+ maMemberHash.emplace( nDataIndex, pNew );
+ return pNew;
+ }
+ return maMemberArray[ nInsert ].get();
+}
+
+void ScDPResultDimension::InitWithMembers(
+ LateInitParams& rParams, const std::vector<SCROW>& pItemData, size_t nPos,
+ ScDPInitState& rInitState)
+{
+ if ( rParams.IsEnd( nPos ) )
+ return;
+ ScDPDimension* pThisDim = rParams.GetDim( nPos );
+ ScDPLevel* pThisLevel = rParams.GetLevel( nPos );
+ SCROW nDataID = pItemData[nPos];
+
+ if (!(pThisDim && pThisLevel))
+ return;
+
+ tools::Long nDimSource = pThisDim->GetDimension(); //TODO: check GetSourceDim?
+
+ // create all members at the first call (preserve order)
+ ResultMembers& rMembers = pResultData->GetDimResultMembers(nDimSource, pThisDim, pThisLevel);
+ ScDPGroupCompare aCompare( pResultData, rInitState, nDimSource );
+ // initialize only specific member (or all if "show empty" flag is set)
+ ScDPResultMember* pResultMember = nullptr;
+ if ( bInitialized )
+ pResultMember = FindMember( nDataID );
+ else
+ bInitialized = true;
+
+ if ( pResultMember == nullptr )
+ { //only insert found item
+ const ScDPParentDimData* pMemberData = rMembers.FindMember( nDataID );
+ if ( pMemberData && aCompare.IsIncluded( *( pMemberData->mpMemberDesc ) ) )
+ pResultMember = InsertMember( pMemberData );
+ }
+ if ( pResultMember )
+ {
+ rInitState.AddMember( nDimSource, pResultMember->GetDataId() );
+ pResultMember->LateInitFrom(rParams, pItemData, nPos+1, rInitState);
+ rInitState.RemoveMember();
+ }
+}
+
+ScDPParentDimData::ScDPParentDimData() :
+ mnOrder(-1), mpParentDim(nullptr), mpParentLevel(nullptr), mpMemberDesc(nullptr) {}
+
+ScDPParentDimData::ScDPParentDimData(
+ SCROW nIndex, const ScDPDimension* pDim, const ScDPLevel* pLev, const ScDPMember* pMember) :
+ mnOrder(nIndex), mpParentDim(pDim), mpParentLevel(pLev), mpMemberDesc(pMember) {}
+
+const ScDPParentDimData* ResultMembers::FindMember( SCROW nIndex ) const
+{
+ auto aRes = maMemberHash.find( nIndex );
+ if( aRes != maMemberHash.end()) {
+ if ( aRes->second.mpMemberDesc && aRes->second.mpMemberDesc->GetItemDataId()==nIndex )
+ return &aRes->second;
+ }
+ return nullptr;
+}
+void ResultMembers::InsertMember( const ScDPParentDimData& rNew )
+{
+ if ( !rNew.mpMemberDesc->getShowDetails() )
+ mbHasHideDetailsMember = true;
+ maMemberHash.emplace( rNew.mpMemberDesc->GetItemDataId(), rNew );
+}
+
+ResultMembers::ResultMembers():
+ mbHasHideDetailsMember( false )
+{
+}
+ResultMembers::~ResultMembers()
+{
+}
+
+LateInitParams::LateInitParams(
+ const vector<ScDPDimension*>& ppDim, const vector<ScDPLevel*>& ppLev, bool bRow ) :
+ mppDim( ppDim ),
+ mppLev( ppLev ),
+ mbRow( bRow ),
+ mbInitChild( true ),
+ mbAllChildren( false )
+{
+}
+
+bool LateInitParams::IsEnd( size_t nPos ) const
+{
+ return nPos >= mppDim.size();
+}
+
+void ScDPResultDimension::CheckShowEmpty( bool bShow )
+{
+ tools::Long nCount = maMemberArray.size();
+
+ for (tools::Long i=0; i<nCount; i++)
+ {
+ ScDPResultMember* pMember = maMemberArray.at(i).get();
+ pMember->CheckShowEmpty(bShow);
+ }
+
+}
+
+void ScDPResultMember::CheckShowEmpty( bool bShow )
+{
+ if (bHasElements)
+ {
+ ScDPResultDimension* pChildDim = GetChildDimension();
+ if (pChildDim)
+ pChildDim->CheckShowEmpty();
+ }
+ else if (IsValid() && bInitialized)
+ {
+ bShow = bShow || (GetParentLevel() && GetParentLevel()->getShowEmpty());
+ if (bShow)
+ {
+ SetHasElements();
+ ScDPResultDimension* pChildDim = GetChildDimension();
+ if (pChildDim)
+ pChildDim->CheckShowEmpty(true);
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dptabsrc.cxx b/sc/source/core/data/dptabsrc.cxx
new file mode 100644
index 000000000..cab605f69
--- /dev/null
+++ b/sc/source/core/data/dptabsrc.cxx
@@ -0,0 +1,2613 @@
+/* -*- 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 <dptabsrc.hxx>
+
+#include <algorithm>
+#include <vector>
+
+#include <comphelper/sequence.hxx>
+#include <o3tl/any.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+#include <rtl/math.hxx>
+#include <sal/log.hxx>
+#include <svl/itemprop.hxx>
+#include <vcl/svapp.hxx>
+
+#include <dpcache.hxx>
+#include <dptabres.hxx>
+#include <dptabdat.hxx>
+#include <global.hxx>
+#include <miscuno.hxx>
+#include <unonames.hxx>
+#include <dpitemdata.hxx>
+#include <dputil.hxx>
+#include <dpresfilter.hxx>
+#include <calcmacros.hxx>
+#include <generalfunction.hxx>
+
+#include <com/sun/star/beans/PropertyAttribute.hpp>
+#include <com/sun/star/sheet/DataPilotFieldFilter.hpp>
+#include <com/sun/star/sheet/DataPilotFieldReferenceType.hpp>
+#include <com/sun/star/sheet/DataPilotFieldSortMode.hpp>
+#include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp>
+#include <com/sun/star/sheet/DataPilotFieldAutoShowInfo.hpp>
+#include <com/sun/star/sheet/GeneralFunction2.hpp>
+#include <com/sun/star/sheet/TableFilterField.hpp>
+
+#include <unotools/calendarwrapper.hxx>
+#include <com/sun/star/i18n/CalendarDisplayIndex.hpp>
+
+using namespace com::sun::star;
+using ::std::vector;
+using ::com::sun::star::uno::Sequence;
+using ::com::sun::star::uno::Any;
+using ::com::sun::star::sheet::DataPilotFieldAutoShowInfo;
+
+#define SC_MINCOUNT_LIMIT 1000000
+
+SC_SIMPLE_SERVICE_INFO( ScDPSource, "ScDPSource", "com.sun.star.sheet.DataPilotSource" )
+SC_SIMPLE_SERVICE_INFO( ScDPDimensions, "ScDPDimensions", "com.sun.star.sheet.DataPilotSourceDimensions" )
+SC_SIMPLE_SERVICE_INFO( ScDPDimension, "ScDPDimension", "com.sun.star.sheet.DataPilotSourceDimension" )
+
+// Typos are on purpose here, quote from Eike Rathke (see https://gerrit.libreoffice.org/c/core/+/101116):
+// "The typo is exactly why the SC_SIMPLE_SERVICE_INFO_COMPAT() lists both service names,
+// the old with the typo and the new corrected one. Correcting the typo in the old name
+// will make all extensions fail that use it. This is not to be changed."
+SC_SIMPLE_SERVICE_INFO_COMPAT( ScDPHierarchies, "ScDPHierarchies",
+ "com.sun.star.sheet.DataPilotSourceHierarchies", "com.sun.star.sheet.DataPilotSourceHierarcies" )
+SC_SIMPLE_SERVICE_INFO_COMPAT( ScDPHierarchy, "ScDPHierarchy",
+ "com.sun.star.sheet.DataPilotSourceHierarchy", "com.sun.star.sheet.DataPilotSourceHierarcy" )
+
+SC_SIMPLE_SERVICE_INFO( ScDPLevels, "ScDPLevels", "com.sun.star.sheet.DataPilotSourceLevels" )
+SC_SIMPLE_SERVICE_INFO( ScDPLevel, "ScDPLevel", "com.sun.star.sheet.DataPilotSourceLevel" )
+SC_SIMPLE_SERVICE_INFO( ScDPMembers, "ScDPMembers", "com.sun.star.sheet.DataPilotSourceMembers" )
+SC_SIMPLE_SERVICE_INFO( ScDPMember, "ScDPMember", "com.sun.star.sheet.DataPilotSourceMember" )
+
+// property maps for PropertySetInfo
+// DataDescription / NumberFormat are internal
+
+//TODO: move to a header?
+static bool lcl_GetBoolFromAny( const uno::Any& aAny )
+{
+ auto b = o3tl::tryAccess<bool>(aAny);
+ return b && *b;
+}
+
+ScDPSource::ScDPSource( ScDPTableData* pD ) :
+ pData( pD ),
+ bColumnGrand( true ), // default is true
+ bRowGrand( true ),
+ bIgnoreEmptyRows( false ),
+ bRepeatIfEmpty( false ),
+ nDupCount( 0 ),
+ bResultOverflow( false ),
+ bPageFiltered( false )
+{
+ pData->SetEmptyFlags( bIgnoreEmptyRows, bRepeatIfEmpty );
+}
+
+ScDPSource::~ScDPSource()
+{
+ // free lists
+
+ pColResults.reset();
+ pRowResults.reset();
+
+ pColResRoot.reset();
+ pRowResRoot.reset();
+ pResData.reset();
+}
+
+const std::optional<OUString> & ScDPSource::GetGrandTotalName() const
+{
+ return mpGrandTotalName;
+}
+
+sheet::DataPilotFieldOrientation ScDPSource::GetOrientation(sal_Int32 nColumn)
+{
+ if (std::find(maColDims.begin(), maColDims.end(), nColumn) != maColDims.end())
+ return sheet::DataPilotFieldOrientation_COLUMN;
+
+ if (std::find(maRowDims.begin(), maRowDims.end(), nColumn) != maRowDims.end())
+ return sheet::DataPilotFieldOrientation_ROW;
+
+ if (std::find(maDataDims.begin(), maDataDims.end(), nColumn) != maDataDims.end())
+ return sheet::DataPilotFieldOrientation_DATA;
+
+ if (std::find(maPageDims.begin(), maPageDims.end(), nColumn) != maPageDims.end())
+ return sheet::DataPilotFieldOrientation_PAGE;
+
+ return sheet::DataPilotFieldOrientation_HIDDEN;
+}
+
+sal_Int32 ScDPSource::GetDataDimensionCount() const
+{
+ return maDataDims.size();
+}
+
+ScDPDimension* ScDPSource::GetDataDimension(sal_Int32 nIndex)
+{
+ if (nIndex < 0 || o3tl::make_unsigned(nIndex) >= maDataDims.size())
+ return nullptr;
+
+ sal_Int32 nDimIndex = maDataDims[nIndex];
+ return GetDimensionsObject()->getByIndex(nDimIndex);
+}
+
+OUString ScDPSource::GetDataDimName(sal_Int32 nIndex)
+{
+ OUString aRet;
+ ScDPDimension* pDim = GetDataDimension(nIndex);
+ if (pDim)
+ aRet = pDim->getName();
+ return aRet;
+}
+
+sal_Int32 ScDPSource::GetPosition(sal_Int32 nColumn)
+{
+ std::vector<sal_Int32>::const_iterator it, itBeg = maColDims.begin(), itEnd = maColDims.end();
+ it = std::find(itBeg, itEnd, nColumn);
+ if (it != itEnd)
+ return std::distance(itBeg, it);
+
+ itBeg = maRowDims.begin();
+ itEnd = maRowDims.end();
+ it = std::find(itBeg, itEnd, nColumn);
+ if (it != itEnd)
+ return std::distance(itBeg, it);
+
+ itBeg = maDataDims.begin();
+ itEnd = maDataDims.end();
+ it = std::find(itBeg, itEnd, nColumn);
+ if (it != itEnd)
+ return std::distance(itBeg, it);
+
+ itBeg = maPageDims.begin();
+ itEnd = maPageDims.end();
+ it = std::find(itBeg, itEnd, nColumn);
+ if (it != itEnd)
+ return std::distance(itBeg, it);
+
+ return 0;
+}
+
+namespace {
+
+bool testSubTotal( bool& rAllowed, sal_Int32 nColumn, const std::vector<sal_Int32>& rDims, ScDPSource* pSource )
+{
+ rAllowed = true;
+ std::vector<sal_Int32>::const_iterator it = rDims.begin(), itEnd = rDims.end();
+ for (; it != itEnd; ++it)
+ {
+ if (*it != nColumn)
+ continue;
+
+ if ( pSource->IsDataLayoutDimension(nColumn) )
+ {
+ // no subtotals for data layout dim, no matter where
+ rAllowed = false;
+ return true;
+ }
+
+ // no subtotals if no other dim but data layout follows
+ ++it;
+ if (it != itEnd && pSource->IsDataLayoutDimension(*it))
+ ++it;
+ if (it == itEnd)
+ rAllowed = false;
+
+ return true; // found
+ }
+
+ return false;
+}
+
+void removeDim( sal_Int32 nRemove, std::vector<sal_Int32>& rDims )
+{
+ std::vector<sal_Int32>::iterator it = std::find(rDims.begin(), rDims.end(), nRemove);
+ if (it != rDims.end())
+ rDims.erase(it);
+}
+
+}
+
+bool ScDPSource::SubTotalAllowed(sal_Int32 nColumn)
+{
+ //TODO: cache this at ScDPResultData
+ bool bAllowed = true;
+ if ( testSubTotal(bAllowed, nColumn, maColDims, this) )
+ return bAllowed;
+ if ( testSubTotal(bAllowed, nColumn, maRowDims, this) )
+ return bAllowed;
+ return bAllowed;
+}
+
+void ScDPSource::SetOrientation(sal_Int32 nColumn, sheet::DataPilotFieldOrientation nNew)
+{
+ //TODO: change to no-op if new orientation is equal to old?
+
+ // remove from old list
+ removeDim(nColumn, maColDims);
+ removeDim(nColumn, maRowDims);
+ removeDim(nColumn, maDataDims);
+ removeDim(nColumn, maPageDims);
+
+ // add to new list
+ switch (nNew)
+ {
+ case sheet::DataPilotFieldOrientation_COLUMN:
+ maColDims.push_back(nColumn);
+ break;
+ case sheet::DataPilotFieldOrientation_ROW:
+ maRowDims.push_back(nColumn);
+ break;
+ case sheet::DataPilotFieldOrientation_DATA:
+ maDataDims.push_back(nColumn);
+ break;
+ case sheet::DataPilotFieldOrientation_PAGE:
+ maPageDims.push_back(nColumn);
+ break;
+ // DataPilot Migration - Cache&&Performance
+ case sheet::DataPilotFieldOrientation_HIDDEN:
+ break;
+ default:
+ OSL_FAIL( "ScDPSource::SetOrientation: unexpected orientation" );
+ break;
+ }
+}
+
+bool ScDPSource::IsDataLayoutDimension(sal_Int32 nDim)
+{
+ return nDim == pData->GetColumnCount();
+}
+
+sheet::DataPilotFieldOrientation ScDPSource::GetDataLayoutOrientation()
+{
+ return GetOrientation(pData->GetColumnCount());
+}
+
+bool ScDPSource::IsDateDimension(sal_Int32 nDim)
+{
+ return pData->IsDateDimension(nDim);
+}
+
+ScDPDimensions* ScDPSource::GetDimensionsObject()
+{
+ if (!pDimensions.is())
+ {
+ pDimensions = new ScDPDimensions(this);
+ }
+ return pDimensions.get();
+}
+
+uno::Reference<container::XNameAccess> SAL_CALL ScDPSource::getDimensions()
+{
+ return GetDimensionsObject();
+}
+
+void ScDPSource::SetDupCount( tools::Long nNew )
+{
+ nDupCount = nNew;
+}
+
+ScDPDimension* ScDPSource::AddDuplicated(std::u16string_view rNewName)
+{
+ OSL_ENSURE( pDimensions.is(), "AddDuplicated without dimensions?" );
+
+ // re-use
+
+ tools::Long nOldDimCount = pDimensions->getCount();
+ for (tools::Long i=0; i<nOldDimCount; i++)
+ {
+ ScDPDimension* pDim = pDimensions->getByIndex(i);
+ if (pDim && pDim->getName() == rNewName)
+ {
+ //TODO: test if pDim is a duplicate of source
+ return pDim;
+ }
+ }
+
+ SetDupCount( nDupCount + 1 );
+ pDimensions->CountChanged(); // uses nDupCount
+
+ return pDimensions->getByIndex( pDimensions->getCount() - 1 );
+}
+
+sal_Int32 ScDPSource::GetSourceDim(sal_Int32 nDim)
+{
+ // original source dimension or data layout dimension?
+ if ( nDim <= pData->GetColumnCount() )
+ return nDim;
+
+ if ( nDim < pDimensions->getCount() )
+ {
+ ScDPDimension* pDimObj = pDimensions->getByIndex( nDim );
+ if ( pDimObj )
+ {
+ tools::Long nSource = pDimObj->GetSourceDim();
+ if ( nSource >= 0 )
+ return nSource;
+ }
+ }
+
+ OSL_FAIL("GetSourceDim: wrong dim");
+ return nDim;
+}
+
+uno::Sequence< uno::Sequence<sheet::DataResult> > SAL_CALL ScDPSource::getResults()
+{
+ CreateRes_Impl(); // create pColResRoot and pRowResRoot
+
+ if ( bResultOverflow ) // set in CreateRes_Impl
+ {
+ // no results available
+ throw uno::RuntimeException();
+ }
+
+ sal_Int32 nColCount = pColResRoot->GetSize(pResData->GetColStartMeasure());
+ sal_Int32 nRowCount = pRowResRoot->GetSize(pResData->GetRowStartMeasure());
+
+ // allocate full sequence
+ //TODO: leave out empty rows???
+
+ uno::Sequence< uno::Sequence<sheet::DataResult> > aSeq( nRowCount );
+ uno::Sequence<sheet::DataResult>* pRowAry = aSeq.getArray();
+ for (sal_Int32 nRow = 0; nRow < nRowCount; nRow++)
+ {
+ uno::Sequence<sheet::DataResult> aColSeq( nColCount );
+ // use default values of DataResult
+ pRowAry[nRow] = aColSeq;
+ }
+
+ ScDPResultFilterContext aFilterCxt;
+ pRowResRoot->FillDataResults(
+ pColResRoot.get(), aFilterCxt, aSeq, pResData->GetRowStartMeasure());
+
+ maResFilterSet.swap(aFilterCxt.maFilterSet); // Keep this data for GETPIVOTDATA.
+
+ return aSeq;
+}
+
+uno::Sequence<double> ScDPSource::getFilteredResults(
+ const uno::Sequence<sheet::DataPilotFieldFilter>& aFilters )
+{
+ if (maResFilterSet.empty())
+ getResults(); // Build result tree first.
+
+ // Get result values from the tree.
+ const ScDPResultTree::ValuesType* pVals = maResFilterSet.getResults(aFilters);
+ if (pVals && !pVals->empty())
+ {
+ return comphelper::containerToSequence(*pVals);
+ }
+
+ if (aFilters.getLength() == 1)
+ {
+ // Try to get result from the leaf nodes.
+ double fVal = maResFilterSet.getLeafResult(aFilters[0]);
+ if (!std::isnan(fVal))
+ {
+ return { fVal };
+ }
+ }
+
+ return uno::Sequence<double>();
+}
+
+void SAL_CALL ScDPSource::refresh()
+{
+ disposeData();
+}
+
+void SAL_CALL ScDPSource::addRefreshListener( const uno::Reference<util::XRefreshListener >& )
+{
+ OSL_FAIL("not implemented"); //TODO: exception?
+}
+
+void SAL_CALL ScDPSource::removeRefreshListener( const uno::Reference<util::XRefreshListener >& )
+{
+ OSL_FAIL("not implemented"); //TODO: exception?
+}
+
+Sequence< Sequence<Any> > SAL_CALL ScDPSource::getDrillDownData(const Sequence<sheet::DataPilotFieldFilter>& aFilters)
+{
+ sal_Int32 nColumnCount = GetData()->GetColumnCount();
+
+ vector<ScDPFilteredCache::Criterion> aFilterCriteria;
+ for (const sheet::DataPilotFieldFilter& rFilter : aFilters)
+ {
+ const OUString& aFieldName = rFilter.FieldName;
+ for (sal_Int32 nCol = 0; nCol < nColumnCount; ++nCol)
+ {
+ if (aFieldName == pData->getDimensionName(nCol))
+ {
+ ScDPDimension* pDim = GetDimensionsObject()->getByIndex( nCol );
+ ScDPMembers* pMembers = pDim->GetHierarchiesObject()->getByIndex(0)->
+ GetLevelsObject()->getByIndex(0)->GetMembersObject();
+ sal_Int32 nIndex = pMembers->GetIndexFromName( rFilter.MatchValueName );
+ if ( nIndex >= 0 )
+ {
+ ScDPItemData aItem(pMembers->getByIndex(nIndex)->FillItemData());
+ aFilterCriteria.emplace_back( );
+ aFilterCriteria.back().mnFieldIndex = nCol;
+ aFilterCriteria.back().mpFilter =
+ std::make_shared<ScDPFilteredCache::SingleFilter>(aItem);
+ }
+ }
+ }
+ }
+
+ // Take into account the visibilities of field members.
+ ScDPResultVisibilityData aResVisData(this);
+ pRowResRoot->FillVisibilityData(aResVisData);
+ pColResRoot->FillVisibilityData(aResVisData);
+ aResVisData.fillFieldFilters(aFilterCriteria);
+
+ Sequence< Sequence<Any> > aTabData;
+ std::unordered_set<sal_Int32> aCatDims;
+ GetCategoryDimensionIndices(aCatDims);
+ pData->GetDrillDownData(std::move(aFilterCriteria), std::move(aCatDims), aTabData);
+ return aTabData;
+}
+
+OUString ScDPSource::getDataDescription()
+{
+ CreateRes_Impl(); // create pResData
+
+ OUString aRet;
+ if ( pResData->GetMeasureCount() == 1 )
+ {
+ bool bTotalResult = false;
+ aRet = pResData->GetMeasureString(0, true, SUBTOTAL_FUNC_NONE, bTotalResult);
+ }
+
+ // empty for more than one measure
+
+ return aRet;
+}
+
+void ScDPSource::setIgnoreEmptyRows(bool bSet)
+{
+ bIgnoreEmptyRows = bSet;
+ pData->SetEmptyFlags( bIgnoreEmptyRows, bRepeatIfEmpty );
+}
+
+void ScDPSource::setRepeatIfEmpty(bool bSet)
+{
+ bRepeatIfEmpty = bSet;
+ pData->SetEmptyFlags( bIgnoreEmptyRows, bRepeatIfEmpty );
+}
+
+void ScDPSource::disposeData()
+{
+ maResFilterSet.clear();
+
+ if ( pResData )
+ {
+ // reset all data...
+
+ pColResRoot.reset();
+ pRowResRoot.reset();
+ pResData.reset();
+ pColResults.reset();
+ pRowResults.reset();
+ aColLevelList.clear();
+ aRowLevelList.clear();
+ }
+
+ pDimensions.clear(); // settings have to be applied (from SaveData) again!
+ SetDupCount( 0 );
+
+ maColDims.clear();
+ maRowDims.clear();
+ maDataDims.clear();
+ maPageDims.clear();
+
+ pData->DisposeData(); // cached entries etc.
+ bPageFiltered = false;
+ bResultOverflow = false;
+}
+
+static tools::Long lcl_CountMinMembers(const vector<ScDPDimension*>& ppDim, const vector<ScDPLevel*>& ppLevel, tools::Long nLevels )
+{
+ // Calculate the product of the member count for those consecutive levels that
+ // have the "show all" flag, one following level, and the data layout dimension.
+
+ tools::Long nTotal = 1;
+ tools::Long nDataCount = 1;
+ bool bWasShowAll = true;
+ tools::Long nPos = nLevels;
+ while ( nPos > 0 )
+ {
+ --nPos;
+
+ if ( nPos+1 < nLevels && ppDim[nPos] == ppDim[nPos+1] )
+ {
+ OSL_FAIL("lcl_CountMinMembers: multiple levels from one dimension not implemented");
+ return 0;
+ }
+
+ bool bDo = false;
+ if ( ppDim[nPos]->getIsDataLayoutDimension() )
+ {
+ // data layout dim doesn't interfere with "show all" flags
+ nDataCount = ppLevel[nPos]->GetMembersObject()->getCount();
+ if ( nDataCount == 0 )
+ nDataCount = 1;
+ }
+ else if ( bWasShowAll ) // "show all" set for all following levels?
+ {
+ bDo = true;
+ if ( !ppLevel[nPos]->getShowEmpty() )
+ {
+ // this level is counted, following ones are not
+ bWasShowAll = false;
+ }
+ }
+ if ( bDo )
+ {
+ tools::Long nThisCount = ppLevel[nPos]->GetMembersObject()->getMinMembers();
+ if ( nThisCount == 0 )
+ {
+ nTotal = 1; // empty level -> start counting from here
+ //TODO: start with visible elements in this level?
+ }
+ else
+ {
+ if ( nTotal >= LONG_MAX / nThisCount )
+ return LONG_MAX; // overflow
+ nTotal *= nThisCount;
+ }
+ }
+ }
+
+ // always include data layout dim, even after restarting
+ if ( nTotal >= LONG_MAX / nDataCount )
+ return LONG_MAX; // overflow
+ nTotal *= nDataCount;
+
+ return nTotal;
+}
+
+void ScDPSource::FillCalcInfo(bool bIsRow, ScDPTableData::CalcInfo& rInfo, bool &rHasAutoShow)
+{
+ const std::vector<sal_Int32>& rDims = bIsRow ? maRowDims : maColDims;
+ for (const auto& rDimIndex : rDims)
+ {
+ ScDPDimension* pDim = GetDimensionsObject()->getByIndex(rDimIndex);
+ tools::Long nHierarchy = ScDPDimension::getUsedHierarchy();
+ if ( nHierarchy >= ScDPHierarchies::getCount() )
+ nHierarchy = 0;
+ ScDPLevels* pLevels = pDim->GetHierarchiesObject()->getByIndex(nHierarchy)->GetLevelsObject();
+ sal_Int32 nCount = pLevels->getCount();
+
+ //TODO: Test
+ if (pDim->getIsDataLayoutDimension() && maDataDims.size() < 2)
+ nCount = 0;
+ //TODO: Test
+
+ for (sal_Int32 j = 0; j < nCount; ++j)
+ {
+ ScDPLevel* pLevel = pLevels->getByIndex(j);
+ pLevel->EvaluateSortOrder();
+
+ // no layout flags for column fields, only for row fields
+ pLevel->SetEnableLayout( bIsRow );
+
+ if ( pLevel->GetAutoShow().IsEnabled )
+ rHasAutoShow = true;
+
+ if (bIsRow)
+ {
+ rInfo.aRowLevelDims.push_back(rDimIndex);
+ rInfo.aRowDims.push_back(pDim);
+ rInfo.aRowLevels.push_back(pLevel);
+ }
+ else
+ {
+ rInfo.aColLevelDims.push_back(rDimIndex);
+ rInfo.aColDims.push_back(pDim);
+ rInfo.aColLevels.push_back(pLevel);
+ }
+
+ pLevel->GetMembersObject(); // initialize for groups
+ }
+ }
+}
+
+namespace {
+
+class CategoryDimInserter
+{
+ ScDPSource& mrSource;
+ std::unordered_set<sal_Int32>& mrCatDims;
+public:
+ CategoryDimInserter(ScDPSource& rSource, std::unordered_set<sal_Int32>& rCatDims) :
+ mrSource(rSource),
+ mrCatDims(rCatDims) {}
+
+ void operator() (tools::Long nDim)
+ {
+ if (!mrSource.IsDataLayoutDimension(nDim))
+ mrCatDims.insert(nDim);
+ }
+};
+
+}
+
+void ScDPSource::GetCategoryDimensionIndices(std::unordered_set<sal_Int32>& rCatDims)
+{
+ std::unordered_set<sal_Int32> aCatDims;
+
+ CategoryDimInserter aInserter(*this, aCatDims);
+ std::for_each(maColDims.begin(), maColDims.end(), aInserter);
+ std::for_each(maRowDims.begin(), maRowDims.end(), aInserter);
+ std::for_each(maPageDims.begin(), maPageDims.end(), aInserter);
+
+ rCatDims.swap(aCatDims);
+}
+
+void ScDPSource::FilterCacheByPageDimensions()
+{
+ // #i117661# Repeated calls to ScDPFilteredCache::filterByPageDimension
+ // are invalid because rows are only hidden, never shown again. If
+ // FilterCacheByPageDimensions is called again, the cache table must
+ // be re-initialized. Currently, CreateRes_Impl always uses a fresh cache
+ // because ScDBDocFunc::DataPilotUpdate calls InvalidateData.
+
+ if (bPageFiltered)
+ {
+ SAL_WARN( "sc.core","tried to apply page field filters several times");
+
+ pData->DisposeData();
+ pData->CreateCacheTable(); // re-initialize the cache table
+ bPageFiltered = false;
+ }
+
+ // filter table by page dimensions.
+ vector<ScDPFilteredCache::Criterion> aCriteria;
+ for (const auto& rDimIndex : maPageDims)
+ {
+ ScDPDimension* pDim = GetDimensionsObject()->getByIndex(rDimIndex);
+ tools::Long nField = pDim->GetDimension();
+
+ ScDPMembers* pMems = pDim->GetHierarchiesObject()->getByIndex(0)->
+ GetLevelsObject()->getByIndex(0)->GetMembersObject();
+
+ tools::Long nMemCount = pMems->getCount();
+ ScDPFilteredCache::Criterion aFilter;
+ aFilter.mnFieldIndex = static_cast<sal_Int32>(nField);
+ aFilter.mpFilter = std::make_shared<ScDPFilteredCache::GroupFilter>();
+ ScDPFilteredCache::GroupFilter* pGrpFilter =
+ static_cast<ScDPFilteredCache::GroupFilter*>(aFilter.mpFilter.get());
+ for (tools::Long j = 0; j < nMemCount; ++j)
+ {
+ ScDPMember* pMem = pMems->getByIndex(j);
+ if (pMem->isVisible())
+ {
+ ScDPItemData aData(pMem->FillItemData());
+ pGrpFilter->addMatchItem(aData);
+ }
+ }
+ if (pGrpFilter->getMatchItemCount() < o3tl::make_unsigned(nMemCount))
+ // there is at least one invisible item. Add this filter criterion to the mix.
+ aCriteria.push_back(aFilter);
+
+ if (!pDim->HasSelectedPage())
+ continue;
+
+ const ScDPItemData& rData = pDim->GetSelectedData();
+ aCriteria.emplace_back();
+ ScDPFilteredCache::Criterion& r = aCriteria.back();
+ r.mnFieldIndex = static_cast<sal_Int32>(nField);
+ r.mpFilter = std::make_shared<ScDPFilteredCache::SingleFilter>(rData);
+ }
+ if (!aCriteria.empty())
+ {
+ std::unordered_set<sal_Int32> aCatDims;
+ GetCategoryDimensionIndices(aCatDims);
+ pData->FilterCacheTable(std::move(aCriteria), std::move(aCatDims));
+ bPageFiltered = true;
+ }
+}
+
+void ScDPSource::CreateRes_Impl()
+{
+ if (pResData)
+ return;
+
+ sheet::DataPilotFieldOrientation nDataOrient = GetDataLayoutOrientation();
+ if (maDataDims.size() > 1 && ( nDataOrient != sheet::DataPilotFieldOrientation_COLUMN &&
+ nDataOrient != sheet::DataPilotFieldOrientation_ROW ) )
+ {
+ // if more than one data dimension, data layout orientation must be set
+ SetOrientation( pData->GetColumnCount(), sheet::DataPilotFieldOrientation_ROW );
+ nDataOrient = sheet::DataPilotFieldOrientation_ROW;
+ }
+
+ // TODO: Aggregate pDataNames, pDataRefValues, nDataRefOrient, and
+ // eDataFunctions into a structure and use vector instead of static
+ // or pointer arrays.
+ vector<OUString> aDataNames;
+ vector<sheet::DataPilotFieldReference> aDataRefValues;
+ vector<ScSubTotalFunc> aDataFunctions;
+ vector<sheet::DataPilotFieldOrientation> aDataRefOrient;
+
+ ScDPTableData::CalcInfo aInfo;
+
+ // LateInit (initialize only those rows/children that are used) can be used unless
+ // any data dimension needs reference values from column/row dimensions
+ bool bLateInit = true;
+
+ // Go through all data dimensions (i.e. fields) and build their meta data
+ // so that they can be passed on to ScDPResultData instance later.
+ // TODO: aggregate all of data dimension info into a structure.
+ for (const tools::Long nDimIndex : maDataDims)
+ {
+ // Get function for each data field.
+ ScDPDimension* pDim = GetDimensionsObject()->getByIndex(nDimIndex);
+ ScGeneralFunction eUser = pDim->getFunction();
+ if (eUser == ScGeneralFunction::AUTO)
+ {
+ //TODO: test for numeric data
+ eUser = ScGeneralFunction::SUM;
+ }
+
+ // Map UNO's enum to internal enum ScSubTotalFunc.
+ aDataFunctions.push_back(ScDPUtil::toSubTotalFunc(eUser));
+
+ // Get reference field/item information.
+ aDataRefValues.push_back(pDim->GetReferenceValue());
+ sheet::DataPilotFieldOrientation nDataRefOrient = sheet::DataPilotFieldOrientation_HIDDEN; // default if not used
+ sal_Int32 eRefType = aDataRefValues.back().ReferenceType;
+ if ( eRefType == sheet::DataPilotFieldReferenceType::ITEM_DIFFERENCE ||
+ eRefType == sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE ||
+ eRefType == sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE_DIFFERENCE ||
+ eRefType == sheet::DataPilotFieldReferenceType::RUNNING_TOTAL )
+ {
+ sal_Int32 nColumn = comphelper::findValue(
+ GetDimensionsObject()->getElementNames(), aDataRefValues.back().ReferenceField);
+ if ( nColumn >= 0 )
+ {
+ nDataRefOrient = GetOrientation(nColumn);
+ // need fully initialized results to find reference values
+ // (both in column or row dimensions), so updated values or
+ // differences to 0 can be displayed even for empty results.
+ bLateInit = false;
+ }
+ }
+
+ aDataRefOrient.push_back(nDataRefOrient);
+
+ aDataNames.push_back(pDim->getName());
+
+ //TODO: modify user visible strings as in ScDPResultData::GetMeasureString instead!
+
+ aDataNames.back() = ScDPUtil::getSourceDimensionName(aDataNames.back());
+
+ //TODO: if the name is overridden by user, a flag must be set
+ //TODO: so the user defined name replaces the function string and field name.
+
+ //TODO: the complete name (function and field) must be stored at the dimension
+
+ tools::Long nSource = pDim->GetSourceDim();
+ if (nSource >= 0)
+ aInfo.aDataSrcCols.push_back(nSource);
+ else
+ aInfo.aDataSrcCols.push_back(nDimIndex);
+ }
+
+ pResData.reset( new ScDPResultData(*this) );
+ pResData->SetMeasureData(aDataFunctions, aDataRefValues, aDataRefOrient, aDataNames);
+ pResData->SetDataLayoutOrientation(nDataOrient);
+ pResData->SetLateInit( bLateInit );
+
+ bool bHasAutoShow = false;
+
+ ScDPInitState aInitState;
+
+ // Page field selections restrict the members shown in related fields
+ // (both in column and row fields). aInitState is filled with the page
+ // field selections, they are kept across the data iterator loop.
+
+ for (const auto& rDimIndex : maPageDims)
+ {
+ ScDPDimension* pDim = GetDimensionsObject()->getByIndex(rDimIndex);
+ if ( pDim->HasSelectedPage() )
+ aInitState.AddMember(rDimIndex, GetCache()->GetIdByItemData(rDimIndex, pDim->GetSelectedData()));
+ }
+
+ // Show grand total columns only when the option is set *and* there is at
+ // least one column field. Same for the grand total rows.
+ sheet::DataPilotFieldOrientation nDataLayoutOrient = GetDataLayoutOrientation();
+ tools::Long nColDimCount2 = maColDims.size() - (nDataLayoutOrient == sheet::DataPilotFieldOrientation_COLUMN ? 1 : 0);
+ tools::Long nRowDimCount2 = maRowDims.size() - (nDataLayoutOrient == sheet::DataPilotFieldOrientation_ROW ? 1 : 0);
+ bool bShowColGrand = bColumnGrand && nColDimCount2 > 0;
+ bool bShowRowGrand = bRowGrand && nRowDimCount2 > 0;
+ pColResRoot.reset( new ScDPResultMember(pResData.get(), bShowColGrand) );
+ pRowResRoot.reset( new ScDPResultMember(pResData.get(), bShowRowGrand) );
+
+ FillCalcInfo(false, aInfo, bHasAutoShow);
+ tools::Long nColLevelCount = aInfo.aColLevels.size();
+
+ pColResRoot->InitFrom( aInfo.aColDims, aInfo.aColLevels, 0, aInitState );
+ pColResRoot->SetHasElements();
+
+ FillCalcInfo(true, aInfo, bHasAutoShow);
+ tools::Long nRowLevelCount = aInfo.aRowLevels.size();
+
+ if ( nRowLevelCount > 0 )
+ {
+ // disable layout flags for the innermost row field (level)
+ aInfo.aRowLevels[nRowLevelCount-1]->SetEnableLayout( false );
+ }
+
+ pRowResRoot->InitFrom( aInfo.aRowDims, aInfo.aRowLevels, 0, aInitState );
+ pRowResRoot->SetHasElements();
+
+ // initialize members object also for all page dimensions (needed for numeric groups)
+ for (const auto& rDimIndex : maPageDims)
+ {
+ ScDPDimension* pDim = GetDimensionsObject()->getByIndex(rDimIndex);
+ tools::Long nHierarchy = ScDPDimension::getUsedHierarchy();
+ if ( nHierarchy >= ScDPHierarchies::getCount() )
+ nHierarchy = 0;
+
+ ScDPLevels* pLevels = pDim->GetHierarchiesObject()->getByIndex(nHierarchy)->GetLevelsObject();
+ tools::Long nCount = pLevels->getCount();
+ for (tools::Long j=0; j<nCount; j++)
+ pLevels->getByIndex(j)->GetMembersObject(); // initialize for groups
+ }
+
+ // pre-check: calculate minimum number of result columns / rows from
+ // levels that have the "show all" flag set
+
+ tools::Long nMinColMembers = lcl_CountMinMembers( aInfo.aColDims, aInfo.aColLevels, nColLevelCount );
+ tools::Long nMinRowMembers = lcl_CountMinMembers( aInfo.aRowDims, aInfo.aRowLevels, nRowLevelCount );
+
+ if ( nMinColMembers > MAXCOLCOUNT/*SC_MINCOUNT_LIMIT*/ || nMinRowMembers > SC_MINCOUNT_LIMIT )
+ {
+ // resulting table is too big -> abort before calculating
+ // (this relies on late init, so no members are allocated in InitFrom above)
+
+ bResultOverflow = true;
+ return;
+ }
+
+ FilterCacheByPageDimensions();
+
+ aInfo.aPageDims = maPageDims;
+ aInfo.pInitState = &aInitState;
+ aInfo.pColRoot = pColResRoot.get();
+ aInfo.pRowRoot = pRowResRoot.get();
+ pData->CalcResults(aInfo, false);
+
+ pColResRoot->CheckShowEmpty();
+ pRowResRoot->CheckShowEmpty();
+
+ // With all data processed, calculate the final results:
+
+ // UpdateDataResults calculates all original results from the collected values,
+ // and stores them as reference values if needed.
+ pRowResRoot->UpdateDataResults( pColResRoot.get(), pResData->GetRowStartMeasure() );
+
+ if ( bHasAutoShow ) // do the double calculation only if AutoShow is used
+ {
+ // Find the desired members and set bAutoHidden flag for the others
+ pRowResRoot->DoAutoShow( pColResRoot.get() );
+
+ // Reset all results to empty, so they can be built again with data for the
+ // desired members only.
+ pColResRoot->ResetResults();
+ pRowResRoot->ResetResults();
+ pData->CalcResults(aInfo, true);
+
+ // Call UpdateDataResults again, with the new (limited) values.
+ pRowResRoot->UpdateDataResults( pColResRoot.get(), pResData->GetRowStartMeasure() );
+ }
+
+ // SortMembers does the sorting by a result dimension, using the original results,
+ // but not running totals etc.
+ pRowResRoot->SortMembers( pColResRoot.get() );
+
+ // UpdateRunningTotals calculates running totals along column/row dimensions,
+ // differences from other members (named or relative), and column/row percentages
+ // or index values.
+ // Running totals and relative differences need to be done using the sorted values.
+ // Column/row percentages and index values must be done after sorting, because the
+ // results may no longer be in the right order (row total for percentage of row is
+ // always 1).
+ ScDPRunningTotalState aRunning( pColResRoot.get(), pRowResRoot.get() );
+ ScDPRowTotals aTotals;
+ pRowResRoot->UpdateRunningTotals( pColResRoot.get(), pResData->GetRowStartMeasure(), aRunning, aTotals );
+
+#if DUMP_PIVOT_TABLE
+ DumpResults();
+#endif
+}
+
+void ScDPSource::FillLevelList( sheet::DataPilotFieldOrientation nOrientation, std::vector<ScDPLevel*> &rList )
+{
+ rList.clear();
+
+ std::vector<sal_Int32>* pDimIndex = nullptr;
+ switch (nOrientation)
+ {
+ case sheet::DataPilotFieldOrientation_COLUMN:
+ pDimIndex = &maColDims;
+ break;
+ case sheet::DataPilotFieldOrientation_ROW:
+ pDimIndex = &maRowDims;
+ break;
+ case sheet::DataPilotFieldOrientation_DATA:
+ pDimIndex = &maDataDims;
+ break;
+ case sheet::DataPilotFieldOrientation_PAGE:
+ pDimIndex = &maPageDims;
+ break;
+ default:
+ OSL_FAIL( "ScDPSource::FillLevelList: unexpected orientation" );
+ break;
+ }
+ if (!pDimIndex)
+ {
+ OSL_FAIL("invalid orientation");
+ return;
+ }
+
+ ScDPDimensions* pDims = GetDimensionsObject();
+ for (const auto& rIndex : *pDimIndex)
+ {
+ ScDPDimension* pDim = pDims->getByIndex(rIndex);
+ OSL_ENSURE( pDim->getOrientation() == nOrientation, "orientations are wrong" );
+
+ ScDPHierarchies* pHiers = pDim->GetHierarchiesObject();
+ sal_Int32 nHierarchy = ScDPDimension::getUsedHierarchy();
+ if ( nHierarchy >= ScDPHierarchies::getCount() )
+ nHierarchy = 0;
+ ScDPHierarchy* pHier = pHiers->getByIndex(nHierarchy);
+ ScDPLevels* pLevels = pHier->GetLevelsObject();
+ sal_Int32 nLevCount = pLevels->getCount();
+ for (sal_Int32 nLev=0; nLev<nLevCount; nLev++)
+ {
+ ScDPLevel* pLevel = pLevels->getByIndex(nLev);
+ rList.push_back(pLevel);
+ }
+ }
+}
+
+void ScDPSource::FillMemberResults()
+{
+ if ( pColResults || pRowResults )
+ return;
+
+ CreateRes_Impl();
+
+ if ( bResultOverflow ) // set in CreateRes_Impl
+ {
+ // no results available -> abort (leave empty)
+ // exception is thrown in ScDPSource::getResults
+ return;
+ }
+
+ FillLevelList( sheet::DataPilotFieldOrientation_COLUMN, aColLevelList );
+ sal_Int32 nColLevelCount = aColLevelList.size();
+ if (nColLevelCount)
+ {
+ tools::Long nColDimSize = pColResRoot->GetSize(pResData->GetColStartMeasure());
+ pColResults.reset(new uno::Sequence<sheet::MemberResult>[nColLevelCount]);
+ for (tools::Long i=0; i<nColLevelCount; i++)
+ pColResults[i].realloc(nColDimSize);
+
+ tools::Long nPos = 0;
+ pColResRoot->FillMemberResults( pColResults.get(), nPos, pResData->GetColStartMeasure(),
+ true, nullptr, nullptr );
+ }
+
+ FillLevelList( sheet::DataPilotFieldOrientation_ROW, aRowLevelList );
+ tools::Long nRowLevelCount = aRowLevelList.size();
+ if (nRowLevelCount)
+ {
+ tools::Long nRowDimSize = pRowResRoot->GetSize(pResData->GetRowStartMeasure());
+ pRowResults.reset( new uno::Sequence<sheet::MemberResult>[nRowLevelCount] );
+ for (tools::Long i=0; i<nRowLevelCount; i++)
+ pRowResults[i].realloc(nRowDimSize);
+
+ tools::Long nPos = 0;
+ pRowResRoot->FillMemberResults( pRowResults.get(), nPos, pResData->GetRowStartMeasure(),
+ true, nullptr, nullptr );
+ }
+}
+
+const uno::Sequence<sheet::MemberResult>* ScDPSource::GetMemberResults( const ScDPLevel* pLevel )
+{
+ FillMemberResults();
+
+ sal_Int32 i = 0;
+ sal_Int32 nColCount = aColLevelList.size();
+ for (i=0; i<nColCount; i++)
+ {
+ ScDPLevel* pColLevel = aColLevelList[i];
+ if ( pColLevel == pLevel )
+ return &pColResults[i];
+ }
+ sal_Int32 nRowCount = aRowLevelList.size();
+ for (i=0; i<nRowCount; i++)
+ {
+ ScDPLevel* pRowLevel = aRowLevelList[i];
+ if ( pRowLevel == pLevel )
+ return &pRowResults[i];
+ }
+ return nullptr;
+}
+
+// XPropertySet
+
+uno::Reference<beans::XPropertySetInfo> SAL_CALL ScDPSource::getPropertySetInfo()
+{
+ using beans::PropertyAttribute::READONLY;
+
+ static const SfxItemPropertyMapEntry aDPSourceMap_Impl[] =
+ {
+ { SC_UNO_DP_COLGRAND, 0, cppu::UnoType<bool>::get(), 0, 0 },
+ { SC_UNO_DP_DATADESC, 0, cppu::UnoType<OUString>::get(), beans::PropertyAttribute::READONLY, 0 },
+ { SC_UNO_DP_IGNOREEMPTY, 0, cppu::UnoType<bool>::get(), 0, 0 }, // for sheet data only
+ { SC_UNO_DP_REPEATEMPTY, 0, cppu::UnoType<bool>::get(), 0, 0 }, // for sheet data only
+ { SC_UNO_DP_ROWGRAND, 0, cppu::UnoType<bool>::get(), 0, 0 },
+ { SC_UNO_DP_ROWFIELDCOUNT, 0, cppu::UnoType<sal_Int32>::get(), READONLY, 0 },
+ { SC_UNO_DP_COLUMNFIELDCOUNT, 0, cppu::UnoType<sal_Int32>::get(), READONLY, 0 },
+ { SC_UNO_DP_DATAFIELDCOUNT, 0, cppu::UnoType<sal_Int32>::get(), READONLY, 0 },
+ { SC_UNO_DP_GRANDTOTAL_NAME, 0, cppu::UnoType<OUString>::get(), 0, 0 },
+ { u"", 0, css::uno::Type(), 0, 0 }
+ };
+ static uno::Reference<beans::XPropertySetInfo> aRef =
+ new SfxItemPropertySetInfo( aDPSourceMap_Impl );
+ return aRef;
+}
+
+void SAL_CALL ScDPSource::setPropertyValue( const OUString& aPropertyName, const uno::Any& aValue )
+{
+ if (aPropertyName == SC_UNO_DP_COLGRAND)
+ bColumnGrand = lcl_GetBoolFromAny(aValue);
+ else if (aPropertyName == SC_UNO_DP_ROWGRAND)
+ bRowGrand = lcl_GetBoolFromAny(aValue);
+ else if (aPropertyName == SC_UNO_DP_IGNOREEMPTY)
+ setIgnoreEmptyRows( lcl_GetBoolFromAny( aValue ) );
+ else if (aPropertyName == SC_UNO_DP_REPEATEMPTY)
+ setRepeatIfEmpty( lcl_GetBoolFromAny( aValue ) );
+ else if (aPropertyName == SC_UNO_DP_GRANDTOTAL_NAME)
+ {
+ OUString aName;
+ if (aValue >>= aName)
+ mpGrandTotalName = aName;
+ }
+ else
+ {
+ OSL_FAIL("unknown property");
+ //TODO: THROW( UnknownPropertyException() );
+ }
+}
+
+uno::Any SAL_CALL ScDPSource::getPropertyValue( const OUString& aPropertyName )
+{
+ uno::Any aRet;
+ if ( aPropertyName == SC_UNO_DP_COLGRAND )
+ aRet <<= bColumnGrand;
+ else if ( aPropertyName == SC_UNO_DP_ROWGRAND )
+ aRet <<= bRowGrand;
+ else if ( aPropertyName == SC_UNO_DP_IGNOREEMPTY )
+ aRet <<= bIgnoreEmptyRows;
+ else if ( aPropertyName == SC_UNO_DP_REPEATEMPTY )
+ aRet <<= bRepeatIfEmpty;
+ else if ( aPropertyName == SC_UNO_DP_DATADESC ) // read-only
+ aRet <<= getDataDescription();
+ else if ( aPropertyName == SC_UNO_DP_ROWFIELDCOUNT ) // read-only
+ aRet <<= static_cast<sal_Int32>(maRowDims.size());
+ else if ( aPropertyName == SC_UNO_DP_COLUMNFIELDCOUNT ) // read-only
+ aRet <<= static_cast<sal_Int32>(maColDims.size());
+ else if ( aPropertyName == SC_UNO_DP_DATAFIELDCOUNT ) // read-only
+ aRet <<= static_cast<sal_Int32>(maDataDims.size());
+ else if (aPropertyName == SC_UNO_DP_GRANDTOTAL_NAME)
+ {
+ if (mpGrandTotalName)
+ aRet <<= *mpGrandTotalName;
+ }
+ else
+ {
+ OSL_FAIL("unknown property");
+ //TODO: THROW( UnknownPropertyException() );
+ }
+ return aRet;
+}
+
+#if DUMP_PIVOT_TABLE
+void ScDPSource::DumpResults() const
+{
+ std::cout << "+++++ column root" << std::endl;
+ pColResRoot->Dump(1);
+ std::cout << "+++++ row root" << std::endl;
+ pRowResRoot->Dump(1);
+}
+#endif
+
+SC_IMPL_DUMMY_PROPERTY_LISTENER( ScDPSource )
+
+ScDPDimensions::ScDPDimensions( ScDPSource* pSrc ) :
+ pSource( pSrc )
+{
+ //TODO: hold pSource
+
+ // include data layout dimension and duplicated dimensions
+ nDimCount = pSource->GetData()->GetColumnCount() + 1 + pSource->GetDupCount();
+}
+
+ScDPDimensions::~ScDPDimensions()
+{
+ //TODO: release pSource
+}
+
+void ScDPDimensions::CountChanged()
+{
+ // include data layout dimension and duplicated dimensions
+ sal_Int32 nNewCount = pSource->GetData()->GetColumnCount() + 1 + pSource->GetDupCount();
+ if ( ppDims )
+ {
+ sal_Int32 i;
+ sal_Int32 nCopy = std::min( nNewCount, nDimCount );
+ rtl::Reference<ScDPDimension>* ppNew = new rtl::Reference<ScDPDimension>[nNewCount];
+
+ for (i=0; i<nCopy; i++) // copy existing dims
+ ppNew[i] = ppDims[i];
+ for (i=nCopy; i<nNewCount; i++) // clear additional pointers
+ ppNew[i] = nullptr;
+
+ ppDims.reset( ppNew );
+ }
+ nDimCount = nNewCount;
+}
+
+// very simple XNameAccess implementation using getCount/getByIndex
+
+uno::Any SAL_CALL ScDPDimensions::getByName( const OUString& aName )
+{
+ sal_Int32 nCount = getCount();
+ for (sal_Int32 i=0; i<nCount; i++)
+ if ( getByIndex(i)->getName() == aName )
+ {
+ uno::Reference<container::XNamed> xNamed = getByIndex(i);
+ uno::Any aRet;
+ aRet <<= xNamed;
+ return aRet;
+ }
+
+ throw container::NoSuchElementException();
+// return uno::Any();
+}
+
+uno::Sequence<OUString> SAL_CALL ScDPDimensions::getElementNames()
+{
+ tools::Long nCount = getCount();
+ uno::Sequence<OUString> aSeq(nCount);
+ OUString* pArr = aSeq.getArray();
+ for (tools::Long i=0; i<nCount; i++)
+ pArr[i] = getByIndex(i)->getName();
+ return aSeq;
+}
+
+sal_Bool SAL_CALL ScDPDimensions::hasByName( const OUString& aName )
+{
+ tools::Long nCount = getCount();
+ for (tools::Long i=0; i<nCount; i++)
+ if ( getByIndex(i)->getName() == aName )
+ return true;
+ return false;
+}
+
+uno::Type SAL_CALL ScDPDimensions::getElementType()
+{
+ return cppu::UnoType<container::XNamed>::get();
+}
+
+sal_Bool SAL_CALL ScDPDimensions::hasElements()
+{
+ return ( getCount() > 0 );
+}
+
+// end of XNameAccess implementation
+
+tools::Long ScDPDimensions::getCount() const
+{
+ // in tabular data, every column of source data is a dimension
+
+ return nDimCount;
+}
+
+ScDPDimension* ScDPDimensions::getByIndex(tools::Long nIndex) const
+{
+ if ( nIndex >= 0 && nIndex < nDimCount )
+ {
+ if ( !ppDims )
+ {
+ const_cast<ScDPDimensions*>(this)->ppDims.reset(new rtl::Reference<ScDPDimension>[nDimCount] );
+ for (tools::Long i=0; i<nDimCount; i++)
+ ppDims[i] = nullptr;
+ }
+ if ( !ppDims[nIndex].is() )
+ {
+ ppDims[nIndex] = new ScDPDimension( pSource, nIndex );
+ }
+
+ return ppDims[nIndex].get();
+ }
+
+ return nullptr; //TODO: exception?
+}
+
+ScDPDimension::ScDPDimension( ScDPSource* pSrc, tools::Long nD ) :
+ pSource( pSrc ),
+ nDim( nD ),
+ nFunction( ScGeneralFunction::SUM ), // sum is default
+ nSourceDim( -1 ),
+ bHasSelectedPage( false ),
+ mbHasHiddenMember(false)
+{
+ //TODO: hold pSource
+}
+
+ScDPDimension::~ScDPDimension()
+{
+ //TODO: release pSource
+}
+
+ScDPHierarchies* ScDPDimension::GetHierarchiesObject()
+{
+ if (!mxHierarchies.is())
+ {
+ mxHierarchies = new ScDPHierarchies( pSource, nDim );
+ }
+ return mxHierarchies.get();
+}
+
+const std::optional<OUString> & ScDPDimension::GetLayoutName() const
+{
+ return mpLayoutName;
+}
+
+const std::optional<OUString> & ScDPDimension::GetSubtotalName() const
+{
+ return mpSubtotalName;
+}
+
+uno::Reference<container::XNameAccess> SAL_CALL ScDPDimension::getHierarchies()
+{
+ return GetHierarchiesObject();
+}
+
+OUString SAL_CALL ScDPDimension::getName()
+{
+ if (!aName.isEmpty())
+ return aName;
+ else
+ return pSource->GetData()->getDimensionName( nDim );
+}
+
+void SAL_CALL ScDPDimension::setName( const OUString& rNewName )
+{
+ // used after cloning
+ aName = rNewName;
+}
+
+sheet::DataPilotFieldOrientation ScDPDimension::getOrientation() const
+{
+ return pSource->GetOrientation( nDim );
+}
+
+bool ScDPDimension::getIsDataLayoutDimension() const
+{
+ return pSource->GetData()->getIsDataLayoutDimension( nDim );
+}
+
+void ScDPDimension::setFunction(ScGeneralFunction nNew)
+{
+ nFunction = nNew;
+}
+
+ScDPDimension* ScDPDimension::CreateCloneObject()
+{
+ OSL_ENSURE( nSourceDim < 0, "recursive duplicate - not implemented" );
+
+ //TODO: set new name here, or temporary name ???
+ OUString aNewName = aName;
+
+ ScDPDimension* pNew = pSource->AddDuplicated( aNewName );
+
+ pNew->aName = aNewName; //TODO: here or in source?
+ pNew->nSourceDim = nDim; //TODO: recursive?
+
+ return pNew;
+}
+
+uno::Reference<util::XCloneable> SAL_CALL ScDPDimension::createClone()
+{
+ return CreateCloneObject();
+}
+
+const ScDPItemData& ScDPDimension::GetSelectedData()
+{
+ if ( !pSelectedData )
+ {
+ // find the named member to initialize pSelectedData from it, with name and value
+
+ tools::Long nLevel = 0;
+
+ tools::Long nHierarchy = getUsedHierarchy();
+ if ( nHierarchy >= ScDPHierarchies::getCount() )
+ nHierarchy = 0;
+ ScDPLevels* pLevels = GetHierarchiesObject()->getByIndex(nHierarchy)->GetLevelsObject();
+ tools::Long nLevCount = pLevels->getCount();
+ if ( nLevel < nLevCount )
+ {
+ ScDPMembers* pMembers = pLevels->getByIndex(nLevel)->GetMembersObject();
+
+ //TODO: merge with ScDPMembers::getByName
+ tools::Long nCount = pMembers->getCount();
+ for (tools::Long i=0; i<nCount && !pSelectedData; i++)
+ {
+ ScDPMember* pMember = pMembers->getByIndex(i);
+ if (aSelectedPage == pMember->GetNameStr(false))
+ {
+ pSelectedData.reset( new ScDPItemData(pMember->FillItemData()) );
+ }
+ }
+ }
+
+ if ( !pSelectedData )
+ pSelectedData.reset( new ScDPItemData(aSelectedPage) ); // default - name only
+ }
+
+ return *pSelectedData;
+}
+
+// XPropertySet
+
+uno::Reference<beans::XPropertySetInfo> SAL_CALL ScDPDimension::getPropertySetInfo()
+{
+ static const SfxItemPropertyMapEntry aDPDimensionMap_Impl[] =
+ {
+ { SC_UNO_DP_FILTER, 0, cppu::UnoType<uno::Sequence<sheet::TableFilterField>>::get(), 0, 0 },
+ { SC_UNO_DP_FLAGS, 0, cppu::UnoType<sal_Int32>::get(), beans::PropertyAttribute::READONLY, 0 },
+ { SC_UNO_DP_FUNCTION, 0, cppu::UnoType<sheet::GeneralFunction>::get(), 0, 0 },
+ { SC_UNO_DP_FUNCTION2, 0, cppu::UnoType<sal_Int16>::get(), 0, 0 },
+ { SC_UNO_DP_ISDATALAYOUT, 0, cppu::UnoType<bool>::get(), beans::PropertyAttribute::READONLY, 0 },
+ { SC_UNO_DP_NUMBERFO, 0, cppu::UnoType<sal_Int32>::get(), beans::PropertyAttribute::READONLY, 0 },
+ { SC_UNO_DP_ORIENTATION, 0, cppu::UnoType<sheet::DataPilotFieldOrientation>::get(), 0, 0 },
+ { SC_UNO_DP_ORIGINAL, 0, cppu::UnoType<container::XNamed>::get(), beans::PropertyAttribute::READONLY, 0 },
+ { SC_UNO_DP_ORIGINAL_POS, 0, cppu::UnoType<sal_Int32>::get(), 0, 0 },
+ { SC_UNO_DP_POSITION, 0, cppu::UnoType<sal_Int32>::get(), 0, 0 },
+ { SC_UNO_DP_REFVALUE, 0, cppu::UnoType<sheet::DataPilotFieldReference>::get(), 0, 0 },
+ { SC_UNO_DP_USEDHIERARCHY, 0, cppu::UnoType<sal_Int32>::get(), 0, 0 },
+ { SC_UNO_DP_LAYOUTNAME, 0, cppu::UnoType<OUString>::get(), 0, 0 },
+ { SC_UNO_DP_FIELD_SUBTOTALNAME, 0, cppu::UnoType<OUString>::get(), 0, 0 },
+ { SC_UNO_DP_HAS_HIDDEN_MEMBER, 0, cppu::UnoType<bool>::get(), 0, 0 },
+ { u"", 0, css::uno::Type(), 0, 0 }
+ };
+ static uno::Reference<beans::XPropertySetInfo> aRef =
+ new SfxItemPropertySetInfo( aDPDimensionMap_Impl );
+ return aRef;
+}
+
+void SAL_CALL ScDPDimension::setPropertyValue( const OUString& aPropertyName, const uno::Any& aValue )
+{
+ if ( aPropertyName == SC_UNO_DP_USEDHIERARCHY )
+ {
+ // #i52547# don't use the incomplete date hierarchy implementation - ignore the call
+ }
+ else if ( aPropertyName == SC_UNO_DP_ORIENTATION )
+ {
+ sheet::DataPilotFieldOrientation eEnum;
+ if (aValue >>= eEnum)
+ pSource->SetOrientation( nDim, eEnum );
+ }
+ else if ( aPropertyName == SC_UNO_DP_FUNCTION )
+ {
+ sheet::GeneralFunction eEnum;
+ if (aValue >>= eEnum)
+ setFunction( static_cast<ScGeneralFunction>(eEnum) );
+ }
+ else if ( aPropertyName == SC_UNO_DP_FUNCTION2 )
+ {
+ sal_Int16 eEnum;
+ if (aValue >>= eEnum)
+ setFunction( static_cast<ScGeneralFunction>(eEnum) );
+ }
+ else if ( aPropertyName == SC_UNO_DP_REFVALUE )
+ aValue >>= aReferenceValue;
+ else if ( aPropertyName == SC_UNO_DP_FILTER )
+ {
+ bool bDone = false;
+ uno::Sequence<sheet::TableFilterField> aSeq;
+ if (aValue >>= aSeq)
+ {
+ sal_Int32 nLength = aSeq.getLength();
+ if ( nLength == 0 )
+ {
+ aSelectedPage.clear();
+ bHasSelectedPage = false;
+ bDone = true;
+ }
+ else if ( nLength == 1 )
+ {
+ const sheet::TableFilterField& rField = aSeq[0];
+ if ( rField.Field == 0 && rField.Operator == sheet::FilterOperator_EQUAL && !rField.IsNumeric )
+ {
+ aSelectedPage = rField.StringValue;
+ bHasSelectedPage = true;
+ bDone = true;
+ }
+ }
+ }
+ if ( !bDone )
+ {
+ OSL_FAIL("Filter property is not a single string");
+ throw lang::IllegalArgumentException();
+ }
+ pSelectedData.reset(); // invalid after changing aSelectedPage
+ }
+ else if (aPropertyName == SC_UNO_DP_LAYOUTNAME)
+ {
+ OUString aTmpName;
+ if (aValue >>= aTmpName)
+ mpLayoutName = aTmpName;
+ }
+ else if (aPropertyName == SC_UNO_DP_FIELD_SUBTOTALNAME)
+ {
+ OUString aTmpName;
+ if (aValue >>= aTmpName)
+ mpSubtotalName = aTmpName;
+ }
+ else if (aPropertyName == SC_UNO_DP_HAS_HIDDEN_MEMBER)
+ {
+ bool b = false;
+ aValue >>= b;
+ mbHasHiddenMember = b;
+ }
+ else
+ {
+ OSL_FAIL("unknown property");
+ //TODO: THROW( UnknownPropertyException() );
+ }
+}
+
+uno::Any SAL_CALL ScDPDimension::getPropertyValue( const OUString& aPropertyName )
+{
+ uno::Any aRet;
+ if ( aPropertyName == SC_UNO_DP_POSITION )
+ aRet <<= pSource->GetPosition( nDim );
+ else if ( aPropertyName == SC_UNO_DP_USEDHIERARCHY )
+ aRet <<= static_cast<sal_Int32>(getUsedHierarchy());
+ else if ( aPropertyName == SC_UNO_DP_ORIENTATION )
+ {
+ sheet::DataPilotFieldOrientation eVal = getOrientation();
+ aRet <<= eVal;
+ }
+ else if ( aPropertyName == SC_UNO_DP_FUNCTION )
+ {
+ ScGeneralFunction nVal = getFunction();
+ if (nVal == ScGeneralFunction::MEDIAN)
+ nVal = ScGeneralFunction::NONE;
+ const int nValAsInt = static_cast<int>(nVal);
+ assert(nValAsInt >= int(css::sheet::GeneralFunction_NONE) &&
+ nValAsInt <= int(css::sheet::GeneralFunction_VARP));
+ aRet <<= static_cast<sheet::GeneralFunction>(nValAsInt);
+ }
+ else if ( aPropertyName == SC_UNO_DP_FUNCTION2 )
+ {
+ ScGeneralFunction eVal = getFunction();
+ aRet <<= static_cast<sal_Int16>(eVal);
+ }
+ else if ( aPropertyName == SC_UNO_DP_REFVALUE )
+ aRet <<= aReferenceValue;
+ else if ( aPropertyName == SC_UNO_DP_ISDATALAYOUT ) // read-only properties
+ aRet <<= getIsDataLayoutDimension();
+ else if ( aPropertyName == SC_UNO_DP_NUMBERFO )
+ {
+ sal_Int32 nFormat = 0;
+ ScGeneralFunction eFunc = getFunction();
+ // #i63745# don't use source format for "count"
+ if ( eFunc != ScGeneralFunction::COUNT && eFunc != ScGeneralFunction::COUNTNUMS )
+ nFormat = pSource->GetData()->GetNumberFormat( ( nSourceDim >= 0 ) ? nSourceDim : nDim );
+
+ switch ( aReferenceValue.ReferenceType )
+ {
+ case sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE:
+ case sheet::DataPilotFieldReferenceType::ITEM_PERCENTAGE_DIFFERENCE:
+ case sheet::DataPilotFieldReferenceType::ROW_PERCENTAGE:
+ case sheet::DataPilotFieldReferenceType::COLUMN_PERCENTAGE:
+ case sheet::DataPilotFieldReferenceType::TOTAL_PERCENTAGE:
+ nFormat = pSource->GetData()->GetNumberFormatByIdx( NF_PERCENT_DEC2 );
+ break;
+ case sheet::DataPilotFieldReferenceType::INDEX:
+ nFormat = pSource->GetData()->GetNumberFormatByIdx( NF_NUMBER_SYSTEM );
+ break;
+ default:
+ break;
+ }
+
+ aRet <<= nFormat;
+ }
+ else if ( aPropertyName == SC_UNO_DP_ORIGINAL )
+ {
+ uno::Reference<container::XNamed> xOriginal;
+ if (nSourceDim >= 0)
+ xOriginal = pSource->GetDimensionsObject()->getByIndex(nSourceDim);
+ aRet <<= xOriginal;
+ }
+ else if (aPropertyName == SC_UNO_DP_ORIGINAL_POS)
+ {
+ aRet <<= nSourceDim;
+ }
+ else if ( aPropertyName == SC_UNO_DP_FILTER )
+ {
+ if ( bHasSelectedPage )
+ {
+ // single filter field: first field equal to selected string
+ sheet::TableFilterField aField( sheet::FilterConnection_AND, 0,
+ sheet::FilterOperator_EQUAL, false, 0.0, aSelectedPage );
+ aRet <<= uno::Sequence<sheet::TableFilterField>( &aField, 1 );
+ }
+ else
+ aRet <<= uno::Sequence<sheet::TableFilterField>(0);
+ }
+ else if (aPropertyName == SC_UNO_DP_LAYOUTNAME)
+ aRet <<= mpLayoutName ? *mpLayoutName : OUString();
+ else if (aPropertyName == SC_UNO_DP_FIELD_SUBTOTALNAME)
+ aRet <<= mpSubtotalName ? *mpSubtotalName : OUString();
+ else if (aPropertyName == SC_UNO_DP_HAS_HIDDEN_MEMBER)
+ aRet <<= mbHasHiddenMember;
+ else if (aPropertyName == SC_UNO_DP_FLAGS)
+ {
+ aRet <<= sal_Int32(0); // tabular data: all orientations are possible
+ }
+ else
+ {
+ OSL_FAIL("unknown property");
+ //TODO: THROW( UnknownPropertyException() );
+ }
+ return aRet;
+}
+
+SC_IMPL_DUMMY_PROPERTY_LISTENER( ScDPDimension )
+
+ScDPHierarchies::ScDPHierarchies( ScDPSource* pSrc, tools::Long nD ) :
+ pSource( pSrc ),
+ nDim( nD )
+{
+ //TODO: hold pSource
+}
+
+ScDPHierarchies::~ScDPHierarchies()
+{
+ //TODO: release pSource
+}
+
+// very simple XNameAccess implementation using getCount/getByIndex
+
+uno::Any SAL_CALL ScDPHierarchies::getByName( const OUString& aName )
+{
+ tools::Long nCount = getCount();
+ for (tools::Long i=0; i<nCount; i++)
+ if ( getByIndex(i)->getName() == aName )
+ {
+ uno::Reference<container::XNamed> xNamed = getByIndex(i);
+ uno::Any aRet;
+ aRet <<= xNamed;
+ return aRet;
+ }
+
+ throw container::NoSuchElementException();
+}
+
+uno::Sequence<OUString> SAL_CALL ScDPHierarchies::getElementNames()
+{
+ tools::Long nCount = getCount();
+ uno::Sequence<OUString> aSeq(nCount);
+ OUString* pArr = aSeq.getArray();
+ for (tools::Long i=0; i<nCount; i++)
+ pArr[i] = getByIndex(i)->getName();
+ return aSeq;
+}
+
+sal_Bool SAL_CALL ScDPHierarchies::hasByName( const OUString& aName )
+{
+ tools::Long nCount = getCount();
+ for (tools::Long i=0; i<nCount; i++)
+ if ( getByIndex(i)->getName() == aName )
+ return true;
+ return false;
+}
+
+uno::Type SAL_CALL ScDPHierarchies::getElementType()
+{
+ return cppu::UnoType<container::XNamed>::get();
+}
+
+sal_Bool SAL_CALL ScDPHierarchies::hasElements()
+{
+ return ( getCount() > 0 );
+}
+
+// end of XNameAccess implementation
+
+sal_Int32 ScDPHierarchies::getCount()
+{
+ return nHierCount;
+}
+
+ScDPHierarchy* ScDPHierarchies::getByIndex(tools::Long nIndex) const
+{
+ // pass hierarchy index to new object in case the implementation
+ // will be extended to more than one hierarchy
+
+ if ( nIndex >= 0 && nIndex < nHierCount )
+ {
+ if ( !ppHiers )
+ {
+ const_cast<ScDPHierarchies*>(this)->ppHiers.reset( new rtl::Reference<ScDPHierarchy>[nHierCount] );
+ for (sal_Int32 i=0; i<nHierCount; i++)
+ ppHiers[i] = nullptr;
+ }
+ if ( !ppHiers[nIndex].is() )
+ {
+ ppHiers[nIndex] = new ScDPHierarchy( pSource, nDim, nIndex );
+ }
+
+ return ppHiers[nIndex].get();
+ }
+
+ return nullptr; //TODO: exception?
+}
+
+ScDPHierarchy::ScDPHierarchy( ScDPSource* pSrc, sal_Int32 nD, sal_Int32 nH ) :
+ pSource( pSrc ),
+ nDim( nD ),
+ nHier( nH )
+{
+ //TODO: hold pSource
+}
+
+ScDPHierarchy::~ScDPHierarchy()
+{
+ //TODO: release pSource
+}
+
+ScDPLevels* ScDPHierarchy::GetLevelsObject()
+{
+ if (!mxLevels.is())
+ {
+ mxLevels = new ScDPLevels( pSource, nDim, nHier );
+ }
+ return mxLevels.get();
+}
+
+uno::Reference<container::XNameAccess> SAL_CALL ScDPHierarchy::getLevels()
+{
+ return GetLevelsObject();
+}
+
+OUString SAL_CALL ScDPHierarchy::getName()
+{
+ OUString aRet; //TODO: globstr-ID !!!!
+ switch (nHier)
+ {
+ case SC_DAPI_HIERARCHY_FLAT:
+ aRet = "flat";
+ break; //TODO: name ???????
+ case SC_DAPI_HIERARCHY_QUARTER:
+ aRet = "Quarter";
+ break; //TODO: name ???????
+ case SC_DAPI_HIERARCHY_WEEK:
+ aRet = "Week";
+ break; //TODO: name ???????
+ default:
+ OSL_FAIL( "ScDPHierarchy::getName: unexpected hierarchy" );
+ break;
+ }
+ return aRet;
+}
+
+void SAL_CALL ScDPHierarchy::setName( const OUString& /* rNewName */ )
+{
+ OSL_FAIL("not implemented"); //TODO: exception?
+}
+
+ScDPLevels::ScDPLevels( ScDPSource* pSrc, sal_Int32 nD, sal_Int32 nH ) :
+ pSource( pSrc ),
+ nDim( nD ),
+ nHier( nH )
+{
+ //TODO: hold pSource
+
+ // text columns have only one level
+
+ tools::Long nSrcDim = pSource->GetSourceDim( nDim );
+ if ( pSource->IsDateDimension( nSrcDim ) )
+ {
+ switch ( nHier )
+ {
+ case SC_DAPI_HIERARCHY_FLAT: nLevCount = SC_DAPI_FLAT_LEVELS; break;
+ case SC_DAPI_HIERARCHY_QUARTER: nLevCount = SC_DAPI_QUARTER_LEVELS; break;
+ case SC_DAPI_HIERARCHY_WEEK: nLevCount = SC_DAPI_WEEK_LEVELS; break;
+ default:
+ OSL_FAIL("wrong hierarchy");
+ nLevCount = 0;
+ }
+ }
+ else
+ nLevCount = 1;
+}
+
+ScDPLevels::~ScDPLevels()
+{
+ //TODO: release pSource
+}
+
+// very simple XNameAccess implementation using getCount/getByIndex
+
+uno::Any SAL_CALL ScDPLevels::getByName( const OUString& aName )
+{
+ tools::Long nCount = getCount();
+ for (tools::Long i=0; i<nCount; i++)
+ if ( getByIndex(i)->getName() == aName )
+ {
+ uno::Reference<container::XNamed> xNamed = getByIndex(i);
+ uno::Any aRet;
+ aRet <<= xNamed;
+ return aRet;
+ }
+
+ throw container::NoSuchElementException();
+}
+
+uno::Sequence<OUString> SAL_CALL ScDPLevels::getElementNames()
+{
+ tools::Long nCount = getCount();
+ uno::Sequence<OUString> aSeq(nCount);
+ OUString* pArr = aSeq.getArray();
+ for (tools::Long i=0; i<nCount; i++)
+ pArr[i] = getByIndex(i)->getName();
+ return aSeq;
+}
+
+sal_Bool SAL_CALL ScDPLevels::hasByName( const OUString& aName )
+{
+ tools::Long nCount = getCount();
+ for (tools::Long i=0; i<nCount; i++)
+ if ( getByIndex(i)->getName() == aName )
+ return true;
+ return false;
+}
+
+uno::Type SAL_CALL ScDPLevels::getElementType()
+{
+ return cppu::UnoType<container::XNamed>::get();
+}
+
+sal_Bool SAL_CALL ScDPLevels::hasElements()
+{
+ return ( getCount() > 0 );
+}
+
+// end of XNameAccess implementation
+
+sal_Int32 ScDPLevels::getCount() const
+{
+ return nLevCount;
+}
+
+ScDPLevel* ScDPLevels::getByIndex(sal_Int32 nIndex) const
+{
+ if ( nIndex >= 0 && nIndex < nLevCount )
+ {
+ if ( !ppLevs )
+ {
+ const_cast<ScDPLevels*>(this)->ppLevs.reset(new rtl::Reference<ScDPLevel>[nLevCount] );
+ for (tools::Long i=0; i<nLevCount; i++)
+ ppLevs[i] = nullptr;
+ }
+ if ( !ppLevs[nIndex].is() )
+ {
+ ppLevs[nIndex] = new ScDPLevel( pSource, nDim, nHier, nIndex );
+ }
+
+ return ppLevs[nIndex].get();
+ }
+
+ return nullptr; //TODO: exception?
+}
+
+namespace {
+
+class ScDPGlobalMembersOrder
+{
+ ScDPLevel& rLevel;
+ bool bAscending;
+
+public:
+ ScDPGlobalMembersOrder( ScDPLevel& rLev, bool bAsc ) :
+ rLevel(rLev),
+ bAscending(bAsc)
+ {}
+
+ bool operator()( sal_Int32 nIndex1, sal_Int32 nIndex2 ) const;
+};
+
+}
+
+bool ScDPGlobalMembersOrder::operator()( sal_Int32 nIndex1, sal_Int32 nIndex2 ) const
+{
+ sal_Int32 nCompare = 0;
+ // seems that some ::std::sort() implementations pass the same index twice
+ if( nIndex1 != nIndex2 )
+ {
+ ScDPMembers* pMembers = rLevel.GetMembersObject();
+ ScDPMember* pMember1 = pMembers->getByIndex(nIndex1);
+ ScDPMember* pMember2 = pMembers->getByIndex(nIndex2);
+ nCompare = pMember1->Compare( *pMember2 );
+ }
+ return bAscending ? (nCompare < 0) : (nCompare > 0);
+}
+
+ScDPLevel::ScDPLevel( ScDPSource* pSrc, sal_Int32 nD, sal_Int32 nH, sal_Int32 nL ) :
+ pSource( pSrc ),
+ nDim( nD ),
+ nHier( nH ),
+ nLev( nL ),
+ aSortInfo( OUString(), true, sheet::DataPilotFieldSortMode::NAME ), // default: sort by name
+ nSortMeasure( 0 ),
+ nAutoMeasure( 0 ),
+ bShowEmpty( false ),
+ bEnableLayout( false ),
+ bRepeatItemLabels( false )
+{
+ //TODO: hold pSource
+ // aSubTotals is empty
+}
+
+ScDPLevel::~ScDPLevel()
+{
+ //TODO: release pSource
+}
+
+void ScDPLevel::EvaluateSortOrder()
+{
+ switch (aSortInfo.Mode)
+ {
+ case sheet::DataPilotFieldSortMode::DATA:
+ {
+ // find index of measure (index among data dimensions)
+
+ tools::Long nMeasureCount = pSource->GetDataDimensionCount();
+ for (tools::Long nMeasure=0; nMeasure<nMeasureCount; nMeasure++)
+ {
+ if (pSource->GetDataDimName(nMeasure) == aSortInfo.Field)
+ {
+ nSortMeasure = nMeasure;
+ break;
+ }
+ }
+
+ //TODO: error if not found?
+ }
+ break;
+ case sheet::DataPilotFieldSortMode::MANUAL:
+ case sheet::DataPilotFieldSortMode::NAME:
+ {
+ ScDPMembers* pLocalMembers = GetMembersObject();
+ tools::Long nCount = pLocalMembers->getCount();
+
+ aGlobalOrder.resize( nCount );
+ for (tools::Long nPos=0; nPos<nCount; nPos++)
+ aGlobalOrder[nPos] = nPos;
+
+ // allow manual or name (manual is always ascending)
+ bool bAscending = ( aSortInfo.Mode == sheet::DataPilotFieldSortMode::MANUAL || aSortInfo.IsAscending );
+ ScDPGlobalMembersOrder aComp( *this, bAscending );
+ ::std::sort( aGlobalOrder.begin(), aGlobalOrder.end(), aComp );
+ }
+ break;
+ }
+
+ if ( !aAutoShowInfo.IsEnabled )
+ return;
+
+ // find index of measure (index among data dimensions)
+
+ tools::Long nMeasureCount = pSource->GetDataDimensionCount();
+ for (tools::Long nMeasure=0; nMeasure<nMeasureCount; nMeasure++)
+ {
+ if (pSource->GetDataDimName(nMeasure) == aAutoShowInfo.DataField)
+ {
+ nAutoMeasure = nMeasure;
+ break;
+ }
+ }
+
+ //TODO: error if not found?
+}
+
+void ScDPLevel::SetEnableLayout(bool bSet)
+{
+ bEnableLayout = bSet;
+}
+
+ScDPMembers* ScDPLevel::GetMembersObject()
+{
+ if (!mxMembers.is())
+ {
+ mxMembers = new ScDPMembers( pSource, nDim, nHier, nLev );
+ }
+ return mxMembers.get();
+}
+
+uno::Reference<sheet::XMembersAccess> SAL_CALL ScDPLevel::getMembers()
+{
+ return GetMembersObject();
+}
+
+uno::Sequence<sheet::MemberResult> SAL_CALL ScDPLevel::getResults()
+{
+ const uno::Sequence<sheet::MemberResult>* pRes = pSource->GetMemberResults( this );
+ if (pRes)
+ return *pRes;
+
+ return {}; //TODO: Error?
+}
+
+OUString SAL_CALL ScDPLevel::getName()
+{
+ tools::Long nSrcDim = pSource->GetSourceDim( nDim );
+ if ( pSource->IsDateDimension( nSrcDim ) )
+ {
+ OUString aRet; //TODO: globstr-ID !!!!
+
+ if ( nHier == SC_DAPI_HIERARCHY_QUARTER )
+ {
+ switch ( nLev )
+ {
+ case SC_DAPI_LEVEL_YEAR:
+ aRet = "Year";
+ break;
+ case SC_DAPI_LEVEL_QUARTER:
+ aRet = "Quarter";
+ break;
+ case SC_DAPI_LEVEL_MONTH:
+ aRet = "Month";
+ break;
+ case SC_DAPI_LEVEL_DAY:
+ aRet = "Day";
+ break;
+ default:
+ OSL_FAIL( "ScDPLevel::getName: unexpected level" );
+ break;
+ }
+ }
+ else if ( nHier == SC_DAPI_HIERARCHY_WEEK )
+ {
+ switch ( nLev )
+ {
+ case SC_DAPI_LEVEL_YEAR:
+ aRet = "Year";
+ break;
+ case SC_DAPI_LEVEL_WEEK:
+ aRet = "Week";
+ break;
+ case SC_DAPI_LEVEL_WEEKDAY:
+ aRet = "Weekday";
+ break;
+ default:
+ OSL_FAIL( "ScDPLevel::getName: unexpected level" );
+ break;
+ }
+ }
+ if (!aRet.isEmpty())
+ return aRet;
+ }
+
+ ScDPDimension* pDim = pSource->GetDimensionsObject()->getByIndex(nSrcDim);
+ if (!pDim)
+ return OUString();
+
+ return pDim->getName();
+}
+
+void SAL_CALL ScDPLevel::setName( const OUString& /* rNewName */ )
+{
+ OSL_FAIL("not implemented"); //TODO: exception?
+}
+
+uno::Sequence<sal_Int16> ScDPLevel::getSubTotals() const
+{
+ //TODO: separate functions for settings and evaluation?
+
+ tools::Long nSrcDim = pSource->GetSourceDim( nDim );
+ if ( !pSource->SubTotalAllowed( nSrcDim ) )
+ return {};
+
+ return aSubTotals;
+}
+
+// XPropertySet
+
+uno::Reference<beans::XPropertySetInfo> SAL_CALL ScDPLevel::getPropertySetInfo()
+{
+ static const SfxItemPropertyMapEntry aDPLevelMap_Impl[] =
+ {
+ //TODO: change type of AutoShow/Layout/Sorting to API struct when available
+ { SC_UNO_DP_AUTOSHOW, 0, cppu::UnoType<sheet::DataPilotFieldAutoShowInfo>::get(), 0, 0 },
+ { SC_UNO_DP_LAYOUT, 0, cppu::UnoType<sheet::DataPilotFieldLayoutInfo>::get(), 0, 0 },
+ { SC_UNO_DP_SHOWEMPTY, 0, cppu::UnoType<bool>::get(), 0, 0 },
+ { SC_UNO_DP_REPEATITEMLABELS, 0, cppu::UnoType<bool>::get(), 0, 0 },
+ { SC_UNO_DP_SORTING, 0, cppu::UnoType<sheet::DataPilotFieldSortInfo>::get(), 0, 0 },
+ { SC_UNO_DP_SUBTOTAL, 0, cppu::UnoType<uno::Sequence<sheet::GeneralFunction>>::get(), 0, 0 },
+ { SC_UNO_DP_SUBTOTAL2, 0, cppu::UnoType<uno::Sequence<sal_Int16>>::get(), 0, 0 },
+ { u"", 0, css::uno::Type(), 0, 0 }
+ };
+ static uno::Reference<beans::XPropertySetInfo> aRef =
+ new SfxItemPropertySetInfo( aDPLevelMap_Impl );
+ return aRef;
+}
+
+void SAL_CALL ScDPLevel::setPropertyValue( const OUString& aPropertyName, const uno::Any& aValue )
+{
+ if ( aPropertyName == SC_UNO_DP_SHOWEMPTY )
+ bShowEmpty = lcl_GetBoolFromAny(aValue);
+ else if ( aPropertyName == SC_UNO_DP_REPEATITEMLABELS )
+ bRepeatItemLabels = lcl_GetBoolFromAny(aValue);
+ else if ( aPropertyName == SC_UNO_DP_SUBTOTAL )
+ {
+ uno::Sequence<sheet::GeneralFunction> aSeq;
+ aValue >>= aSeq;
+ aSubTotals.realloc(aSeq.getLength());
+ std::transform(std::cbegin(aSeq), std::cend(aSeq), aSubTotals.getArray(),
+ [](const sheet::GeneralFunction& rFunc) -> sal_Int16 {
+ return static_cast<sal_Int16>(rFunc); });
+ }
+ else if ( aPropertyName == SC_UNO_DP_SUBTOTAL2 )
+ aValue >>= aSubTotals;
+ else if ( aPropertyName == SC_UNO_DP_SORTING )
+ aValue >>= aSortInfo;
+ else if ( aPropertyName == SC_UNO_DP_AUTOSHOW )
+ aValue >>= aAutoShowInfo;
+ else if ( aPropertyName == SC_UNO_DP_LAYOUT )
+ aValue >>= aLayoutInfo;
+ else
+ {
+ OSL_FAIL("unknown property");
+ }
+}
+
+uno::Any SAL_CALL ScDPLevel::getPropertyValue( const OUString& aPropertyName )
+{
+ uno::Any aRet;
+ if ( aPropertyName == SC_UNO_DP_SHOWEMPTY )
+ aRet <<= bShowEmpty;
+ else if ( aPropertyName == SC_UNO_DP_REPEATITEMLABELS )
+ aRet <<= bRepeatItemLabels;
+ else if ( aPropertyName == SC_UNO_DP_SUBTOTAL )
+ {
+ const uno::Sequence<sal_Int16> aSeq = getSubTotals();
+ uno::Sequence<sheet::GeneralFunction> aNewSeq(aSeq.getLength());
+ std::transform(aSeq.begin(), aSeq.end(), aNewSeq.getArray(),
+ [](const sal_Int16 nFunc) -> sheet::GeneralFunction {
+ if (nFunc == sheet::GeneralFunction2::MEDIAN)
+ return sheet::GeneralFunction_NONE;
+ return static_cast<sheet::GeneralFunction>(nFunc);
+ });
+
+ aRet <<= aNewSeq;
+ }
+ else if ( aPropertyName == SC_UNO_DP_SUBTOTAL2 )
+ {
+ uno::Sequence<sal_Int16> aSeq = getSubTotals(); //TODO: avoid extra copy?
+ aRet <<= aSeq;
+ }
+ else if ( aPropertyName == SC_UNO_DP_SORTING )
+ aRet <<= aSortInfo;
+ else if ( aPropertyName == SC_UNO_DP_AUTOSHOW )
+ aRet <<= aAutoShowInfo;
+ else if ( aPropertyName == SC_UNO_DP_LAYOUT )
+ aRet <<= aLayoutInfo;
+ else if (aPropertyName == SC_UNO_DP_LAYOUTNAME)
+ {
+ // read only property
+ tools::Long nSrcDim = pSource->GetSourceDim(nDim);
+ ScDPDimension* pDim = pSource->GetDimensionsObject()->getByIndex(nSrcDim);
+ if (!pDim)
+ return aRet;
+
+ const std::optional<OUString> & pLayoutName = pDim->GetLayoutName();
+ if (!pLayoutName)
+ return aRet;
+
+ aRet <<= *pLayoutName;
+ }
+ else
+ {
+ OSL_FAIL("unknown property");
+ }
+ return aRet;
+}
+
+SC_IMPL_DUMMY_PROPERTY_LISTENER( ScDPLevel )
+
+ScDPMembers::ScDPMembers( ScDPSource* pSrc, sal_Int32 nD, sal_Int32 nH, sal_Int32 nL ) :
+ pSource( pSrc ),
+ nDim( nD ),
+ nHier( nH ),
+ nLev( nL )
+{
+ //TODO: hold pSource
+
+ tools::Long nSrcDim = pSource->GetSourceDim( nDim );
+ if ( pSource->IsDataLayoutDimension(nSrcDim) )
+ nMbrCount = pSource->GetDataDimensionCount();
+ else if ( nHier != SC_DAPI_HIERARCHY_FLAT && pSource->IsDateDimension( nSrcDim ) )
+ {
+ nMbrCount = 0;
+ if ( nHier == SC_DAPI_HIERARCHY_QUARTER )
+ {
+ switch (nLev)
+ {
+ case SC_DAPI_LEVEL_YEAR:
+ {
+ const ScDPItemData* pLastNumData = nullptr;
+ for ( SCROW n = 0; n < static_cast<SCROW>(pSource->GetData()->GetColumnEntries(nDim).size()); n-- )
+ {
+ const ScDPItemData* pData = GetSrcItemDataByIndex( n );
+ if ( pData && pData->HasStringData() )
+ break;
+ else
+ pLastNumData = pData;
+ }
+
+ if ( pLastNumData )
+ {
+ const ScDPItemData* pFirstData = GetSrcItemDataByIndex( 0 );
+ double fFirstVal = pFirstData->GetValue();
+ double fLastVal = pLastNumData->GetValue();
+
+ tools::Long nFirstYear = pSource->GetData()->GetDatePart(
+ static_cast<tools::Long>(::rtl::math::approxFloor( fFirstVal )),
+ nHier, nLev );
+ tools::Long nLastYear = pSource->GetData()->GetDatePart(
+ static_cast<tools::Long>(::rtl::math::approxFloor( fLastVal )),
+ nHier, nLev );
+
+ nMbrCount = nLastYear + 1 - nFirstYear;
+ }
+ else
+ nMbrCount = 0; // no values
+ }
+ break;
+ case SC_DAPI_LEVEL_QUARTER: nMbrCount = 4; break;
+ case SC_DAPI_LEVEL_MONTH: nMbrCount = 12; break;
+ case SC_DAPI_LEVEL_DAY: nMbrCount = 31; break;
+ default:
+ OSL_FAIL( "ScDPMembers::ScDPMembers: unexpected level" );
+ break;
+ }
+ }
+ else if ( nHier == SC_DAPI_HIERARCHY_WEEK )
+ {
+ switch (nLev)
+ {
+ case SC_DAPI_LEVEL_YEAR: nMbrCount = 1; break; //TODO: get years from source
+ case SC_DAPI_LEVEL_WEEK: nMbrCount = 53; break;
+ case SC_DAPI_LEVEL_WEEKDAY: nMbrCount = 7; break;
+ default:
+ OSL_FAIL( "ScDPMembers::ScDPMembers: unexpected level" );
+ break;
+ }
+ }
+ }
+ else
+ nMbrCount = pSource->GetData()->GetMembersCount( nSrcDim );
+}
+
+ScDPMembers::~ScDPMembers()
+{
+}
+
+// XNameAccess implementation using getCount/getByIndex
+
+sal_Int32 ScDPMembers::GetIndexFromName( const OUString& rName ) const
+{
+ if ( aHashMap.empty() )
+ {
+ // store the index for each name
+
+ sal_Int32 nCount = getCount();
+ for (sal_Int32 i=0; i<nCount; i++)
+ aHashMap[ getByIndex(i)->getName() ] = i;
+ }
+
+ ScDPMembersHashMap::const_iterator aIter = aHashMap.find( rName );
+ if ( aIter != aHashMap.end() )
+ return aIter->second; // found index
+ else
+ return -1; // not found
+}
+
+uno::Any SAL_CALL ScDPMembers::getByName( const OUString& aName )
+{
+ sal_Int32 nIndex = GetIndexFromName( aName );
+ if ( nIndex >= 0 )
+ {
+ uno::Reference<container::XNamed> xNamed = getByIndex(nIndex);
+ uno::Any aRet;
+ aRet <<= xNamed;
+ return aRet;
+ }
+
+ throw container::NoSuchElementException();
+}
+
+uno::Sequence<OUString> SAL_CALL ScDPMembers::getElementNames()
+{
+ return getElementNames( false );
+}
+
+sal_Bool SAL_CALL ScDPMembers::hasByName( const OUString& aName )
+{
+ return ( GetIndexFromName( aName ) >= 0 );
+}
+
+uno::Type SAL_CALL ScDPMembers::getElementType()
+{
+ return cppu::UnoType<container::XNamed>::get();
+}
+
+sal_Bool SAL_CALL ScDPMembers::hasElements()
+{
+ return ( getCount() > 0 );
+}
+
+// end of XNameAccess implementation
+
+// XMembersAccess implementation
+
+uno::Sequence<OUString> SAL_CALL ScDPMembers::getLocaleIndependentElementNames()
+{
+ return getElementNames( true );
+}
+
+// end of XMembersAccess implementation
+
+uno::Sequence<OUString> ScDPMembers::getElementNames( bool bLocaleIndependent ) const
+{
+ // Return list of names in sorted order,
+ // so it's displayed in that order in the field options dialog.
+ // Sorting is done at the level object (parent of this).
+
+ ScDPLevel* pLevel = pSource->GetDimensionsObject()->getByIndex(nDim)->
+ GetHierarchiesObject()->getByIndex(nHier)->GetLevelsObject()->getByIndex(nLev);
+ pLevel->EvaluateSortOrder();
+ const std::vector<sal_Int32>& rGlobalOrder = pLevel->GetGlobalOrder();
+ bool bSort = !rGlobalOrder.empty();
+
+ tools::Long nCount = getCount();
+ uno::Sequence<OUString> aSeq(nCount);
+ OUString* pArr = aSeq.getArray();
+ for (tools::Long i=0; i<nCount; i++)
+ pArr[i] = getByIndex(bSort ? rGlobalOrder[i] : i)->GetNameStr( bLocaleIndependent);
+ return aSeq;
+}
+
+sal_Int32 ScDPMembers::getMinMembers() const
+{
+ // used in lcl_CountMinMembers
+
+ sal_Int32 nVisCount = 0;
+ if (!maMembers.empty())
+ {
+ nVisCount = std::count_if(maMembers.begin(), maMembers.end(), [](const rtl::Reference<ScDPMember>& pMbr) {
+ // count only visible with details (default is true for both)
+ return !pMbr || (pMbr->isVisible() && pMbr->getShowDetails()); });
+ }
+ else
+ nVisCount = nMbrCount; // default for all
+
+ return nVisCount;
+}
+
+ScDPMember* ScDPMembers::getByIndex(sal_Int32 nIndex) const
+{
+ // result of GetColumnEntries must not change between ScDPMembers ctor
+ // and all calls to getByIndex
+
+ if ( nIndex >= 0 && nIndex < nMbrCount )
+ {
+ if (maMembers.empty())
+ maMembers.resize(nMbrCount);
+
+ if (!maMembers[nIndex])
+ {
+ rtl::Reference<ScDPMember> pNew;
+ sal_Int32 nSrcDim = pSource->GetSourceDim( nDim );
+ if ( pSource->IsDataLayoutDimension(nSrcDim) )
+ {
+ // empty name (never shown, not used for lookup)
+ pNew.set(new ScDPMember(pSource, nDim, nHier, nLev, 0));
+ }
+ else if ( nHier != SC_DAPI_HIERARCHY_FLAT && pSource->IsDateDimension( nSrcDim ) )
+ {
+ sal_Int32 nGroupBy = 0;
+ sal_Int32 nVal = 0;
+ OUString aName;
+
+ if ( nLev == SC_DAPI_LEVEL_YEAR ) // YEAR is in both hierarchies
+ {
+ //TODO: cache year range here!
+
+ double fFirstVal = pSource->GetData()->GetMemberByIndex( nSrcDim, 0 )->GetValue();
+ tools::Long nFirstYear = pSource->GetData()->GetDatePart(
+ static_cast<tools::Long>(::rtl::math::approxFloor( fFirstVal )),
+ nHier, nLev );
+
+ nVal = nFirstYear + nIndex;
+ }
+ else if ( nHier == SC_DAPI_HIERARCHY_WEEK && nLev == SC_DAPI_LEVEL_WEEKDAY )
+ {
+ nVal = nIndex; // DayOfWeek is 0-based
+ aName = ScGlobal::GetCalendar().getDisplayName(
+ css::i18n::CalendarDisplayIndex::DAY,
+ sal::static_int_cast<sal_Int16>(nVal), 0 );
+ }
+ else if ( nHier == SC_DAPI_HIERARCHY_QUARTER && nLev == SC_DAPI_LEVEL_MONTH )
+ {
+ nVal = nIndex; // Month is 0-based
+ aName = ScGlobal::GetCalendar().getDisplayName(
+ css::i18n::CalendarDisplayIndex::MONTH,
+ sal::static_int_cast<sal_Int16>(nVal), 0 );
+ }
+ else
+ nVal = nIndex + 1; // Quarter, Day, Week are 1-based
+
+ switch (nLev)
+ {
+ case SC_DAPI_LEVEL_YEAR:
+ nGroupBy = sheet::DataPilotFieldGroupBy::YEARS;
+ break;
+ case SC_DAPI_LEVEL_QUARTER:
+ case SC_DAPI_LEVEL_WEEK:
+ nGroupBy = sheet::DataPilotFieldGroupBy::QUARTERS;
+ break;
+ case SC_DAPI_LEVEL_MONTH:
+ case SC_DAPI_LEVEL_WEEKDAY:
+ nGroupBy = sheet::DataPilotFieldGroupBy::MONTHS;
+ break;
+ case SC_DAPI_LEVEL_DAY:
+ nGroupBy = sheet::DataPilotFieldGroupBy::DAYS;
+ break;
+ default:
+ ;
+ }
+ if (aName.isEmpty())
+ aName = OUString::number(nVal);
+
+ ScDPItemData aData(nGroupBy, nVal);
+ SCROW nId = pSource->GetCache()->GetIdByItemData(nDim, aData);
+ pNew.set(new ScDPMember(pSource, nDim, nHier, nLev, nId));
+ }
+ else
+ {
+ const std::vector<SCROW>& memberIndexs = pSource->GetData()->GetColumnEntries(nSrcDim);
+ pNew.set(new ScDPMember(pSource, nDim, nHier, nLev, memberIndexs[nIndex]));
+ }
+ maMembers[nIndex] = pNew;
+ }
+
+ return maMembers[nIndex].get();
+ }
+
+ return nullptr; //TODO: exception?
+}
+
+ScDPMember::ScDPMember(
+ ScDPSource* pSrc, sal_Int32 nD, sal_Int32 nH, sal_Int32 nL, SCROW nIndex) :
+ pSource( pSrc ),
+ nDim( nD ),
+ nHier( nH ),
+ nLev( nL ),
+ mnDataId( nIndex ),
+ nPosition( -1 ),
+ bVisible( true ),
+ bShowDet( true )
+{
+ //TODO: hold pSource
+}
+
+ScDPMember::~ScDPMember()
+{
+ //TODO: release pSource
+}
+
+bool ScDPMember::IsNamedItem(SCROW nIndex) const
+{
+ sal_Int32 nSrcDim = pSource->GetSourceDim( nDim );
+ if ( nHier != SC_DAPI_HIERARCHY_FLAT && pSource->IsDateDimension( nSrcDim ) )
+ {
+ const ScDPItemData* pData = pSource->GetCache()->GetItemDataById(nDim, nIndex);
+ if (pData->IsValue())
+ {
+ tools::Long nComp = pSource->GetData()->GetDatePart(
+ static_cast<tools::Long>(::rtl::math::approxFloor( pData->GetValue() )),
+ nHier, nLev );
+ // fValue is converted from integer, so simple comparison works
+ const ScDPItemData* pData2 = GetItemData();
+ return pData2 && nComp == pData2->GetValue();
+ }
+ }
+
+ return nIndex == mnDataId;
+}
+
+sal_Int32 ScDPMember::Compare( const ScDPMember& rOther ) const
+{
+ if ( nPosition >= 0 )
+ {
+ if ( rOther.nPosition >= 0 )
+ {
+ OSL_ENSURE( nPosition != rOther.nPosition, "same position for two members" );
+ return ( nPosition < rOther.nPosition ) ? -1 : 1;
+ }
+ else
+ {
+ // only this has a position - members with specified positions come before those without
+ return -1;
+ }
+ }
+ else if ( rOther.nPosition >= 0 )
+ {
+ // only rOther has a position
+ return 1;
+ }
+
+ // no positions set - compare names
+ return pSource->GetData()->Compare( pSource->GetSourceDim(nDim),mnDataId,rOther.GetItemDataId());
+}
+
+ScDPItemData ScDPMember::FillItemData() const
+{
+ //TODO: handle date hierarchy...
+
+ const ScDPItemData* pData = GetItemData();
+ return (pData ? *pData : ScDPItemData());
+}
+
+const std::optional<OUString> & ScDPMember::GetLayoutName() const
+{
+ return mpLayoutName;
+}
+
+OUString ScDPMember::GetNameStr( bool bLocaleIndependent ) const
+{
+ const ScDPItemData* pData = GetItemData();
+ if (pData)
+ return pSource->GetData()->GetFormattedString(nDim, *pData, bLocaleIndependent);
+ return OUString();
+}
+
+OUString SAL_CALL ScDPMember::getName()
+{
+ return GetNameStr( false );
+}
+
+void SAL_CALL ScDPMember::setName( const OUString& /* rNewName */ )
+{
+ OSL_FAIL("not implemented"); //TODO: exception?
+}
+
+// XPropertySet
+
+uno::Reference<beans::XPropertySetInfo> SAL_CALL ScDPMember::getPropertySetInfo()
+{
+ static const SfxItemPropertyMapEntry aDPMemberMap_Impl[] =
+ {
+ { SC_UNO_DP_ISVISIBLE, 0, cppu::UnoType<bool>::get(), 0, 0 },
+ { SC_UNO_DP_POSITION, 0, cppu::UnoType<sal_Int32>::get(), 0, 0 },
+ { SC_UNO_DP_SHOWDETAILS, 0, cppu::UnoType<bool>::get(), 0, 0 },
+ { SC_UNO_DP_LAYOUTNAME, 0, cppu::UnoType<OUString>::get(), 0, 0 },
+ { u"", 0, css::uno::Type(), 0, 0 }
+ };
+ static uno::Reference<beans::XPropertySetInfo> aRef =
+ new SfxItemPropertySetInfo( aDPMemberMap_Impl );
+ return aRef;
+}
+
+void SAL_CALL ScDPMember::setPropertyValue( const OUString& aPropertyName, const uno::Any& aValue )
+{
+ if ( aPropertyName == SC_UNO_DP_ISVISIBLE )
+ bVisible = lcl_GetBoolFromAny(aValue);
+ else if ( aPropertyName == SC_UNO_DP_SHOWDETAILS )
+ bShowDet = lcl_GetBoolFromAny(aValue);
+ else if ( aPropertyName == SC_UNO_DP_POSITION )
+ aValue >>= nPosition;
+ else if (aPropertyName == SC_UNO_DP_LAYOUTNAME)
+ {
+ OUString aName;
+ if (aValue >>= aName)
+ mpLayoutName = aName;
+ }
+ else
+ {
+ OSL_FAIL("unknown property");
+ }
+}
+
+uno::Any SAL_CALL ScDPMember::getPropertyValue( const OUString& aPropertyName )
+{
+ uno::Any aRet;
+ if ( aPropertyName == SC_UNO_DP_ISVISIBLE )
+ aRet <<= bVisible;
+ else if ( aPropertyName == SC_UNO_DP_SHOWDETAILS )
+ aRet <<= bShowDet;
+ else if ( aPropertyName == SC_UNO_DP_POSITION )
+ aRet <<= nPosition;
+ else if (aPropertyName == SC_UNO_DP_LAYOUTNAME)
+ aRet <<= mpLayoutName ? *mpLayoutName : OUString();
+ else
+ {
+ OSL_FAIL("unknown property");
+ }
+ return aRet;
+}
+
+SC_IMPL_DUMMY_PROPERTY_LISTENER( ScDPMember )
+
+const ScDPCache* ScDPSource::GetCache()
+{
+ OSL_ENSURE( GetData() , "empty ScDPTableData pointer");
+ return ( GetData()!=nullptr ) ? &GetData()->GetCacheTable().getCache() : nullptr ;
+}
+
+const ScDPItemData* ScDPMember::GetItemData() const
+{
+ const ScDPItemData* pData = pSource->GetItemDataById(nDim, mnDataId);
+ SAL_WARN_IF( !pData, "sc.core", "ScDPMember::GetItemData: what data? nDim " << nDim << ", mnDataId " << mnDataId);
+ return pData;
+}
+
+const ScDPItemData* ScDPSource::GetItemDataById(sal_Int32 nDim, sal_Int32 nId)
+{
+ return GetData()->GetMemberById(nDim, nId);
+}
+
+const ScDPItemData* ScDPMembers::GetSrcItemDataByIndex(SCROW nIndex)
+{
+ const std::vector< SCROW >& memberIds = pSource->GetData()->GetColumnEntries( nDim );
+ if ( nIndex < 0 || o3tl::make_unsigned(nIndex) >= memberIds.size() )
+ return nullptr;
+ SCROW nId = memberIds[ nIndex ];
+ return pSource->GetItemDataById( nDim, nId );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/dputil.cxx b/sc/source/core/data/dputil.cxx
new file mode 100644
index 000000000..651d55093
--- /dev/null
+++ b/sc/source/core/data/dputil.cxx
@@ -0,0 +1,420 @@
+/* -*- 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 <dputil.hxx>
+#include <dpitemdata.hxx>
+#include <dpnumgroupinfo.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <generalfunction.hxx>
+
+#include <comphelper/string.hxx>
+#include <unotools/localedatawrapper.hxx>
+#include <unotools/calendarwrapper.hxx>
+#include <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+#include <rtl/math.hxx>
+#include <o3tl/string_view.hxx>
+#include <osl/diagnose.h>
+
+#include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp>
+#include <com/sun/star/i18n/CalendarDisplayIndex.hpp>
+
+using namespace com::sun::star;
+
+namespace {
+
+const sal_uInt16 SC_DP_LEAPYEAR = 1648; // arbitrary leap year for date calculations
+
+OUString getTwoDigitString(sal_Int32 nValue)
+{
+ OUString aRet = OUString::number( nValue );
+ if ( aRet.getLength() < 2 )
+ aRet = "0" + aRet;
+ return aRet;
+}
+
+void appendDateStr(OUStringBuffer& rBuffer, double fValue, SvNumberFormatter* pFormatter)
+{
+ sal_uInt32 nFormat = pFormatter->GetStandardFormat( SvNumFormatType::DATE, ScGlobal::eLnge );
+ OUString aString;
+ pFormatter->GetInputLineString(fValue, nFormat, aString);
+ rBuffer.append(aString);
+}
+
+OUString getSpecialDateName(double fValue, bool bFirst, SvNumberFormatter* pFormatter)
+{
+ OUStringBuffer aBuffer;
+ aBuffer.append( bFirst ? '<' : '>' );
+ appendDateStr(aBuffer, fValue, pFormatter);
+ return aBuffer.makeStringAndClear();
+}
+
+}
+
+bool ScDPUtil::isDuplicateDimension(std::u16string_view rName)
+{
+ return o3tl::ends_with(rName, u"*");
+}
+
+OUString ScDPUtil::getSourceDimensionName(std::u16string_view rName)
+{
+ return OUString(comphelper::string::stripEnd(rName, '*'));
+}
+
+sal_uInt8 ScDPUtil::getDuplicateIndex(const OUString& rName)
+{
+ // Count all trailing '*'s.
+
+ sal_Int32 n = rName.getLength();
+ if (!n)
+ return 0;
+
+ sal_uInt8 nDupCount = 0;
+ const sal_Unicode* p = rName.getStr();
+ const sal_Unicode* pStart = p;
+ p += n-1; // Set it to the last char.
+ for (; p != pStart; --p, ++nDupCount)
+ {
+ if (*p != '*')
+ break;
+ }
+
+ return nDupCount;
+}
+
+OUString ScDPUtil::createDuplicateDimensionName(const OUString& rOriginal, size_t nDupCount)
+{
+ if (!nDupCount)
+ return rOriginal;
+
+ OUStringBuffer aBuf(rOriginal);
+ for (size_t i = 0; i < nDupCount; ++i)
+ aBuf.append('*');
+
+ return aBuf.makeStringAndClear();
+}
+
+OUString ScDPUtil::getDateGroupName(
+ sal_Int32 nDatePart, sal_Int32 nValue, SvNumberFormatter* pFormatter,
+ double fStart, double fEnd)
+{
+ if (nValue == ScDPItemData::DateFirst)
+ return getSpecialDateName(fStart, true, pFormatter);
+ if (nValue == ScDPItemData::DateLast)
+ return getSpecialDateName(fEnd, false, pFormatter);
+
+ switch ( nDatePart )
+ {
+ case sheet::DataPilotFieldGroupBy::YEARS:
+ return OUString::number(nValue);
+ case sheet::DataPilotFieldGroupBy::QUARTERS:
+ return ScGlobal::getLocaleData().getQuarterAbbreviation(sal_Int16(nValue-1)); // nValue is 1-based
+ case css::sheet::DataPilotFieldGroupBy::MONTHS:
+ return ScGlobal::GetCalendar().getDisplayName(
+ i18n::CalendarDisplayIndex::MONTH, sal_Int16(nValue-1), 0); // 0-based, get short name
+ case sheet::DataPilotFieldGroupBy::DAYS:
+ {
+ Date aDate(1, 1, SC_DP_LEAPYEAR);
+ aDate.AddDays(nValue - 1); // nValue is 1-based
+ tools::Long nDays = aDate - pFormatter->GetNullDate();
+
+ const sal_uInt32 nFormat = pFormatter->GetFormatIndex(NF_DATE_SYS_DDMMM, ScGlobal::eLnge);
+ const Color* pColor;
+ OUString aStr;
+ pFormatter->GetOutputString(nDays, nFormat, aStr, &pColor);
+ return aStr;
+ }
+ case sheet::DataPilotFieldGroupBy::HOURS:
+ {
+ //TODO: allow am/pm format?
+ return getTwoDigitString(nValue);
+ }
+ break;
+ case sheet::DataPilotFieldGroupBy::MINUTES:
+ case sheet::DataPilotFieldGroupBy::SECONDS:
+ {
+ return ScGlobal::getLocaleData().getTimeSep() + getTwoDigitString(nValue);
+ }
+ break;
+ default:
+ OSL_FAIL("invalid date part");
+ }
+
+ return "FIXME: unhandled value";
+}
+
+double ScDPUtil::getNumGroupStartValue(double fValue, const ScDPNumGroupInfo& rInfo)
+{
+ if (fValue < rInfo.mfStart && !rtl::math::approxEqual(fValue, rInfo.mfStart))
+ return -std::numeric_limits<double>::infinity();
+
+ if (fValue > rInfo.mfEnd && !rtl::math::approxEqual(fValue, rInfo.mfEnd))
+ return std::numeric_limits<double>::infinity();
+
+ double fDiff = fValue - rInfo.mfStart;
+ double fDiv = rtl::math::approxFloor( fDiff / rInfo.mfStep );
+ double fGroupStart = rInfo.mfStart + fDiv * rInfo.mfStep;
+
+ if (rtl::math::approxEqual(fGroupStart, rInfo.mfEnd) &&
+ !rtl::math::approxEqual(fGroupStart, rInfo.mfStart))
+ {
+ if (!rInfo.mbDateValues)
+ {
+ // A group that would consist only of the end value is not
+ // created, instead the value is included in the last group
+ // before. So the previous group is used if the calculated group
+ // start value is the selected end value.
+
+ fDiv -= 1.0;
+ return rInfo.mfStart + fDiv * rInfo.mfStep;
+ }
+
+ // For date values, the end value is instead treated as above the
+ // limit if it would be a group of its own.
+
+ return rInfo.mfEnd + rInfo.mfStep;
+ }
+
+ return fGroupStart;
+}
+
+namespace {
+
+void lcl_AppendDateStr( OUStringBuffer& rBuffer, double fValue, SvNumberFormatter* pFormatter )
+{
+ sal_uInt32 nFormat = pFormatter->GetStandardFormat( SvNumFormatType::DATE, ScGlobal::eLnge );
+ OUString aString;
+ pFormatter->GetInputLineString( fValue, nFormat, aString );
+ rBuffer.append( aString );
+}
+
+OUString lcl_GetSpecialNumGroupName( double fValue, bool bFirst, sal_Unicode cDecSeparator,
+ bool bDateValues, SvNumberFormatter* pFormatter )
+{
+ OSL_ENSURE( cDecSeparator != 0, "cDecSeparator not initialized" );
+
+ OUStringBuffer aBuffer;
+ aBuffer.append( bFirst ? '<' : '>' );
+ if ( bDateValues )
+ lcl_AppendDateStr( aBuffer, fValue, pFormatter );
+ else
+ rtl::math::doubleToUStringBuffer( aBuffer, fValue, rtl_math_StringFormat_Automatic,
+ rtl_math_DecimalPlaces_Max, cDecSeparator, true );
+ return aBuffer.makeStringAndClear();
+}
+
+OUString lcl_GetNumGroupName(
+ double fStartValue, const ScDPNumGroupInfo& rInfo, sal_Unicode cDecSep,
+ SvNumberFormatter* pFormatter)
+{
+ OSL_ENSURE( cDecSep != 0, "cDecSeparator not initialized" );
+
+ double fStep = rInfo.mfStep;
+ double fEndValue = fStartValue + fStep;
+ if (rInfo.mbIntegerOnly && (rInfo.mbDateValues || !rtl::math::approxEqual(fEndValue, rInfo.mfEnd)))
+ {
+ // The second number of the group label is
+ // (first number + size - 1) if there are only integer numbers,
+ // (first number + size) if any non-integer numbers are involved.
+ // Exception: The last group (containing the end value) is always
+ // shown as including the end value (but not for dates).
+
+ fEndValue -= 1.0;
+ }
+
+ if ( fEndValue > rInfo.mfEnd && !rInfo.mbAutoEnd )
+ {
+ // limit the last group to the end value
+
+ fEndValue = rInfo.mfEnd;
+ }
+
+ OUStringBuffer aBuffer;
+ if ( rInfo.mbDateValues )
+ {
+ lcl_AppendDateStr( aBuffer, fStartValue, pFormatter );
+ aBuffer.append( " - " ); // with spaces
+ lcl_AppendDateStr( aBuffer, fEndValue, pFormatter );
+ }
+ else
+ {
+ rtl::math::doubleToUStringBuffer( aBuffer, fStartValue, rtl_math_StringFormat_Automatic,
+ rtl_math_DecimalPlaces_Max, cDecSep, true );
+ aBuffer.append( '-' );
+ rtl::math::doubleToUStringBuffer( aBuffer, fEndValue, rtl_math_StringFormat_Automatic,
+ rtl_math_DecimalPlaces_Max, cDecSep, true );
+ }
+
+ return aBuffer.makeStringAndClear();
+}
+
+}
+
+OUString ScDPUtil::getNumGroupName(
+ double fValue, const ScDPNumGroupInfo& rInfo, sal_Unicode cDecSep, SvNumberFormatter* pFormatter)
+{
+ if ( fValue < rInfo.mfStart && !rtl::math::approxEqual( fValue, rInfo.mfStart ) )
+ return lcl_GetSpecialNumGroupName( rInfo.mfStart, true, cDecSep, rInfo.mbDateValues, pFormatter );
+
+ if ( fValue > rInfo.mfEnd && !rtl::math::approxEqual( fValue, rInfo.mfEnd ) )
+ return lcl_GetSpecialNumGroupName( rInfo.mfEnd, false, cDecSep, rInfo.mbDateValues, pFormatter );
+
+ double fDiff = fValue - rInfo.mfStart;
+ double fDiv = rtl::math::approxFloor( fDiff / rInfo.mfStep );
+ double fGroupStart = rInfo.mfStart + fDiv * rInfo.mfStep;
+
+ if ( rtl::math::approxEqual( fGroupStart, rInfo.mfEnd ) &&
+ !rtl::math::approxEqual( fGroupStart, rInfo.mfStart ) )
+ {
+ if (rInfo.mbDateValues)
+ {
+ // For date values, the end value is instead treated as above the limit
+ // if it would be a group of its own.
+ return lcl_GetSpecialNumGroupName( rInfo.mfEnd, false, cDecSep, rInfo.mbDateValues, pFormatter );
+ }
+ }
+
+ return lcl_GetNumGroupName(fGroupStart, rInfo, cDecSep, pFormatter);
+}
+
+sal_Int32 ScDPUtil::getDatePartValue(
+ double fValue, const ScDPNumGroupInfo* pInfo, sal_Int32 nDatePart,
+ const SvNumberFormatter* pFormatter)
+{
+ // Start and end are inclusive
+ // (End date without a time value is included, with a time value it's not)
+
+ if (pInfo)
+ {
+ if (fValue < pInfo->mfStart && !rtl::math::approxEqual(fValue, pInfo->mfStart))
+ return ScDPItemData::DateFirst;
+ if (fValue > pInfo->mfEnd && !rtl::math::approxEqual(fValue, pInfo->mfEnd))
+ return ScDPItemData::DateLast;
+ }
+
+ sal_Int32 nResult = 0;
+
+ if (nDatePart == sheet::DataPilotFieldGroupBy::HOURS ||
+ nDatePart == sheet::DataPilotFieldGroupBy::MINUTES ||
+ nDatePart == sheet::DataPilotFieldGroupBy::SECONDS)
+ {
+ // handle time
+ // (do as in the cell functions, ScInterpreter::ScGetHour() etc.)
+
+ sal_uInt16 nHour, nMinute, nSecond;
+ double fFractionOfSecond;
+ tools::Time::GetClock( fValue, nHour, nMinute, nSecond, fFractionOfSecond, 0);
+
+ switch (nDatePart)
+ {
+ case sheet::DataPilotFieldGroupBy::HOURS:
+ nResult = nHour;
+ break;
+ case sheet::DataPilotFieldGroupBy::MINUTES:
+ nResult = nMinute;
+ break;
+ case sheet::DataPilotFieldGroupBy::SECONDS:
+ nResult = nSecond;
+ break;
+ }
+ }
+ else
+ {
+ Date aDate = pFormatter->GetNullDate();
+ aDate.AddDays(::rtl::math::approxFloor(fValue));
+
+ switch ( nDatePart )
+ {
+ case css::sheet::DataPilotFieldGroupBy::YEARS:
+ nResult = aDate.GetYear();
+ break;
+ case css::sheet::DataPilotFieldGroupBy::QUARTERS:
+ nResult = 1 + (aDate.GetMonth() - 1) / 3; // 1..4
+ break;
+ case css::sheet::DataPilotFieldGroupBy::MONTHS:
+ nResult = aDate.GetMonth(); // 1..12
+ break;
+ case css::sheet::DataPilotFieldGroupBy::DAYS:
+ {
+ Date aYearStart(1, 1, aDate.GetYear());
+ nResult = (aDate - aYearStart) + 1; // Jan 01 has value 1
+ if (nResult >= 60 && !aDate.IsLeapYear())
+ {
+ // days are counted from 1 to 366 - if not from a leap year, adjust
+ ++nResult;
+ }
+ }
+ break;
+ default:
+ OSL_FAIL("invalid date part");
+ }
+ }
+
+ return nResult;
+}
+
+namespace {
+
+const TranslateId aFuncStrIds[] = {
+ {}, // SUBTOTAL_FUNC_NONE
+ STR_FUN_TEXT_AVG, // SUBTOTAL_FUNC_AVE
+ STR_FUN_TEXT_COUNT, // SUBTOTAL_FUNC_CNT
+ STR_FUN_TEXT_COUNT, // SUBTOTAL_FUNC_CNT2
+ STR_FUN_TEXT_MAX, // SUBTOTAL_FUNC_MAX
+ STR_FUN_TEXT_MIN, // SUBTOTAL_FUNC_MIN
+ STR_FUN_TEXT_PRODUCT, // SUBTOTAL_FUNC_PROD
+ STR_FUN_TEXT_STDDEV, // SUBTOTAL_FUNC_STD
+ STR_FUN_TEXT_STDDEV, // SUBTOTAL_FUNC_STDP
+ STR_FUN_TEXT_SUM, // SUBTOTAL_FUNC_SUM
+ STR_FUN_TEXT_VAR, // SUBTOTAL_FUNC_VAR
+ STR_FUN_TEXT_VAR, // SUBTOTAL_FUNC_VARP
+ STR_FUN_TEXT_MEDIAN, // SUBTOTAL_FUNC_MED
+ {} // SUBTOTAL_FUNC_SELECTION_COUNT - not used for pivot table
+};
+
+}
+
+OUString ScDPUtil::getDisplayedMeasureName(const OUString& rName, ScSubTotalFunc eFunc)
+{
+ assert(unsigned(eFunc) < SAL_N_ELEMENTS(aFuncStrIds));
+ TranslateId pId = aFuncStrIds[eFunc];
+ if (!pId)
+ return rName;
+ return ScResId(pId) + // function name
+ " - " +
+ rName; // field name
+}
+
+ScSubTotalFunc ScDPUtil::toSubTotalFunc(ScGeneralFunction eGenFunc)
+{
+ ScSubTotalFunc eSubTotal = SUBTOTAL_FUNC_NONE;
+ switch (eGenFunc)
+ {
+ case ScGeneralFunction::NONE: eSubTotal = SUBTOTAL_FUNC_NONE; break;
+ case ScGeneralFunction::SUM: eSubTotal = SUBTOTAL_FUNC_SUM; break;
+ case ScGeneralFunction::COUNT: eSubTotal = SUBTOTAL_FUNC_CNT2; break;
+ case ScGeneralFunction::AVERAGE: eSubTotal = SUBTOTAL_FUNC_AVE; break;
+ case ScGeneralFunction::MEDIAN: eSubTotal = SUBTOTAL_FUNC_MED; break;
+ case ScGeneralFunction::MAX: eSubTotal = SUBTOTAL_FUNC_MAX; break;
+ case ScGeneralFunction::MIN: eSubTotal = SUBTOTAL_FUNC_MIN; break;
+ case ScGeneralFunction::PRODUCT: eSubTotal = SUBTOTAL_FUNC_PROD; break;
+ case ScGeneralFunction::COUNTNUMS: eSubTotal = SUBTOTAL_FUNC_CNT; break;
+ case ScGeneralFunction::STDEV: eSubTotal = SUBTOTAL_FUNC_STD; break;
+ case ScGeneralFunction::STDEVP: eSubTotal = SUBTOTAL_FUNC_STDP; break;
+ case ScGeneralFunction::VAR: eSubTotal = SUBTOTAL_FUNC_VAR; break;
+ case ScGeneralFunction::VARP: eSubTotal = SUBTOTAL_FUNC_VARP; break;
+ case ScGeneralFunction::AUTO: eSubTotal = SUBTOTAL_FUNC_NONE; break;
+ default:
+ assert(false);
+ }
+ return eSubTotal;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/drawpage.cxx b/sc/source/core/data/drawpage.cxx
new file mode 100644
index 000000000..6b6f029fb
--- /dev/null
+++ b/sc/source/core/data/drawpage.cxx
@@ -0,0 +1,51 @@
+/* -*- 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 <drawpage.hxx>
+#include <drwlayer.hxx>
+#include <pageuno.hxx>
+
+ScDrawPage::ScDrawPage(ScDrawLayer& rNewModel, bool bMasterPage)
+: FmFormPage(rNewModel, bMasterPage)
+{
+ SetSize( Size( SAL_MAX_INT32, SAL_MAX_INT32 ) );
+ // largest size supported by sal_Int32 SdrPage::mnWidth/Height
+}
+
+ScDrawPage::~ScDrawPage()
+{
+}
+
+rtl::Reference<SdrPage> ScDrawPage::CloneSdrPage(SdrModel& rTargetModel) const
+{
+ ScDrawLayer& rScDrawLayer(static_cast< ScDrawLayer& >(rTargetModel));
+ rtl::Reference<ScDrawPage> pClonedScDrawPage(
+ new ScDrawPage(
+ rScDrawLayer,
+ IsMasterPage()));
+ pClonedScDrawPage->FmFormPage::lateInit(*this);
+ return pClonedScDrawPage;
+}
+
+css::uno::Reference< css::uno::XInterface > ScDrawPage::createUnoPage()
+{
+ return static_cast<cppu::OWeakObject*>( new ScPageObj( this ) );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/drwlayer.cxx b/sc/source/core/data/drwlayer.cxx
new file mode 100644
index 000000000..3f41a017e
--- /dev/null
+++ b/sc/source/core/data/drwlayer.cxx
@@ -0,0 +1,2676 @@
+/* -*- 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/Reference.hxx>
+#include <com/sun/star/chart/XChartDocument.hpp>
+#include <com/sun/star/chart2/XChartDocument.hpp>
+#include <com/sun/star/embed/XClassifiedObject.hpp>
+#include <com/sun/star/embed/XEmbeddedObject.hpp>
+
+#include <scitems.hxx>
+#include <editeng/eeitem.hxx>
+#include <editeng/frmdiritem.hxx>
+#include <sot/exchange.hxx>
+#include <svx/objfac3d.hxx>
+#include <svx/xtable.hxx>
+#include <svx/svdoutl.hxx>
+#include <svx/svditer.hxx>
+#include <svx/svdlayer.hxx>
+#include <svx/svdoashp.hxx>
+#include <svx/svdobj.hxx>
+#include <svx/svdocapt.hxx>
+#include <svx/svdomeas.hxx>
+#include <svx/svdoole2.hxx>
+#include <svx/svdopath.hxx>
+#include <svx/svdundo.hxx>
+#include <svx/sdsxyitm.hxx>
+#include <svx/svxids.hrc>
+#include <i18nlangtag/mslangid.hxx>
+#include <editeng/unolingu.hxx>
+#include <svx/drawitem.hxx>
+#include <editeng/fhgtitem.hxx>
+#include <editeng/scriptspaceitem.hxx>
+#include <sfx2/objsh.hxx>
+#include <svl/itempool.hxx>
+#include <vcl/canvastools.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <tools/globname.hxx>
+#include <tools/UnitConversion.hxx>
+#include <osl/diagnose.h>
+
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+
+#include <drwlayer.hxx>
+#include <drawpage.hxx>
+#include <global.hxx>
+#include <document.hxx>
+#include <userdat.hxx>
+#include <markdata.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <scmod.hxx>
+#include <postit.hxx>
+#include <attrib.hxx>
+#include <charthelper.hxx>
+#include <table.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+
+#include <memory>
+#include <algorithm>
+#include <cstdlib>
+
+namespace com::sun::star::embed { class XEmbeddedObject; }
+
+#define DET_ARROW_OFFSET 1000
+
+using namespace ::com::sun::star;
+
+static E3dObjFactory* pF3d = nullptr;
+static sal_uInt16 nInst = 0;
+
+SfxObjectShell* ScDrawLayer::pGlobalDrawPersist = nullptr;
+
+bool bDrawIsInUndo = false; //TODO: Member
+
+ScUndoObjData::ScUndoObjData( SdrObject* pObjP, const ScAddress& rOS, const ScAddress& rOE,
+ const ScAddress& rNS, const ScAddress& rNE ) :
+ SdrUndoObj( *pObjP ),
+ aOldStt( rOS ),
+ aOldEnd( rOE ),
+ aNewStt( rNS ),
+ aNewEnd( rNE )
+{
+}
+
+ScUndoObjData::~ScUndoObjData()
+{
+}
+
+void ScUndoObjData::Undo()
+{
+ ScDrawObjData* pData = ScDrawLayer::GetObjData( pObj );
+ OSL_ENSURE(pData,"ScUndoObjData: Data missing");
+ if (pData)
+ {
+ pData->maStart = aOldStt;
+ pData->maEnd = aOldEnd;
+ }
+
+ // Undo also an untransformed anchor
+ pData = ScDrawLayer::GetNonRotatedObjData( pObj );
+ if (pData)
+ {
+ pData->maStart = aOldStt;
+ pData->maEnd = aOldEnd;
+ }
+}
+
+void ScUndoObjData::Redo()
+{
+ ScDrawObjData* pData = ScDrawLayer::GetObjData( pObj );
+ OSL_ENSURE(pData,"ScUndoObjData: Data missing");
+ if (pData)
+ {
+ pData->maStart = aNewStt;
+ pData->maEnd = aNewEnd;
+ }
+
+ // Redo also an untransformed anchor
+ pData = ScDrawLayer::GetNonRotatedObjData( pObj );
+ if (pData)
+ {
+ pData->maStart = aNewStt;
+ pData->maEnd = aNewEnd;
+ }
+}
+
+ScUndoAnchorData::ScUndoAnchorData( SdrObject* pObjP, ScDocument* pDoc, SCTAB nTab ) :
+ SdrUndoObj( *pObjP ),
+ mpDoc( pDoc ),
+ mnTab( nTab )
+{
+ mbWasCellAnchored = ScDrawLayer::IsCellAnchored( *pObjP );
+ mbWasResizeWithCell = ScDrawLayer::IsResizeWithCell( *pObjP );
+}
+
+ScUndoAnchorData::~ScUndoAnchorData()
+{
+}
+
+void ScUndoAnchorData::Undo()
+{
+ // Trigger Object Change
+ if (pObj->IsInserted() && pObj->getSdrPageFromSdrObject())
+ {
+ SdrHint aHint(SdrHintKind::ObjectChange, *pObj);
+ pObj->getSdrModelFromSdrObject().Broadcast(aHint);
+ }
+
+ if (mbWasCellAnchored)
+ ScDrawLayer::SetCellAnchoredFromPosition(*pObj, *mpDoc, mnTab, mbWasResizeWithCell);
+ else
+ ScDrawLayer::SetPageAnchored( *pObj );
+}
+
+void ScUndoAnchorData::Redo()
+{
+ if (mbWasCellAnchored)
+ ScDrawLayer::SetPageAnchored( *pObj );
+ else
+ ScDrawLayer::SetCellAnchoredFromPosition(*pObj, *mpDoc, mnTab, mbWasResizeWithCell);
+
+ // Trigger Object Change
+ if (pObj->IsInserted() && pObj->getSdrPageFromSdrObject())
+ {
+ SdrHint aHint(SdrHintKind::ObjectChange, *pObj);
+ pObj->getSdrModelFromSdrObject().Broadcast(aHint);
+ }
+}
+
+ScTabDeletedHint::ScTabDeletedHint( SCTAB nTabNo ) :
+ nTab( nTabNo )
+{
+}
+
+ScTabDeletedHint::~ScTabDeletedHint()
+{
+}
+
+ScTabSizeChangedHint::ScTabSizeChangedHint( SCTAB nTabNo ) :
+ nTab( nTabNo )
+{
+}
+
+ScTabSizeChangedHint::~ScTabSizeChangedHint()
+{
+}
+
+#define MAXMM 10000000
+
+
+static void lcl_ReverseTwipsToMM( tools::Rectangle& rRect )
+{
+ rRect = o3tl::convert(rRect, o3tl::Length::mm100, o3tl::Length::twip);
+}
+
+static ScRange lcl_getClipRangeFromClipDoc(ScDocument* pClipDoc, SCTAB nClipTab)
+{
+ if (!pClipDoc)
+ return ScRange();
+
+ SCCOL nClipStartX;
+ SCROW nClipStartY;
+ SCCOL nClipEndX;
+ SCROW nClipEndY;
+ pClipDoc->GetClipStart(nClipStartX, nClipStartY);
+ pClipDoc->GetClipArea(nClipEndX, nClipEndY, true);
+ nClipEndX = nClipEndX + nClipStartX;
+ nClipEndY += nClipStartY; // GetClipArea returns the difference
+
+ return ScRange(nClipStartX, nClipStartY, nClipTab, nClipEndX, nClipEndY, nClipTab);
+}
+
+ScDrawLayer::ScDrawLayer( ScDocument* pDocument, const OUString& rName ) :
+ FmFormModel(
+ nullptr,
+ pGlobalDrawPersist ? pGlobalDrawPersist : (pDocument ? pDocument->GetDocumentShell() : nullptr)),
+ aName( rName ),
+ pDoc( pDocument ),
+ bRecording( false ),
+ bAdjustEnabled( true ),
+ bHyphenatorSet( false )
+{
+ SetVOCInvalidationIsReliable(true);
+
+ pGlobalDrawPersist = nullptr; // Only use once
+
+ SfxObjectShell* pObjSh = pDocument ? pDocument->GetDocumentShell() : nullptr;
+ XColorListRef pXCol = XColorList::GetStdColorList();
+ if ( pObjSh )
+ {
+ SetObjectShell( pObjSh );
+
+ // set color table
+ const SvxColorListItem* pColItem = pObjSh->GetItem( SID_COLOR_TABLE );
+ if ( pColItem )
+ pXCol = pColItem->GetColorList();
+ }
+ SetPropertyList( static_cast<XPropertyList *> (pXCol.get()) );
+
+ SetSwapGraphics();
+
+ SetScaleUnit(MapUnit::Map100thMM);
+ SfxItemPool& rPool = GetItemPool();
+ rPool.SetDefaultMetric(MapUnit::Map100thMM);
+ SvxFrameDirectionItem aModeItem( SvxFrameDirection::Environment, EE_PARA_WRITINGDIR );
+ rPool.SetPoolDefaultItem( aModeItem );
+
+ // #i33700#
+ // Set shadow distance defaults as PoolDefaultItems. Details see bug.
+ rPool.SetPoolDefaultItem(makeSdrShadowXDistItem(300));
+ rPool.SetPoolDefaultItem(makeSdrShadowYDistItem(300));
+
+ // default for script spacing depends on locale, see SdDrawDocument ctor in sd
+ LanguageType eOfficeLanguage = Application::GetSettings().GetLanguageTag().getLanguageType();
+ if (MsLangId::isKorean(eOfficeLanguage) || eOfficeLanguage == LANGUAGE_JAPANESE)
+ {
+ // secondary is edit engine pool
+ rPool.GetSecondaryPool()->SetPoolDefaultItem( SvxScriptSpaceItem( false, EE_PARA_ASIANCJKSPACING ) );
+ }
+
+ rPool.FreezeIdRanges(); // the pool is also used directly
+
+ SdrLayerAdmin& rAdmin = GetLayerAdmin();
+ rAdmin.NewLayer("vorne", SC_LAYER_FRONT.get());
+ rAdmin.NewLayer("hinten", SC_LAYER_BACK.get());
+ rAdmin.NewLayer("intern", SC_LAYER_INTERN.get());
+ // tdf#140252 use same name as in ctor of SdrLayerAdmin
+ rAdmin.NewLayer(rAdmin.GetControlLayerName(), SC_LAYER_CONTROLS.get());
+ rAdmin.NewLayer("hidden", SC_LAYER_HIDDEN.get());
+
+ // Set link for URL-Fields
+ ScModule* pScMod = SC_MOD();
+ Outliner& rOutliner = GetDrawOutliner();
+ rOutliner.SetCalcFieldValueHdl( LINK( pScMod, ScModule, CalcFieldValueHdl ) );
+
+ Outliner& rHitOutliner = GetHitTestOutliner();
+ rHitOutliner.SetCalcFieldValueHdl( LINK( pScMod, ScModule, CalcFieldValueHdl ) );
+
+ // set FontHeight pool defaults without changing static SdrEngineDefaults
+ SfxItemPool* pOutlinerPool = rOutliner.GetEditTextObjectPool();
+ if ( pOutlinerPool )
+ {
+ m_pItemPool->SetPoolDefaultItem(SvxFontHeightItem( 423, 100, EE_CHAR_FONTHEIGHT )); // 12Pt
+ m_pItemPool->SetPoolDefaultItem(SvxFontHeightItem( 423, 100, EE_CHAR_FONTHEIGHT_CJK )); // 12Pt
+ m_pItemPool->SetPoolDefaultItem(SvxFontHeightItem( 423, 100, EE_CHAR_FONTHEIGHT_CTL )); // 12Pt
+ }
+ SfxItemPool* pHitOutlinerPool = rHitOutliner.GetEditTextObjectPool();
+ if ( pHitOutlinerPool )
+ {
+ pHitOutlinerPool->SetPoolDefaultItem(SvxFontHeightItem( 423, 100, EE_CHAR_FONTHEIGHT )); // 12Pt
+ pHitOutlinerPool->SetPoolDefaultItem(SvxFontHeightItem( 423, 100, EE_CHAR_FONTHEIGHT_CJK )); // 12Pt
+ pHitOutlinerPool->SetPoolDefaultItem(SvxFontHeightItem( 423, 100, EE_CHAR_FONTHEIGHT_CTL )); // 12Pt
+ }
+
+ // initial undo mode as in Calc document
+ if( pDoc )
+ EnableUndo( pDoc->IsUndoEnabled() );
+
+ // URL-Buttons have no handler anymore, all is done by themselves
+
+ if( !nInst++ )
+ {
+ pF3d = new E3dObjFactory;
+ }
+}
+
+ScDrawLayer::~ScDrawLayer()
+{
+ Broadcast(SdrHint(SdrHintKind::ModelCleared));
+
+ ClearModel(true);
+
+ pUndoGroup.reset();
+ if( !--nInst )
+ {
+ delete pF3d;
+ pF3d = nullptr;
+ }
+}
+
+void ScDrawLayer::UseHyphenator()
+{
+ if (!bHyphenatorSet)
+ {
+ css::uno::Reference< css::linguistic2::XHyphenator >
+ xHyphenator = LinguMgr::GetHyphenator();
+
+ GetDrawOutliner().SetHyphenator( xHyphenator );
+ GetHitTestOutliner().SetHyphenator( xHyphenator );
+
+ bHyphenatorSet = true;
+ }
+}
+
+rtl::Reference<SdrPage> ScDrawLayer::AllocPage(bool bMasterPage)
+{
+ return new ScDrawPage(*this, bMasterPage);
+}
+
+bool ScDrawLayer::HasObjects() const
+{
+ bool bFound = false;
+
+ sal_uInt16 nCount = GetPageCount();
+ for (sal_uInt16 i=0; i<nCount && !bFound; i++)
+ if (GetPage(i)->GetObjCount())
+ bFound = true;
+
+ return bFound;
+}
+
+SdrModel* ScDrawLayer::AllocModel() const
+{
+ // Allocated model (for clipboard etc) must not have a pointer
+ // to the original model's document, pass NULL as document:
+
+ return new ScDrawLayer( nullptr, aName );
+}
+
+bool ScDrawLayer::ScAddPage( SCTAB nTab )
+{
+ if (bDrawIsInUndo)
+ return false; // not inserted
+
+ rtl::Reference<ScDrawPage> pPage = static_cast<ScDrawPage*>(AllocPage( false ).get());
+ InsertPage(pPage.get(), static_cast<sal_uInt16>(nTab));
+ if (bRecording)
+ AddCalcUndo(std::make_unique<SdrUndoNewPage>(*pPage));
+
+ ResetTab(nTab, pDoc->GetTableCount()-1);
+ return true; // inserted
+}
+
+void ScDrawLayer::ScRemovePage( SCTAB nTab )
+{
+ if (bDrawIsInUndo)
+ return;
+
+ Broadcast( ScTabDeletedHint( nTab ) );
+ if (bRecording)
+ {
+ SdrPage* pPage = GetPage(static_cast<sal_uInt16>(nTab));
+ AddCalcUndo(std::make_unique<SdrUndoDelPage>(*pPage)); // Undo-Action becomes the page owner
+ RemovePage( static_cast<sal_uInt16>(nTab) ); // just deliver, not deleting
+ }
+ else
+ DeletePage( static_cast<sal_uInt16>(nTab) ); // just get rid of it
+
+ ResetTab(nTab, pDoc->GetTableCount()-1);
+}
+
+void ScDrawLayer::ScRenamePage( SCTAB nTab, const OUString& rNewName )
+{
+ ScDrawPage* pPage = static_cast<ScDrawPage*>( GetPage(static_cast<sal_uInt16>(nTab)) );
+ if (pPage)
+ pPage->SetName(rNewName);
+}
+
+void ScDrawLayer::ScMovePage( sal_uInt16 nOldPos, sal_uInt16 nNewPos )
+{
+ MovePage( nOldPos, nNewPos );
+ sal_uInt16 nMinPos = std::min(nOldPos, nNewPos);
+ ResetTab(nMinPos, pDoc->GetTableCount()-1);
+}
+
+void ScDrawLayer::ScCopyPage( sal_uInt16 nOldPos, sal_uInt16 nNewPos )
+{
+ if (bDrawIsInUndo)
+ return;
+
+ SdrPage* pOldPage = GetPage(nOldPos);
+ SdrPage* pNewPage = GetPage(nNewPos);
+
+ // Copying
+
+ if (pOldPage && pNewPage)
+ {
+ SCTAB nOldTab = static_cast<SCTAB>(nOldPos);
+ SCTAB nNewTab = static_cast<SCTAB>(nNewPos);
+
+ SdrObjListIter aIter( pOldPage, SdrIterMode::Flat );
+ SdrObject* pOldObject = aIter.Next();
+ while (pOldObject)
+ {
+ ScDrawObjData* pOldData = GetObjData(pOldObject);
+ if (pOldData)
+ {
+ pOldData->maStart.SetTab(nOldTab);
+ pOldData->maEnd.SetTab(nOldTab);
+ }
+
+ // Clone to target SdrModel
+ SdrObject* pNewObject(pOldObject->CloneSdrObject(*this));
+ pNewObject->NbcMove(Size(0,0));
+ pNewPage->InsertObject( pNewObject );
+ ScDrawObjData* pNewData = GetObjData(pNewObject);
+ if (pNewData)
+ {
+ pNewData->maStart.SetTab(nNewTab);
+ pNewData->maEnd.SetTab(nNewTab);
+ }
+
+ if (bRecording)
+ AddCalcUndo( std::make_unique<SdrUndoInsertObj>( *pNewObject ) );
+
+ pOldObject = aIter.Next();
+ }
+ }
+
+ ResetTab(static_cast<SCTAB>(nNewPos), pDoc->GetTableCount()-1);
+}
+
+void ScDrawLayer::ResetTab( SCTAB nStart, SCTAB nEnd )
+{
+ SCTAB nPageSize = static_cast<SCTAB>(GetPageCount());
+ if (nPageSize < 0)
+ // No drawing pages exist.
+ return;
+
+ if (nEnd >= nPageSize)
+ // Avoid iterating beyond the last existing page.
+ nEnd = nPageSize - 1;
+
+ for (SCTAB i = nStart; i <= nEnd; ++i)
+ {
+ SdrPage* pPage = GetPage(static_cast<sal_uInt16>(i));
+ if (!pPage)
+ continue;
+
+ SdrObjListIter aIter(pPage, SdrIterMode::Flat);
+ for (SdrObject* pObj = aIter.Next(); pObj; pObj = aIter.Next())
+ {
+ ScDrawObjData* pData = GetObjData(pObj);
+ if (!pData)
+ continue;
+
+ pData->maStart.SetTab(i);
+ pData->maEnd.SetTab(i);
+ }
+ }
+}
+
+static bool IsInBlock( const ScAddress& rPos, SCCOL nCol1,SCROW nRow1, SCCOL nCol2,SCROW nRow2 )
+{
+ return rPos.Col() >= nCol1 && rPos.Col() <= nCol2 &&
+ rPos.Row() >= nRow1 && rPos.Row() <= nRow2;
+}
+
+void ScDrawLayer::MoveCells( SCTAB nTab, SCCOL nCol1,SCROW nRow1, SCCOL nCol2,SCROW nRow2,
+ SCCOL nDx,SCROW nDy, bool bUpdateNoteCaptionPos )
+{
+ SdrPage* pPage = GetPage(static_cast<sal_uInt16>(nTab));
+ OSL_ENSURE(pPage,"Page not found");
+ if (!pPage)
+ return;
+
+ bool bNegativePage = pDoc && pDoc->IsNegativePage( nTab );
+
+ const size_t nCount = pPage->GetObjCount();
+ for ( size_t i = 0; i < nCount; ++i )
+ {
+ SdrObject* pObj = pPage->GetObj( i );
+ ScDrawObjData* pData = GetObjDataTab( pObj, nTab );
+ if( pData )
+ {
+ const ScAddress aOldStt = pData->maStart;
+ const ScAddress aOldEnd = pData->maEnd;
+ bool bChange = false;
+ if ( aOldStt.IsValid() && IsInBlock( aOldStt, nCol1,nRow1, nCol2,nRow2 ) )
+ {
+ pData->maStart.IncCol( nDx );
+ pData->maStart.IncRow( nDy );
+ bChange = true;
+ }
+ if ( aOldEnd.IsValid() && IsInBlock( aOldEnd, nCol1,nRow1, nCol2,nRow2 ) )
+ {
+ pData->maEnd.IncCol( nDx );
+ pData->maEnd.IncRow( nDy );
+ bChange = true;
+ }
+ if (bChange)
+ {
+ if ( dynamic_cast<const SdrRectObj*>( pObj) != nullptr && pData->maStart.IsValid() && pData->maEnd.IsValid() )
+ pData->maStart.PutInOrder( pData->maEnd );
+
+ // Update also an untransformed anchor that's what we stored ( and still do ) to xml
+ ScDrawObjData* pNoRotatedAnchor = GetNonRotatedObjData( pObj );
+ if ( pNoRotatedAnchor )
+ {
+ const ScAddress aOldSttNoRotatedAnchor = pNoRotatedAnchor->maStart;
+ const ScAddress aOldEndNoRotatedAnchor = pNoRotatedAnchor->maEnd;
+ if ( aOldSttNoRotatedAnchor.IsValid() && IsInBlock( aOldSttNoRotatedAnchor, nCol1,nRow1, nCol2,nRow2 ) )
+ {
+ pNoRotatedAnchor->maStart.IncCol(nDx);
+ pNoRotatedAnchor->maStart.IncRow(nDy);
+ }
+ if ( aOldEndNoRotatedAnchor.IsValid() && IsInBlock( aOldEndNoRotatedAnchor, nCol1,nRow1, nCol2,nRow2 ) )
+ {
+ pNoRotatedAnchor->maEnd.IncCol(nDx);
+ pNoRotatedAnchor->maEnd.IncRow(nDy);
+ }
+ }
+
+ AddCalcUndo( std::make_unique<ScUndoObjData>( pObj, aOldStt, aOldEnd, pData->maStart, pData->maEnd ) );
+ RecalcPos( pObj, *pData, bNegativePage, bUpdateNoteCaptionPos );
+ }
+ }
+ }
+}
+
+void ScDrawLayer::SetPageSize(sal_uInt16 nPageNo, const Size& rSize, bool bUpdateNoteCaptionPos,
+ const ScObjectHandling eObjectHandling)
+{
+ SdrPage* pPage = GetPage(nPageNo);
+ if (!pPage)
+ return;
+
+ if ( rSize != pPage->GetSize() )
+ {
+ pPage->SetSize( rSize );
+ Broadcast( ScTabSizeChangedHint( static_cast<SCTAB>(nPageNo) ) ); // SetWorkArea() on the views
+ }
+
+ // Do not call RecalcPos while loading, because row height is not finished, when SetPageSize
+ // is called first time. Instead the objects are initialized from ScXMLImport::endDocument() and
+ // RecalcPos is called from there.
+ if (!pDoc || pDoc->IsImportingXML())
+ return;
+
+ // Implement Detective lines (adjust to new heights / widths)
+ // even if size is still the same
+ // (individual rows/columns can have been changed))
+
+ bool bNegativePage = pDoc && pDoc->IsNegativePage( static_cast<SCTAB>(nPageNo) );
+
+ // Disable mass broadcasts from drawing objects' position changes.
+ bool bWasLocked = isLocked();
+ setLock(true);
+
+ const size_t nCount = pPage->GetObjCount();
+ for ( size_t i = 0; i < nCount; ++i )
+ {
+ SdrObject* pObj = pPage->GetObj( i );
+ ScDrawObjData* pData = GetObjDataTab( pObj, static_cast<SCTAB>(nPageNo) );
+ if( pData ) // cell anchored
+ {
+ if (pData->meType == ScDrawObjData::DrawingObject
+ || pData->meType == ScDrawObjData::ValidationCircle)
+ {
+ switch (eObjectHandling)
+ {
+ case ScObjectHandling::RecalcPosMode:
+ RecalcPos(pObj, *pData, bNegativePage, bUpdateNoteCaptionPos);
+ break;
+ case ScObjectHandling::MoveRTLMode:
+ MoveRTL(pObj);
+ break;
+ case ScObjectHandling::MirrorRTLMode:
+ MirrorRTL(pObj);
+ break;
+ }
+ }
+ else // DetectiveArrow and CellNote
+ RecalcPos(pObj, *pData, bNegativePage, bUpdateNoteCaptionPos);
+ }
+ else // page anchored
+ {
+ switch (eObjectHandling)
+ {
+ case ScObjectHandling::MoveRTLMode:
+ MoveRTL(pObj);
+ break;
+ case ScObjectHandling::MirrorRTLMode:
+ MirrorRTL(pObj);
+ break;
+ case ScObjectHandling::RecalcPosMode: // does not occur for page anchored shapes
+ break;
+ }
+ }
+ }
+
+ setLock(bWasLocked);
+}
+
+namespace
+{
+ //Can't have a zero width dimension
+ tools::Rectangle lcl_makeSafeRectangle(const tools::Rectangle &rNew)
+ {
+ tools::Rectangle aRect = rNew;
+ if (aRect.Bottom() == aRect.Top())
+ aRect.SetBottom( aRect.Top()+1 );
+ if (aRect.Right() == aRect.Left())
+ aRect.SetRight( aRect.Left()+1 );
+ return aRect;
+ }
+
+ Point lcl_calcAvailableDiff(const ScDocument &rDoc, SCCOL nCol, SCROW nRow, SCTAB nTab, const Point &aWantedDiff)
+ {
+ Point aAvailableDiff(aWantedDiff);
+ tools::Long nHeight = o3tl::convert(rDoc.GetRowHeight( nRow, nTab ), o3tl::Length::twip, o3tl::Length::mm100);
+ tools::Long nWidth = o3tl::convert(rDoc.GetColWidth( nCol, nTab ), o3tl::Length::twip, o3tl::Length::mm100);
+ if (aAvailableDiff.Y() > nHeight)
+ aAvailableDiff.setY( nHeight );
+ if (aAvailableDiff.X() > nWidth)
+ aAvailableDiff.setX( nWidth );
+ return aAvailableDiff;
+ }
+
+ tools::Rectangle lcl_UpdateCalcPoly(basegfx::B2DPolygon &rCalcPoly, int nWhichPoint, const Point &rPos)
+ {
+ rCalcPoly.setB2DPoint(nWhichPoint, basegfx::B2DPoint(rPos.X(), rPos.Y()));
+ basegfx::B2DRange aRange(basegfx::utils::getRange(rCalcPoly));
+ return tools::Rectangle(static_cast<tools::Long>(aRange.getMinX()), static_cast<tools::Long>(aRange.getMinY()),
+ static_cast<tools::Long>(aRange.getMaxX()), static_cast<tools::Long>(aRange.getMaxY()));
+ }
+
+bool lcl_AreRectanglesApproxEqual(const tools::Rectangle& rRectA, const tools::Rectangle& rRectB)
+{
+ // Twips <-> Hmm conversions introduce +-1 differences although there are no real changes in the object.
+ // Therefore test with == is not appropriate in some cases.
+ if (std::abs(rRectA.Left() - rRectB.Left()) > 1)
+ return false;
+ if (std::abs(rRectA.Top() - rRectB.Top()) > 1)
+ return false;
+ if (std::abs(rRectA.Right() - rRectB.Right()) > 1)
+ return false;
+ if (std::abs(rRectA.Bottom() - rRectB.Bottom()) > 1)
+ return false;
+ return true;
+}
+
+bool lcl_NeedsMirrorYCorrection(const SdrObject* pObj)
+{
+ return pObj->GetObjIdentifier() == SdrObjKind::CustomShape
+ && static_cast<const SdrObjCustomShape*>(pObj)->IsMirroredY();
+}
+
+void lcl_SetLogicRectFromAnchor(SdrObject* pObj, const ScDrawObjData& rAnchor, const ScDocument* pDoc)
+{
+ // This is only used during initialization. At that time, shape handling is always LTR. No need
+ // to consider negative page.
+ if (!pObj || !pDoc || !rAnchor.maEnd.IsValid() || !rAnchor.maStart.IsValid())
+ return;
+
+ // In case of a vertical mirrored custom shape, LibreOffice uses internally an additional 180deg
+ // in aGeo.nRotationAngle and in turn has a different logic rectangle position. We remove flip,
+ // set the logic rectangle, and apply flip again. You cannot simple use a 180deg-rotated
+ // rectangle, because custom shape mirroring is internally applied after the other
+ // transformations.
+ const bool bNeedsMirrorYCorrection = lcl_NeedsMirrorYCorrection(pObj); // remember state
+ if (bNeedsMirrorYCorrection)
+ {
+ const tools::Rectangle aRect(pObj->GetSnapRect());
+ const Point aLeft(aRect.Left(), (aRect.Top() + aRect.Bottom()) >> 1);
+ const Point aRight(aLeft.X() + 1000, aLeft.Y());
+ pObj->NbcMirror(aLeft, aRight);
+ }
+
+ // Build full sized logic rectangle from start and end given in anchor.
+ const tools::Rectangle aStartCellRect(
+ pDoc->GetMMRect(rAnchor.maStart.Col(), rAnchor.maStart.Row(), rAnchor.maStart.Col(),
+ rAnchor.maStart.Row(), rAnchor.maStart.Tab(), false /*bHiddenAsZero*/));
+ Point aStartPoint(aStartCellRect.Left(), aStartCellRect.Top());
+ aStartPoint.AdjustX(rAnchor.maStartOffset.getX());
+ aStartPoint.AdjustY(rAnchor.maStartOffset.getY());
+
+ const tools::Rectangle aEndCellRect(
+ pDoc->GetMMRect(rAnchor.maEnd.Col(), rAnchor.maEnd.Row(), rAnchor.maEnd.Col(),
+ rAnchor.maEnd.Row(), rAnchor.maEnd.Tab(), false /*bHiddenAsZero*/));
+ Point aEndPoint(aEndCellRect.Left(), aEndCellRect.Top());
+ aEndPoint.AdjustX(rAnchor.maEndOffset.getX());
+ aEndPoint.AdjustY(rAnchor.maEndOffset.getY());
+
+ // Set this as new, full sized logical rectangle
+ tools::Rectangle aNewRectangle(aStartPoint, aEndPoint);
+ aNewRectangle.Justify();
+ if (!lcl_AreRectanglesApproxEqual(pObj->GetLogicRect(), aNewRectangle))
+ pObj->NbcSetLogicRect(lcl_makeSafeRectangle(aNewRectangle));
+
+ // The shape has the correct logical rectangle now. Reapply the above removed mirroring.
+ if (bNeedsMirrorYCorrection)
+ {
+ const tools::Rectangle aRect(pObj->GetSnapRect());
+ const Point aLeft(aRect.Left(), (aRect.Top() + aRect.Bottom()) >> 1);
+ const Point aRight(aLeft.X() + 1000, aLeft.Y());
+ pObj->NbcMirror(aLeft, aRight);
+ }
+}
+
+} // namespace
+
+void ScDrawLayer::ResizeLastRectFromAnchor(const SdrObject* pObj, ScDrawObjData& rData,
+ bool bNegativePage, bool bCanResize)
+{
+ tools::Rectangle aRect = pObj->GetSnapRect();
+ SCCOL nCol1 = rData.maStart.Col();
+ SCROW nRow1 = rData.maStart.Row();
+ SCTAB nTab1 = rData.maStart.Tab();
+ SCCOL nCol2 = rData.maEnd.Col();
+ SCROW nRow2 = rData.maEnd.Row();
+ SCTAB nTab2 = rData.maEnd.Tab();
+ Point aPos(pDoc->GetColOffset(nCol1, nTab1, /*bHiddenAsZero*/true),
+ pDoc->GetRowOffset(nRow1, nTab1, /*bHiddenAsZero*/true));
+ aPos.setX(convertTwipToMm100(aPos.X()));
+ aPos.setY(convertTwipToMm100(aPos.Y()));
+ aPos += lcl_calcAvailableDiff(*pDoc, nCol1, nRow1, nTab1, rData.maStartOffset);
+
+ // this sets the needed changed position (translation)
+ aRect.SetPos(aPos);
+
+ if (bCanResize)
+ {
+ // all this stuff is additional stuff to evtl. not only translate the
+ // range (Rectangle), but also check for and evtl. do corrections for it's size
+ const tools::Rectangle aLastCellRect(rData.getLastCellRect());
+
+ // If the row was hidden before, or we don't have a valid cell rect, calculate the
+ // new rect based on the end point.
+ // Also when the end point is set, we need to consider it.
+ if (rData.mbWasInHiddenRow || aLastCellRect.IsEmpty() || nRow1 != nRow2 || nCol1 != nCol2)
+ {
+ Point aEnd(pDoc->GetColOffset(nCol2, nTab2, /*bHiddenAsZero*/true),
+ pDoc->GetRowOffset(nRow2, nTab2, /*bHiddenAsZero*/true));
+ aEnd.setX(convertTwipToMm100(aEnd.X()));
+ aEnd.setY(convertTwipToMm100(aEnd.Y()));
+ aEnd += lcl_calcAvailableDiff(*pDoc, nCol2, nRow2, nTab2, rData.maEndOffset);
+
+ aRect = tools::Rectangle(aPos, aEnd);
+ }
+ else if (!aLastCellRect.IsEmpty())
+ {
+ // We calculate based on the last cell rect to be able to scale the image
+ // as much as the cell was scaled.
+ // Still, we keep the image in its current cell (to keep start anchor == end anchor)
+ const tools::Rectangle aCurrentCellRect(GetCellRect(*GetDocument(), rData.maStart, true));
+ tools::Long nCurrentWidth(aCurrentCellRect.GetWidth());
+ tools::Long nCurrentHeight(aCurrentCellRect.GetHeight());
+ const tools::Long nLastWidth(aLastCellRect.GetWidth());
+ const tools::Long nLastHeight(aLastCellRect.GetHeight());
+
+ // tdf#116931 Avoid and correct nifty numerical problems with the integer
+ // based and converted values (GetCellRect uses multiplies with HMM_PER_TWIPS)
+ if(nCurrentWidth + 1 == nLastWidth || nCurrentWidth == nLastWidth + 1)
+ {
+ nCurrentWidth = nLastWidth;
+ }
+
+ if(nCurrentHeight + 1 == nLastHeight || nCurrentHeight == nLastHeight + 1)
+ {
+ nCurrentHeight = nLastHeight;
+ }
+
+ // get initial ScalingFactors
+ double fWidthFactor(nCurrentWidth == nLastWidth || 0 == nLastWidth
+ ? 1.0
+ : static_cast<double>(nCurrentWidth) / static_cast<double>(nLastWidth));
+ double fHeightFactor(nCurrentHeight == nLastHeight || 0 == nLastHeight
+ ? 1.0
+ : static_cast<double>(nCurrentHeight) / static_cast<double>(nLastHeight));
+
+ // check if we grow or shrink - and at all
+ const bool bIsGrowing(nCurrentWidth > nLastWidth || nCurrentHeight > nLastHeight);
+ const bool bIsShrinking(nCurrentWidth < nLastWidth || nCurrentHeight < nLastHeight);
+ const bool bIsSizeChanged(bIsGrowing || bIsShrinking);
+
+ // handle AspectRatio, only needed if size does change
+ if(bIsSizeChanged && pObj->shouldKeepAspectRatio())
+ {
+ tools::Rectangle aRectIncludingOffset = aRect;
+ aRectIncludingOffset.setWidth(aRect.GetWidth() + rData.maStartOffset.X());
+ aRectIncludingOffset.setHeight(aRect.GetHeight() + rData.maStartOffset.Y());
+ tools::Long nWidth = aRectIncludingOffset.GetWidth();
+ assert(nWidth && "div-by-zero");
+ double fMaxWidthFactor = static_cast<double>(nCurrentWidth)
+ / static_cast<double>(nWidth);
+ tools::Long nHeight = aRectIncludingOffset.GetHeight();
+ assert(nHeight && "div-by-zero");
+ double fMaxHeightFactor = static_cast<double>(nCurrentHeight)
+ / static_cast<double>(nHeight);
+ double fMaxFactor = std::min(fMaxHeightFactor, fMaxWidthFactor);
+
+ if(bIsGrowing) // cell is growing larger
+ {
+ // To actually grow the image, we need to take the max
+ fWidthFactor = std::max(fWidthFactor, fHeightFactor);
+ }
+ else if(bIsShrinking) // cell is growing smaller, take the min
+ {
+ fWidthFactor = std::min(fWidthFactor, fHeightFactor);
+ }
+
+ // We don't want the image to become larger than the current cell
+ fWidthFactor = fHeightFactor = std::min(fWidthFactor, fMaxFactor);
+ }
+
+ if(bIsSizeChanged)
+ {
+ // tdf#116931 re-organized scaling (if needed)
+ // Check if we need to scale at all. Always scale on growing.
+ bool bNeedToScale(bIsGrowing);
+
+ if(!bNeedToScale && bIsShrinking)
+ {
+ // Check if original still fits into space. Do *not* forget to
+ // compare with evtl. numerically corrected aCurrentCellRect
+ const bool bFitsInX(aRect.Right() <= aCurrentCellRect.Left() + nCurrentWidth);
+ const bool bFitsInY(aRect.Bottom() <= aCurrentCellRect.Top() + nCurrentHeight);
+
+ // If the image still fits in the smaller cell, don't resize it at all
+ bNeedToScale = (!bFitsInX || !bFitsInY);
+ }
+
+ if(bNeedToScale)
+ {
+ // tdf#116931 use transformations now. Translation is already applied
+ // (see aRect.SetPos above), so only scale needs to be applied - relative
+ // to *new* CellRect (which is aCurrentCellRect).
+ // Prepare scale relative to top-left of aCurrentCellRect
+ basegfx::B2DHomMatrix aChange;
+
+ aChange.translate(-aCurrentCellRect.Left(), -aCurrentCellRect.Top());
+ aChange.scale(fWidthFactor, fHeightFactor);
+ aChange.translate(aCurrentCellRect.Left(), aCurrentCellRect.Top());
+
+ // create B2DRange and transform by prepared scale
+ basegfx::B2DRange aNewRange = vcl::unotools::b2DRectangleFromRectangle(aRect);
+
+ aNewRange.transform(aChange);
+
+ // apply to aRect
+ aRect = tools::Rectangle(
+ basegfx::fround(aNewRange.getMinX()), basegfx::fround(aNewRange.getMinY()),
+ basegfx::fround(aNewRange.getMaxX()), basegfx::fround(aNewRange.getMaxY()));
+ }
+ }
+ }
+ }
+
+ if (bNegativePage)
+ MirrorRectRTL(aRect);
+
+ rData.setShapeRect(GetDocument(), lcl_makeSafeRectangle(aRect), pObj->IsVisible());
+}
+
+void ScDrawLayer::InitializeCellAnchoredObj(SdrObject* pObj, ScDrawObjData& rData)
+{
+ // This is called from ScXMLImport::endDocument()
+ if (!pDoc || !pObj)
+ return;
+ if (!rData.getShapeRect().IsEmpty())
+ return; // already initialized, should not happen
+ if (rData.meType == ScDrawObjData::CellNote || rData.meType == ScDrawObjData::ValidationCircle
+ || rData.meType == ScDrawObjData::DetectiveArrow)
+ return; // handled in RecalcPos
+
+ // Prevent multiple broadcasts during the series of changes.
+ bool bWasLocked = pObj->getSdrModelFromSdrObject().isLocked();
+ pObj->getSdrModelFromSdrObject().setLock(true);
+
+ // rNoRotatedAnchor refers in its start and end addresses and its start and end offsets to
+ // the logic rectangle of the object. The values are so, as if no hidden columns and rows
+ // exists and if it is a LTR sheet. These values are directly used for XML in ODF file.
+ ScDrawObjData& rNoRotatedAnchor = *GetNonRotatedObjData(pObj, true /*bCreate*/);
+
+ // From XML import, rData contains temporarily the anchor information as they are given in
+ // XML. Copy it to rNoRotatedAnchor, where it belongs. rData will later contain the anchor
+ // of the transformed object as visible on screen.
+ rNoRotatedAnchor.maStart = rData.maStart;
+ rNoRotatedAnchor.maEnd = rData.maEnd;
+ rNoRotatedAnchor.maStartOffset = rData.maStartOffset;
+ rNoRotatedAnchor.maEndOffset = rData.maEndOffset;
+
+ SCCOL nCol1 = rNoRotatedAnchor.maStart.Col();
+ SCROW nRow1 = rNoRotatedAnchor.maStart.Row();
+ SCTAB nTab1 = rNoRotatedAnchor.maStart.Tab(); // Used as parameter several times
+
+ // Object has coordinates relative to left/top of containing cell in XML. Change object to
+ // absolute coordinates as internally used.
+ const tools::Rectangle aRect(
+ pDoc->GetMMRect(nCol1, nRow1, nCol1, nRow1, nTab1, false /*bHiddenAsZero*/));
+ const Size aShift(aRect.Left(), aRect.Top());
+ pObj->NbcMove(aShift);
+
+ const ScAnchorType aAnchorType = ScDrawLayer::GetAnchorType(*pObj);
+ if (aAnchorType == SCA_CELL_RESIZE)
+ {
+ if (pObj->GetObjIdentifier() == SdrObjKind::Line)
+ {
+ // Horizontal lines might have wrong start and end anchor because of erroneously applied
+ // 180deg rotation (tdf#137446). Other lines have wrong end anchor. Coordinates in
+ // object are correct. Use them for recreating the anchor.
+ const basegfx::B2DPolygon aPoly(
+ static_cast<SdrPathObj*>(pObj)->GetPathPoly().getB2DPolygon(0));
+ const basegfx::B2DPoint aB2DPoint0(aPoly.getB2DPoint(0));
+ const basegfx::B2DPoint aB2DPoint1(aPoly.getB2DPoint(1));
+ const Point aPointLT(FRound(std::min(aB2DPoint0.getX(), aB2DPoint1.getX())),
+ FRound(std::min(aB2DPoint0.getY(), aB2DPoint1.getY())));
+ const Point aPointRB(FRound(std::max(aB2DPoint0.getX(), aB2DPoint1.getX())),
+ FRound(std::max(aB2DPoint0.getY(), aB2DPoint1.getY())));
+ const tools::Rectangle aObjRect(aPointLT, aPointRB);
+ GetCellAnchorFromPosition(aObjRect, rNoRotatedAnchor, *pDoc, nTab1,
+ false /*bHiddenAsZero*/);
+ }
+ else if (pObj->GetObjIdentifier() == SdrObjKind::Measure)
+ {
+ // Measure lines might have got wrong start and end anchor from XML import. Recreate
+ // anchor from start and end point.
+ SdrMeasureObj* pMeasureObj = static_cast<SdrMeasureObj*>(pObj);
+ // tdf#137576. The logic rectangle has likely no current values here, but only the
+ // 1cm x 1cm default size. The call of TakeUnrotatedSnapRect is currently (LO 7.2)
+ // the only way to force a recalc of the logic rectangle.
+ tools::Rectangle aObjRect;
+ pMeasureObj->TakeUnrotatedSnapRect(aObjRect);
+ GetCellAnchorFromPosition(aObjRect, rNoRotatedAnchor, *pDoc, rData.maStart.Tab(),
+ false /*bHiddenAsZero*/);
+ }
+ else
+ {
+ // In case there are hidden rows or cols, versions 7.0 and earlier have written width and
+ // height in file so that hidden row or col are count as zero. XML import bases the
+ // logical rectangle of the object on it. Shapes have at least wrong size, when row or col
+ // are shown. We try to regenerate the logic rectangle as far as possible from the anchor.
+ // ODF specifies anyway, that the size has to be ignored, if end cell attributes exist.
+ lcl_SetLogicRectFromAnchor(pObj, rNoRotatedAnchor, pDoc);
+ }
+ }
+ else // aAnchorType == SCA_CELL, other types will not occur here.
+ {
+ // XML has no end cell address in this case. We generate it from position.
+ UpdateCellAnchorFromPositionEnd(*pObj, rNoRotatedAnchor, *pDoc, nTab1,
+ true /*bUseLogicRect*/);
+ }
+
+ // Make sure maShapeRect of rNoRotatedAnchor is not empty. Method ScDrawView::Notify()
+ // needs it to detect a change in object geometry. For example a 180deg rotation effects only
+ // logic rect.
+ rNoRotatedAnchor.setShapeRect(GetDocument(), pObj->GetLogicRect(), true);
+
+ // Start and end addresses and offsets in rData refer to the actual snap rectangle of the
+ // shape. We initialize them here based on the "full" sized object. Adaptation to reduced size
+ // (by hidden row/col) is done later in RecalcPos.
+ GetCellAnchorFromPosition(pObj->GetSnapRect(), rData, *pDoc, nTab1, false /*bHiddenAsZero*/);
+
+ // As of ODF 1.3 strict there is no attribute to store whether an object is hidden. So a "visible"
+ // object might actually be hidden by being in hidden row or column. We detect it here.
+ // Note, that visibility by hidden row or column refers to the snap rectangle.
+ if (pObj->IsVisible()
+ && (pDoc->RowHidden(rData.maStart.Row(), rData.maStart.Tab())
+ || pDoc->ColHidden(rData.maStart.Col(), rData.maStart.Tab())))
+ pObj->SetVisible(false);
+
+ // Set visibility. ToDo: Really used?
+ rNoRotatedAnchor.setShapeRect(GetDocument(), pObj->GetLogicRect(), pObj->IsVisible());
+
+ // And set maShapeRect in rData. It stores not only the current rectangles, but currently,
+ // existence of maShapeRect is the flag for initialization is done.
+ rData.setShapeRect(GetDocument(), pObj->GetSnapRect(), pObj->IsVisible());
+
+ pObj->getSdrModelFromSdrObject().setLock(bWasLocked);
+}
+
+void ScDrawLayer::RecalcPos( SdrObject* pObj, ScDrawObjData& rData, bool bNegativePage, bool bUpdateNoteCaptionPos )
+{
+ OSL_ENSURE( pDoc, "ScDrawLayer::RecalcPos - missing document" );
+ if( !pDoc )
+ return;
+
+ if (rData.meType == ScDrawObjData::CellNote)
+ {
+ OSL_ENSURE( rData.maStart.IsValid(), "ScDrawLayer::RecalcPos - invalid position for cell note" );
+ /* #i109372# On insert/remove rows/columns/cells: Updating the caption
+ position must not be done, if the cell containing the note has not
+ been moved yet in the document. The calling code now passes an
+ additional boolean stating if the cells are already moved. */
+ /* tdf #152081 Do not change hidden objects. That would produce zero height
+ or width and loss of caption.*/
+ if (bUpdateNoteCaptionPos && pObj->IsVisible())
+ {
+ /* When inside an undo action, there may be pending note captions
+ where cell note is already deleted (thus document cannot find
+ the note object anymore). The caption will be deleted later
+ with drawing undo. */
+ if( ScPostIt* pNote = pDoc->GetNote( rData.maStart ) )
+ pNote->UpdateCaptionPos( rData.maStart );
+ }
+ return;
+ }
+
+ bool bValid1 = rData.maStart.IsValid();
+ SCCOL nCol1 = rData.maStart.Col();
+ SCROW nRow1 = rData.maStart.Row();
+ SCTAB nTab1 = rData.maStart.Tab();
+ bool bValid2 = rData.maEnd.IsValid();
+ SCCOL nCol2 = rData.maEnd.Col();
+ SCROW nRow2 = rData.maEnd.Row();
+ SCTAB nTab2 = rData.maEnd.Tab();
+
+ if (rData.meType == ScDrawObjData::ValidationCircle)
+ {
+ // Validation circle for detective.
+ rData.setShapeRect(GetDocument(), pObj->GetLogicRect());
+
+ // rData.maStart should contain the address of the be validated cell.
+ tools::Rectangle aRect = GetCellRect(*GetDocument(), rData.maStart, true);
+ aRect.AdjustLeft( -250 );
+ aRect.AdjustRight(250 );
+ aRect.AdjustTop( -70 );
+ aRect.AdjustBottom(70 );
+ if ( bNegativePage )
+ MirrorRectRTL( aRect );
+
+ if ( pObj->GetLogicRect() != aRect )
+ {
+ if (bRecording)
+ AddCalcUndo( std::make_unique<SdrUndoGeoObj>( *pObj ) );
+ rData.setShapeRect(GetDocument(), lcl_makeSafeRectangle(aRect));
+ // maStart has the meaning of "to be validated cell" in a validation circle. For usual
+ // drawing objects it has the meaning "left/top of logic/snap rect". Because the rectangle
+ // is expanded above, SetLogicRect() will set maStart to one cell left and one cell above
+ // of the to be validated cell. We need to backup the old value and restore it.
+ ScAddress aBackup(rData.maStart);
+ pObj->SetLogicRect(rData.getShapeRect());
+ rData.maStart = aBackup;
+ }
+ }
+ else if (rData.meType == ScDrawObjData::DetectiveArrow)
+ {
+ rData.setShapeRect(GetDocument(), pObj->GetLogicRect());
+ basegfx::B2DPolygon aCalcPoly;
+ Point aOrigStartPos(pObj->GetPoint(0));
+ Point aOrigEndPos(pObj->GetPoint(1));
+ aCalcPoly.append(basegfx::B2DPoint(aOrigStartPos.X(), aOrigStartPos.Y()));
+ aCalcPoly.append(basegfx::B2DPoint(aOrigEndPos.X(), aOrigEndPos.Y()));
+ //TODO: do not create multiple Undos for one object (last one can be omitted then)
+
+ SCCOL nLastCol;
+ SCROW nLastRow;
+ if( bValid1 )
+ {
+ Point aPos( pDoc->GetColOffset( nCol1, nTab1 ), pDoc->GetRowOffset( nRow1, nTab1 ) );
+ if (!pDoc->ColHidden(nCol1, nTab1, nullptr, &nLastCol))
+ aPos.AdjustX(pDoc->GetColWidth( nCol1, nTab1 ) / 4 );
+ if (!pDoc->RowHidden(nRow1, nTab1, nullptr, &nLastRow))
+ aPos.AdjustY(pDoc->GetRowHeight( nRow1, nTab1 ) / 2 );
+ aPos.setX(convertTwipToMm100(aPos.X()));
+ aPos.setY(convertTwipToMm100(aPos.Y()));
+ Point aStartPos = aPos;
+ if ( bNegativePage )
+ aStartPos.setX( -aStartPos.X() ); // don't modify aPos - used below
+ if ( pObj->GetPoint( 0 ) != aStartPos )
+ {
+ if (bRecording)
+ AddCalcUndo( std::make_unique<SdrUndoGeoObj>( *pObj ) );
+
+ rData.setShapeRect(GetDocument(), lcl_UpdateCalcPoly(aCalcPoly, 0, aStartPos));
+ pObj->SetPoint( aStartPos, 0 );
+ }
+
+ if( !bValid2 )
+ {
+ Point aEndPos( aPos.X() + DET_ARROW_OFFSET, aPos.Y() - DET_ARROW_OFFSET );
+ if (aEndPos.Y() < 0)
+ aEndPos.AdjustY(2 * DET_ARROW_OFFSET);
+ if ( bNegativePage )
+ aEndPos.setX( -aEndPos.X() );
+ if ( pObj->GetPoint( 1 ) != aEndPos )
+ {
+ if (bRecording)
+ AddCalcUndo( std::make_unique<SdrUndoGeoObj>( *pObj ) );
+
+ rData.setShapeRect(GetDocument(), lcl_UpdateCalcPoly(aCalcPoly, 1, aEndPos));
+ pObj->SetPoint( aEndPos, 1 );
+ }
+ }
+ }
+ if( bValid2 )
+ {
+ Point aPos( pDoc->GetColOffset( nCol2, nTab2 ), pDoc->GetRowOffset( nRow2, nTab2 ) );
+ if (!pDoc->ColHidden(nCol2, nTab2, nullptr, &nLastCol))
+ aPos.AdjustX(pDoc->GetColWidth( nCol2, nTab2 ) / 4 );
+ if (!pDoc->RowHidden(nRow2, nTab2, nullptr, &nLastRow))
+ aPos.AdjustY(pDoc->GetRowHeight( nRow2, nTab2 ) / 2 );
+ aPos.setX(convertTwipToMm100(aPos.X()));
+ aPos.setY(convertTwipToMm100(aPos.Y()));
+ Point aEndPos = aPos;
+ if ( bNegativePage )
+ aEndPos.setX( -aEndPos.X() ); // don't modify aPos - used below
+ if ( pObj->GetPoint( 1 ) != aEndPos )
+ {
+ if (bRecording)
+ AddCalcUndo( std::make_unique<SdrUndoGeoObj>( *pObj ) );
+
+ rData.setShapeRect(GetDocument(), lcl_UpdateCalcPoly(aCalcPoly, 1, aEndPos));
+ pObj->SetPoint( aEndPos, 1 );
+ }
+
+ if( !bValid1 )
+ {
+ Point aStartPos( aPos.X() - DET_ARROW_OFFSET, aPos.Y() - DET_ARROW_OFFSET );
+ if (aStartPos.X() < 0)
+ aStartPos.AdjustX(2 * DET_ARROW_OFFSET);
+ if (aStartPos.Y() < 0)
+ aStartPos.AdjustY(2 * DET_ARROW_OFFSET);
+ if ( bNegativePage )
+ aStartPos.setX( -aStartPos.X() );
+ if ( pObj->GetPoint( 0 ) != aStartPos )
+ {
+ if (bRecording)
+ AddCalcUndo( std::make_unique<SdrUndoGeoObj>( *pObj ) );
+
+ rData.setShapeRect(GetDocument(), lcl_UpdateCalcPoly(aCalcPoly, 0, aStartPos));
+ pObj->SetPoint( aStartPos, 0 );
+ }
+ }
+ }
+ } // end ScDrawObjData::DetectiveArrow
+ else // start ScDrawObjData::DrawingObject
+ {
+ // Do not change hidden objects. That would produce zero height or width and loss of offsets.
+ if (!pObj->IsVisible())
+ return;
+
+ // Prevent multiple broadcasts during the series of changes.
+ bool bWasLocked = pObj->getSdrModelFromSdrObject().isLocked();
+ pObj->getSdrModelFromSdrObject().setLock(true);
+
+ bool bCanResize = bValid2 && !pObj->IsResizeProtect() && rData.mbResizeWithCell;
+
+ // update anchor with snap rect
+ ResizeLastRectFromAnchor( pObj, rData, bNegativePage, bCanResize );
+
+ ScDrawObjData& rNoRotatedAnchor = *GetNonRotatedObjData( pObj, true /*bCreate*/);
+
+ if( bCanResize )
+ {
+ tools::Rectangle aNew = rData.getShapeRect();
+ tools::Rectangle aOld(pObj->GetSnapRect());
+ if (!lcl_AreRectanglesApproxEqual(aNew, aOld))
+ {
+ if (bRecording)
+ AddCalcUndo( std::make_unique<SdrUndoGeoObj>( *pObj ) );
+
+ // ToDo: Implement NbcSetSnapRect of SdrMeasureObj. Then this can be removed.
+ tools::Long nOldWidth = aOld.GetWidth();
+ tools::Long nOldHeight = aOld.GetHeight();
+ if (pObj->IsPolyObj() && nOldWidth && nOldHeight)
+ {
+ // Polyline objects need special treatment.
+ Size aSizeMove(aNew.Left()-aOld.Left(), aNew.Top()-aOld.Top());
+ pObj->NbcMove(aSizeMove);
+
+ double fXFrac = static_cast<double>(aNew.GetWidth()) / static_cast<double>(nOldWidth);
+ double fYFrac = static_cast<double>(aNew.GetHeight()) / static_cast<double>(nOldHeight);
+ pObj->NbcResize(aNew.TopLeft(), Fraction(fXFrac), Fraction(fYFrac));
+ }
+
+ rData.setShapeRect(GetDocument(), lcl_makeSafeRectangle(rData.getShapeRect()), pObj->IsVisible());
+ if (pObj->GetObjIdentifier() == SdrObjKind::CustomShape)
+ pObj->AdjustToMaxRect(rData.getShapeRect());
+ else
+ pObj->SetSnapRect(rData.getShapeRect());
+
+ // The shape rectangle in the 'unrotated' anchor needs to be updated to the changed
+ // object geometry. It is used in adjustAnchoredPosition() in ScDrawView::Notify().
+ rNoRotatedAnchor.setShapeRect(pDoc, pObj->GetLogicRect(), pObj->IsVisible());
+ }
+ }
+ else
+ {
+ const Point aPos(rData.getShapeRect().TopLeft());
+ if ( pObj->GetRelativePos() != aPos )
+ {
+ if (bRecording)
+ AddCalcUndo( std::make_unique<SdrUndoGeoObj>( *pObj ) );
+ pObj->SetRelativePos( aPos );
+ rNoRotatedAnchor.setShapeRect(pDoc, pObj->GetLogicRect(), pObj->IsVisible());
+ }
+ }
+ /*
+ * If we were not allowed resize the object, then the end cell anchor
+ * is possibly incorrect now, and if the object has no end-cell (e.g.
+ * missing in original .xml) we are also forced to generate one
+ */
+ bool bEndAnchorIsBad = !bValid2 || pObj->IsResizeProtect();
+ if (bEndAnchorIsBad)
+ {
+ // update 'rotated' anchor
+ ScDrawLayer::UpdateCellAnchorFromPositionEnd(*pObj, rData, *pDoc, nTab1, false);
+ // update 'unrotated' anchor
+ ScDrawLayer::UpdateCellAnchorFromPositionEnd(*pObj, rNoRotatedAnchor, *pDoc, nTab1 );
+ }
+
+ // End prevent multiple broadcasts during the series of changes.
+ pObj->getSdrModelFromSdrObject().setLock(bWasLocked);
+ if (!bWasLocked)
+ pObj->BroadcastObjectChange();
+ } // end ScDrawObjData::DrawingObject
+}
+
+bool ScDrawLayer::GetPrintArea( ScRange& rRange, bool bSetHor, bool bSetVer ) const
+{
+ OSL_ENSURE( pDoc, "ScDrawLayer::GetPrintArea without document" );
+ if ( !pDoc )
+ return false;
+
+ SCTAB nTab = rRange.aStart.Tab();
+ OSL_ENSURE( rRange.aEnd.Tab() == nTab, "GetPrintArea: Tab differ" );
+
+ bool bNegativePage = pDoc->IsNegativePage( nTab );
+
+ bool bAny = false;
+ tools::Long nEndX = 0;
+ tools::Long nEndY = 0;
+ tools::Long nStartX = LONG_MAX;
+ tools::Long nStartY = LONG_MAX;
+
+ // Calculate borders
+
+ if (!bSetHor)
+ {
+ nStartX = 0;
+ SCCOL nStartCol = rRange.aStart.Col();
+ SCCOL i;
+ for (i=0; i<nStartCol; i++)
+ nStartX +=pDoc->GetColWidth(i,nTab);
+ nEndX = nStartX;
+ SCCOL nEndCol = rRange.aEnd.Col();
+ for (i=nStartCol; i<=nEndCol; i++)
+ nEndX += pDoc->GetColWidth(i,nTab);
+ nStartX = convertTwipToMm100(nStartX);
+ nEndX = convertTwipToMm100(nEndX);
+ }
+ if (!bSetVer)
+ {
+ nStartY = pDoc->GetRowHeight( 0, rRange.aStart.Row()-1, nTab);
+ nEndY = nStartY + pDoc->GetRowHeight( rRange.aStart.Row(),
+ rRange.aEnd.Row(), nTab);
+ nStartY = convertTwipToMm100(nStartY);
+ nEndY = convertTwipToMm100(nEndY);
+ }
+
+ if ( bNegativePage )
+ {
+ nStartX = -nStartX; // positions are negative, swap start/end so the same comparisons work
+ nEndX = -nEndX;
+ ::std::swap( nStartX, nEndX );
+ }
+
+ const SdrPage* pPage = GetPage(static_cast<sal_uInt16>(nTab));
+ OSL_ENSURE(pPage,"Page not found");
+ if (pPage)
+ {
+ SdrObjListIter aIter( pPage, SdrIterMode::Flat );
+ SdrObject* pObject = aIter.Next();
+ while (pObject)
+ {
+ //TODO: test Flags (hidden?)
+
+ tools::Rectangle aObjRect = pObject->GetCurrentBoundRect();
+ bool bFit = true;
+ if ( !bSetHor && ( aObjRect.Right() < nStartX || aObjRect.Left() > nEndX ) )
+ bFit = false;
+ if ( !bSetVer && ( aObjRect.Bottom() < nStartY || aObjRect.Top() > nEndY ) )
+ bFit = false;
+ // #i104716# don't include hidden note objects
+ if ( bFit && pObject->GetLayer() != SC_LAYER_HIDDEN )
+ {
+ if (bSetHor)
+ {
+ if (aObjRect.Left() < nStartX) nStartX = aObjRect.Left();
+ if (aObjRect.Right() > nEndX) nEndX = aObjRect.Right();
+ }
+ if (bSetVer)
+ {
+ if (aObjRect.Top() < nStartY) nStartY = aObjRect.Top();
+ if (aObjRect.Bottom() > nEndY) nEndY = aObjRect.Bottom();
+ }
+ bAny = true;
+ }
+
+ pObject = aIter.Next();
+ }
+ }
+
+ if ( bNegativePage )
+ {
+ nStartX = -nStartX; // reverse transformation, so the same cell address calculation works
+ nEndX = -nEndX;
+ ::std::swap( nStartX, nEndX );
+ }
+
+ if (bAny)
+ {
+ OSL_ENSURE( nStartX<=nEndX && nStartY<=nEndY, "Start/End wrong in ScDrawLayer::GetPrintArea" );
+
+ if (bSetHor)
+ {
+ nStartX = o3tl::toTwips(nStartX, o3tl::Length::mm100);
+ nEndX = o3tl::toTwips(nEndX, o3tl::Length::mm100);
+ tools::Long nWidth;
+
+ nWidth = 0;
+ rRange.aStart.SetCol( 0 );
+ if (nWidth <= nStartX)
+ {
+ for (SCCOL nCol : pDoc->GetColumnsRange(nTab, 0, pDoc->MaxCol()))
+ {
+ nWidth += pDoc->GetColWidth(nCol,nTab);
+ if (nWidth > nStartX)
+ {
+ rRange.aStart.SetCol( nCol );
+ break;
+ }
+ }
+ }
+
+ nWidth = 0;
+ rRange.aEnd.SetCol( 0 );
+ if (nWidth <= nEndX)
+ {
+ for (SCCOL nCol : pDoc->GetColumnsRange(nTab, 0, pDoc->MaxCol())) //TODO: start at Start
+ {
+ nWidth += pDoc->GetColWidth(nCol,nTab);
+ if (nWidth > nEndX)
+ {
+ rRange.aEnd.SetCol( nCol );
+ break;
+ }
+ }
+ }
+ }
+
+ if (bSetVer)
+ {
+ nStartY = o3tl::toTwips(nStartY, o3tl::Length::mm100);
+ nEndY = o3tl::toTwips(nEndY, o3tl::Length::mm100);
+ SCROW nRow = pDoc->GetRowForHeight( nTab, nStartY);
+ rRange.aStart.SetRow( nRow>0 ? (nRow-1) : 0);
+ nRow = pDoc->GetRowForHeight( nTab, nEndY);
+ rRange.aEnd.SetRow( nRow == pDoc->MaxRow() ? pDoc->MaxRow() :
+ (nRow>0 ? (nRow-1) : 0));
+ }
+ }
+ else
+ {
+ if (bSetHor)
+ {
+ rRange.aStart.SetCol(0);
+ rRange.aEnd.SetCol(0);
+ }
+ if (bSetVer)
+ {
+ rRange.aStart.SetRow(0);
+ rRange.aEnd.SetRow(0);
+ }
+ }
+ return bAny;
+}
+
+void ScDrawLayer::AddCalcUndo( std::unique_ptr<SdrUndoAction> pUndo )
+{
+ if (bRecording)
+ {
+ if (!pUndoGroup)
+ pUndoGroup.reset(new SdrUndoGroup(*this));
+
+ pUndoGroup->AddAction( std::move(pUndo) );
+ }
+}
+
+void ScDrawLayer::BeginCalcUndo(bool bDisableTextEditUsesCommonUndoManager)
+{
+ SetDisableTextEditUsesCommonUndoManager(bDisableTextEditUsesCommonUndoManager);
+ pUndoGroup.reset();
+ bRecording = true;
+}
+
+std::unique_ptr<SdrUndoGroup> ScDrawLayer::GetCalcUndo()
+{
+ std::unique_ptr<SdrUndoGroup> pRet = std::move(pUndoGroup);
+ bRecording = false;
+ SetDisableTextEditUsesCommonUndoManager(false);
+ return pRet;
+}
+
+void ScDrawLayer::MoveArea( SCTAB nTab, SCCOL nCol1,SCROW nRow1, SCCOL nCol2,SCROW nRow2,
+ SCCOL nDx,SCROW nDy, bool bInsDel, bool bUpdateNoteCaptionPos )
+{
+ OSL_ENSURE( pDoc, "ScDrawLayer::MoveArea without document" );
+ if ( !pDoc )
+ return;
+
+ if (!bAdjustEnabled)
+ return;
+
+ bool bNegativePage = pDoc->IsNegativePage( nTab );
+
+ tools::Rectangle aRect = pDoc->GetMMRect( nCol1, nRow1, nCol2, nRow2, nTab );
+ lcl_ReverseTwipsToMM( aRect );
+ //TODO: use twips directly?
+
+ Point aMove;
+
+ if (nDx > 0)
+ for (SCCOL s=0; s<nDx; s++)
+ aMove.AdjustX(pDoc->GetColWidth(s+nCol1,nTab) );
+ else
+ for (SCCOL s=-1; s>=nDx; s--)
+ aMove.AdjustX( -(pDoc->GetColWidth(s+nCol1,nTab)) );
+ if (nDy > 0)
+ aMove.AdjustY(pDoc->GetRowHeight( nRow1, nRow1+nDy-1, nTab) );
+ else
+ aMove.AdjustY( -sal_Int16(pDoc->GetRowHeight( nRow1+nDy, nRow1-1, nTab)) );
+
+ if ( bNegativePage )
+ aMove.setX( -aMove.X() );
+
+ Point aTopLeft = aRect.TopLeft(); // Beginning when zoomed out
+ if (bInsDel)
+ {
+ if ( aMove.X() != 0 && nDx < 0 ) // nDx counts cells, sign is independent of RTL
+ aTopLeft.AdjustX(aMove.X() );
+ if ( aMove.Y() < 0 )
+ aTopLeft.AdjustY(aMove.Y() );
+ }
+
+ // Detectiv arrows: Adjust cell position
+
+ MoveCells( nTab, nCol1,nRow1, nCol2,nRow2, nDx,nDy, bUpdateNoteCaptionPos );
+}
+
+bool ScDrawLayer::HasObjectsInRows( SCTAB nTab, SCROW nStartRow, SCROW nEndRow )
+{
+ OSL_ENSURE( pDoc, "ScDrawLayer::HasObjectsInRows without document" );
+ if ( !pDoc )
+ return false;
+
+ SdrPage* pPage = GetPage(static_cast<sal_uInt16>(nTab));
+ OSL_ENSURE(pPage,"Page not found");
+ if (!pPage)
+ return false;
+
+ // for an empty page, there's no need to calculate the row heights
+ if (!pPage->GetObjCount())
+ return false;
+
+ tools::Rectangle aTestRect;
+
+ aTestRect.AdjustTop(pDoc->GetRowHeight( 0, nStartRow-1, nTab) );
+
+ if (nEndRow==pDoc->MaxRow())
+ aTestRect.SetBottom( MAXMM );
+ else
+ {
+ aTestRect.SetBottom( aTestRect.Top() );
+ aTestRect.AdjustBottom(pDoc->GetRowHeight( nStartRow, nEndRow, nTab) );
+ aTestRect.SetBottom(convertTwipToMm100(aTestRect.Bottom()));
+ }
+
+ aTestRect.SetTop(convertTwipToMm100(aTestRect.Top()));
+
+ aTestRect.SetLeft( 0 );
+ aTestRect.SetRight( MAXMM );
+
+ bool bNegativePage = pDoc->IsNegativePage( nTab );
+ if ( bNegativePage )
+ MirrorRectRTL( aTestRect );
+
+ bool bFound = false;
+
+ tools::Rectangle aObjRect;
+ SdrObjListIter aIter( pPage );
+ SdrObject* pObject = aIter.Next();
+ while ( pObject && !bFound )
+ {
+ aObjRect = pObject->GetSnapRect(); //TODO: GetLogicRect ?
+ if (aTestRect.Contains(aObjRect.TopLeft()) || aTestRect.Contains(aObjRect.BottomLeft()))
+ bFound = true;
+
+ pObject = aIter.Next();
+ }
+
+ return bFound;
+}
+
+void ScDrawLayer::DeleteObjectsInArea( SCTAB nTab, SCCOL nCol1,SCROW nRow1,
+ SCCOL nCol2,SCROW nRow2, bool bAnchored )
+{
+ OSL_ENSURE( pDoc, "ScDrawLayer::DeleteObjectsInArea without document" );
+ if ( !pDoc )
+ return;
+
+ SdrPage* pPage = GetPage(static_cast<sal_uInt16>(nTab));
+ OSL_ENSURE(pPage,"Page ?");
+ if (!pPage)
+ return;
+
+ pPage->RecalcObjOrdNums();
+
+ const size_t nObjCount = pPage->GetObjCount();
+ if (!nObjCount)
+ return;
+
+ tools::Rectangle aDelRect = pDoc->GetMMRect( nCol1, nRow1, nCol2, nRow2, nTab );
+ tools::Rectangle aDelCircle = aDelRect;
+ aDelCircle.AdjustLeft(-250);
+ aDelCircle.AdjustRight(250);
+ aDelCircle.AdjustTop(-70);
+ aDelCircle.AdjustBottom(70);
+
+ std::vector<SdrObject*> ppObj;
+ ppObj.reserve(nObjCount);
+
+ SdrObjListIter aIter( pPage, SdrIterMode::Flat );
+ SdrObject* pObject = aIter.Next();
+ while (pObject)
+ {
+ // do not delete note caption, they are always handled by the cell note
+ // TODO: detective objects are still deleted, is this desired?
+ if (!IsNoteCaption( pObject ))
+ {
+ tools::Rectangle aObjRect;
+ ScDrawObjData* pObjData = ScDrawLayer::GetObjData(pObject);
+ if (pObjData && pObjData->meType == ScDrawObjData::ValidationCircle)
+ {
+ aObjRect = pObject->GetLogicRect();
+ if(aDelCircle.Contains(aObjRect))
+ ppObj.push_back(pObject);
+ }
+ else
+ {
+ aObjRect = pObject->GetCurrentBoundRect();
+ if (aDelRect.Contains(aObjRect))
+ {
+ if (bAnchored)
+ {
+ ScAnchorType aAnchorType = ScDrawLayer::GetAnchorType(*pObject);
+ if (aAnchorType == SCA_CELL || aAnchorType == SCA_CELL_RESIZE)
+ ppObj.push_back(pObject);
+ }
+ else
+ ppObj.push_back(pObject);
+ }
+ }
+ }
+
+ pObject = aIter.Next();
+ }
+
+ if (bRecording)
+ for (auto p : ppObj)
+ AddCalcUndo(std::make_unique<SdrUndoDelObj>(*p));
+
+ for (auto p : ppObj)
+ pPage->RemoveObject(p->GetOrdNum());
+}
+
+void ScDrawLayer::DeleteObjectsInSelection( const ScMarkData& rMark )
+{
+ OSL_ENSURE( pDoc, "ScDrawLayer::DeleteObjectsInSelection without document" );
+ if ( !pDoc )
+ return;
+
+ if ( !rMark.IsMultiMarked() )
+ return;
+
+ const ScRange& aMarkRange = rMark.GetMultiMarkArea();
+
+ SCTAB nTabCount = pDoc->GetTableCount();
+ for (const SCTAB nTab : rMark)
+ {
+ if (nTab >= nTabCount)
+ break;
+
+ SdrPage* pPage = GetPage(static_cast<sal_uInt16>(nTab));
+ if (pPage)
+ {
+ pPage->RecalcObjOrdNums();
+ const size_t nObjCount = pPage->GetObjCount();
+ if (nObjCount)
+ {
+ // Rectangle around the whole selection
+ tools::Rectangle aMarkBound = pDoc->GetMMRect(
+ aMarkRange.aStart.Col(), aMarkRange.aStart.Row(),
+ aMarkRange.aEnd.Col(), aMarkRange.aEnd.Row(), nTab );
+
+ std::vector<SdrObject*> ppObj;
+ ppObj.reserve(nObjCount);
+
+ SdrObjListIter aIter( pPage, SdrIterMode::Flat );
+ SdrObject* pObject = aIter.Next();
+ while (pObject)
+ {
+ // do not delete note caption, they are always handled by the cell note
+ // TODO: detective objects are still deleted, is this desired?
+ if (!IsNoteCaption( pObject ))
+ {
+ tools::Rectangle aObjRect = pObject->GetCurrentBoundRect();
+ ScRange aRange = pDoc->GetRange(nTab, aObjRect);
+ bool bObjectInMarkArea =
+ aMarkBound.Contains(aObjRect) && rMark.IsAllMarked(aRange);
+ const ScDrawObjData* pObjData = ScDrawLayer::GetObjData(pObject);
+ ScAnchorType aAnchorType = ScDrawLayer::GetAnchorType(*pObject);
+ bool bObjectAnchoredToMarkedCell
+ = ((aAnchorType == SCA_CELL || aAnchorType == SCA_CELL_RESIZE)
+ && pObjData && pObjData->maStart.IsValid()
+ && rMark.IsCellMarked(pObjData->maStart.Col(),
+ pObjData->maStart.Row()));
+ if (bObjectInMarkArea || bObjectAnchoredToMarkedCell)
+ {
+ ppObj.push_back(pObject);
+ }
+ }
+
+ pObject = aIter.Next();
+ }
+
+ // Delete objects (backwards)
+
+ if (bRecording)
+ for (auto p : ppObj)
+ AddCalcUndo(std::make_unique<SdrUndoDelObj>(*p));
+
+ for (auto p : ppObj)
+ pPage->RemoveObject(p->GetOrdNum());
+ }
+ }
+ else
+ {
+ OSL_FAIL("pPage?");
+ }
+ }
+}
+
+void ScDrawLayer::CopyToClip( ScDocument* pClipDoc, SCTAB nTab, const tools::Rectangle& rRange )
+{
+ // copy everything in the specified range into the same page (sheet) in the clipboard doc
+
+ SdrPage* pSrcPage = GetPage(static_cast<sal_uInt16>(nTab));
+ if (!pSrcPage)
+ return;
+
+ ScDrawLayer* pDestModel = nullptr;
+ SdrPage* pDestPage = nullptr;
+
+ SdrObjListIter aIter( pSrcPage, SdrIterMode::Flat );
+ SdrObject* pOldObject = aIter.Next();
+ while (pOldObject)
+ {
+ tools::Rectangle aObjRect = pOldObject->GetCurrentBoundRect();
+
+ bool bObjectInArea = rRange.Contains(aObjRect);
+ const ScDrawObjData* pObjData = ScDrawLayer::GetObjData(pOldObject);
+ if (pObjData)
+ {
+ ScRange aClipRange = lcl_getClipRangeFromClipDoc(pClipDoc, nTab);
+ bObjectInArea = bObjectInArea || aClipRange.Contains(pObjData->maStart);
+ }
+
+ // do not copy internal objects (detective) and note captions
+ if (bObjectInArea && pOldObject->GetLayer() != SC_LAYER_INTERN
+ && !IsNoteCaption(pOldObject))
+ {
+ if ( !pDestModel )
+ {
+ pDestModel = pClipDoc->GetDrawLayer(); // does the document already have a drawing layer?
+ if ( !pDestModel )
+ {
+ // allocate drawing layer in clipboard document only if there are objects to copy
+
+ pClipDoc->InitDrawLayer(); //TODO: create contiguous pages
+ pDestModel = pClipDoc->GetDrawLayer();
+ }
+ if (pDestModel)
+ pDestPage = pDestModel->GetPage( static_cast<sal_uInt16>(nTab) );
+ }
+
+ OSL_ENSURE( pDestPage, "no page" );
+ if (pDestPage)
+ {
+ // Clone to target SdrModel
+ SdrObject* pNewObject(pOldObject->CloneSdrObject(*pDestModel));
+
+ uno::Reference< chart2::XChartDocument > xOldChart( ScChartHelper::GetChartFromSdrObject( pOldObject ) );
+ if(!xOldChart.is())//#i110034# do not move charts as they lose all their data references otherwise
+ pNewObject->NbcMove(Size(0,0));
+ pDestPage->InsertObject( pNewObject );
+
+ // no undo needed in clipboard document
+ // charts are not updated
+ }
+ }
+
+ pOldObject = aIter.Next();
+ }
+}
+
+static bool lcl_IsAllInRange( const ::std::vector< ScRangeList >& rRangesVector, const ScRange& rClipRange )
+{
+ // check if every range of rRangesVector is completely in rClipRange
+
+ for( const ScRangeList& rRanges : rRangesVector )
+ {
+ for ( size_t i = 0, nCount = rRanges.size(); i < nCount; i++ )
+ {
+ const ScRange & rRange = rRanges[ i ];
+ if ( !rClipRange.Contains( rRange ) )
+ {
+ return false; // at least one range is not valid
+ }
+ }
+ }
+
+ return true; // everything is fine
+}
+
+static bool lcl_MoveRanges( ::std::vector< ScRangeList >& rRangesVector, const ScRange& rSourceRange,
+ const ScAddress& rDestPos, const ScDocument& rDoc )
+{
+ bool bChanged = false;
+
+ ScRange aErrorRange( ScAddress::UNINITIALIZED );
+ for( ScRangeList& rRanges : rRangesVector )
+ {
+ for ( size_t i = 0, nCount = rRanges.size(); i < nCount; i++ )
+ {
+ ScRange & rRange = rRanges[ i ];
+ if ( rSourceRange.Contains( rRange ) )
+ {
+ SCCOL nDiffX = rDestPos.Col() - rSourceRange.aStart.Col();
+ SCROW nDiffY = rDestPos.Row() - rSourceRange.aStart.Row();
+ SCTAB nDiffZ = rDestPos.Tab() - rSourceRange.aStart.Tab();
+ if (!rRange.Move( nDiffX, nDiffY, nDiffZ, aErrorRange, rDoc ))
+ {
+ assert(!"can't move range");
+ }
+ bChanged = true;
+ }
+ }
+ }
+
+ return bChanged;
+}
+
+void ScDrawLayer::CopyFromClip( ScDrawLayer* pClipModel, SCTAB nSourceTab, const tools::Rectangle& rSourceRange,
+ const ScAddress& rDestPos, const tools::Rectangle& rDestRange )
+{
+ OSL_ENSURE( pDoc, "ScDrawLayer::CopyFromClip without document" );
+ if ( !pDoc )
+ return;
+
+ if (!pClipModel)
+ return;
+
+ if (bDrawIsInUndo) //TODO: can this happen?
+ {
+ OSL_FAIL("CopyFromClip, bDrawIsInUndo");
+ return;
+ }
+
+ bool bMirrorObj = ( rSourceRange.Left() < 0 && rSourceRange.Right() < 0 &&
+ rDestRange.Left() > 0 && rDestRange.Right() > 0 ) ||
+ ( rSourceRange.Left() > 0 && rSourceRange.Right() > 0 &&
+ rDestRange.Left() < 0 && rDestRange.Right() < 0 );
+ tools::Rectangle aMirroredSource = rSourceRange;
+ if ( bMirrorObj )
+ MirrorRectRTL( aMirroredSource );
+
+ SCTAB nDestTab = rDestPos.Tab();
+
+ SdrPage* pSrcPage = pClipModel->GetPage(static_cast<sal_uInt16>(nSourceTab));
+ SdrPage* pDestPage = GetPage(static_cast<sal_uInt16>(nDestTab));
+ OSL_ENSURE( pSrcPage && pDestPage, "draw page missing" );
+ if ( !pSrcPage || !pDestPage )
+ return;
+
+ SdrObjListIter aIter( pSrcPage, SdrIterMode::Flat );
+ SdrObject* pOldObject = aIter.Next();
+
+ ScDocument* pClipDoc = pClipModel->GetDocument();
+ // a clipboard document and its source share the same document item pool,
+ // so the pointers can be compared to see if this is copy&paste within
+ // the same document
+ bool bSameDoc = pDoc && pClipDoc && pDoc->GetPool() == pClipDoc->GetPool();
+ bool bDestClip = pDoc && pDoc->IsClipboard();
+
+ //#i110034# charts need correct sheet names for xml range conversion during load
+ //so the target sheet name is temporarily renamed (if we have any SdrObjects)
+ OUString aDestTabName;
+ bool bRestoreDestTabName = false;
+ if( pOldObject && !bSameDoc && !bDestClip )
+ {
+ if( pDoc && pClipDoc )
+ {
+ OUString aSourceTabName;
+ if( pClipDoc->GetName( nSourceTab, aSourceTabName )
+ && pDoc->GetName( nDestTab, aDestTabName ) )
+ {
+ if( aSourceTabName != aDestTabName &&
+ pDoc->ValidNewTabName(aSourceTabName) )
+ {
+ bRestoreDestTabName = pDoc->RenameTab( nDestTab, aSourceTabName );
+ }
+ }
+ }
+ }
+
+ // first mirror, then move
+ Size aMove( rDestRange.Left() - aMirroredSource.Left(), rDestRange.Top() - aMirroredSource.Top() );
+
+ tools::Long nDestWidth = rDestRange.GetWidth();
+ tools::Long nDestHeight = rDestRange.GetHeight();
+ tools::Long nSourceWidth = rSourceRange.GetWidth();
+ tools::Long nSourceHeight = rSourceRange.GetHeight();
+
+ tools::Long nWidthDiff = nDestWidth - nSourceWidth;
+ tools::Long nHeightDiff = nDestHeight - nSourceHeight;
+
+ Fraction aHorFract(1,1);
+ Fraction aVerFract(1,1);
+ bool bResize = false;
+ // sizes can differ by 1 from twips->1/100mm conversion for equal cell sizes,
+ // don't resize to empty size when pasting into hidden columns or rows
+ if ( std::abs(nWidthDiff) > 1 && nDestWidth > 1 && nSourceWidth > 1 )
+ {
+ aHorFract = Fraction( nDestWidth, nSourceWidth );
+ bResize = true;
+ }
+ if ( std::abs(nHeightDiff) > 1 && nDestHeight > 1 && nSourceHeight > 1 )
+ {
+ aVerFract = Fraction( nDestHeight, nSourceHeight );
+ bResize = true;
+ }
+ Point aRefPos = rDestRange.TopLeft(); // for resizing (after moving)
+
+ while (pOldObject)
+ {
+ tools::Rectangle aObjRect = pOldObject->GetCurrentBoundRect();
+ // do not copy internal objects (detective) and note captions
+
+ SCTAB nClipTab = bRestoreDestTabName ? nDestTab : nSourceTab;
+ ScRange aClipRange = lcl_getClipRangeFromClipDoc(pClipDoc, nClipTab);
+
+ bool bObjectInArea = rSourceRange.Contains(aObjRect);
+ const ScDrawObjData* pObjData = ScDrawLayer::GetObjData(pOldObject);
+ if (pObjData) // Consider images anchored to the copied cell
+ bObjectInArea = bObjectInArea || aClipRange.Contains(pObjData->maStart);
+ if (bObjectInArea && (pOldObject->GetLayer() != SC_LAYER_INTERN)
+ && !IsNoteCaption(pOldObject))
+ {
+ // Clone to target SdrModel
+ SdrObject* pNewObject(pOldObject->CloneSdrObject(*this));
+
+ if ( bMirrorObj )
+ MirrorRTL( pNewObject ); // first mirror, then move
+
+ pNewObject->NbcMove( aMove );
+ if ( bResize )
+ pNewObject->NbcResize( aRefPos, aHorFract, aVerFract );
+
+ pDestPage->InsertObject( pNewObject );
+ if (bRecording)
+ AddCalcUndo( std::make_unique<SdrUndoInsertObj>( *pNewObject ) );
+
+ //#i110034# handle chart data references (after InsertObject)
+
+ if ( pNewObject->GetObjIdentifier() == SdrObjKind::OLE2 )
+ {
+ uno::Reference< embed::XEmbeddedObject > xIPObj = static_cast<SdrOle2Obj*>(pNewObject)->GetObjRef();
+ uno::Reference< embed::XClassifiedObject > xClassified = xIPObj;
+ SvGlobalName aObjectClassName;
+ if ( xClassified.is() )
+ {
+ try {
+ aObjectClassName = SvGlobalName( xClassified->getClassID() );
+ } catch( uno::Exception& )
+ {
+ // TODO: handle error?
+ }
+ }
+
+ if ( xIPObj.is() && SotExchange::IsChart( aObjectClassName ) )
+ {
+ uno::Reference< chart2::XChartDocument > xNewChart( ScChartHelper::GetChartFromSdrObject( pNewObject ) );
+ if( xNewChart.is() && !xNewChart->hasInternalDataProvider() )
+ {
+ OUString aChartName = static_cast<SdrOle2Obj*>(pNewObject)->GetPersistName();
+ ::std::vector< ScRangeList > aRangesVector;
+ pDoc->GetChartRanges( aChartName, aRangesVector, *pDoc );
+ if( !aRangesVector.empty() )
+ {
+ bool bInSourceRange = false;
+ if ( pClipDoc )
+ {
+ bInSourceRange = lcl_IsAllInRange( aRangesVector, aClipRange );
+ }
+
+ // always lose references when pasting into a clipboard document (transpose)
+ if ( ( bInSourceRange || bSameDoc ) && !bDestClip )
+ {
+ if ( bInSourceRange )
+ {
+ if ( rDestPos != aClipRange.aStart )
+ {
+ // update the data ranges to the new (copied) position
+ if ( lcl_MoveRanges( aRangesVector, aClipRange, rDestPos, *pDoc ) )
+ pDoc->SetChartRanges( aChartName, aRangesVector );
+ }
+ }
+ else
+ {
+ // leave the ranges unchanged
+ }
+ }
+ else
+ {
+ // pasting into a new document without the complete source data
+ // -> break connection to source data and switch to own data
+
+ uno::Reference< chart::XChartDocument > xOldChartDoc( ScChartHelper::GetChartFromSdrObject( pOldObject ), uno::UNO_QUERY );
+ uno::Reference< chart::XChartDocument > xNewChartDoc( xNewChart, uno::UNO_QUERY );
+ if( xOldChartDoc.is() && xNewChartDoc.is() )
+ xNewChartDoc->attachData( xOldChartDoc->getData() );
+
+ // (see ScDocument::UpdateChartListenerCollection, PastingDrawFromOtherDoc)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ pOldObject = aIter.Next();
+ }
+
+ if( bRestoreDestTabName )
+ pDoc->RenameTab( nDestTab, aDestTabName );
+}
+
+void ScDrawLayer::MirrorRTL( SdrObject* pObj )
+{
+ OSL_ENSURE( pDoc, "ScDrawLayer::MirrorRTL - missing document" );
+ if( !pDoc )
+ return;
+
+ SdrObjKind nIdent = pObj->GetObjIdentifier();
+
+ // don't mirror OLE or graphics, otherwise ask the object
+ // if it can be mirrored
+ bool bCanMirror = ( nIdent != SdrObjKind::Graphic && nIdent != SdrObjKind::OLE2 );
+ if (bCanMirror)
+ {
+ SdrObjTransformInfoRec aInfo;
+ pObj->TakeObjInfo( aInfo );
+ bCanMirror = aInfo.bMirror90Allowed;
+ }
+
+ if (bCanMirror)
+ {
+ ScDrawObjData* pData = GetObjData(pObj);
+ if (pData) // cell anchored
+ {
+ // Remember values from positive side.
+ const tools::Rectangle aOldSnapRect = pObj->GetSnapRect();
+ const tools::Rectangle aOldLogicRect = pObj->GetLogicRect();
+ // Generate noRotate anchor if missing.
+ ScDrawObjData* pNoRotatedAnchor = GetNonRotatedObjData(pObj);
+ if (!pNoRotatedAnchor)
+ {
+ ScDrawObjData aNoRotateAnchor;
+ const tools::Rectangle aLogicRect(pObj->GetLogicRect());
+ GetCellAnchorFromPosition(aLogicRect, aNoRotateAnchor,
+ *pDoc, pData->maStart.Tab());
+ aNoRotateAnchor.mbResizeWithCell = pData->mbResizeWithCell;
+ SetNonRotatedAnchor(*pObj, aNoRotateAnchor);
+ pNoRotatedAnchor = GetNonRotatedObjData(pObj);
+ assert(pNoRotatedAnchor);
+ }
+ // Mirror object at vertical axis
+ Point aRef1( 0, 0 );
+ Point aRef2( 0, 1 );
+ if (bRecording)
+ AddCalcUndo( std::make_unique<SdrUndoGeoObj>( *pObj ) );
+ pObj->Mirror( aRef1, aRef2 );
+
+ // Adapt offsets in pNoRotatedAnchor so, that object will be moved to current position in
+ // save and reload.
+ const tools::Long nInverseShift = aOldSnapRect.Left() + aOldSnapRect.Right();
+ const Point aLogicLT = pObj->GetLogicRect().TopLeft();
+ const Point aMirroredLogicLT = aLogicLT + Point(nInverseShift, 0);
+ const Point aOffsetDiff = aMirroredLogicLT - aOldLogicRect.TopLeft();
+ // new Offsets
+ pNoRotatedAnchor->maStartOffset += aOffsetDiff;
+ pNoRotatedAnchor->maEndOffset += aOffsetDiff;
+ }
+ else // page anchored
+ {
+ Point aRef1( 0, 0 );
+ Point aRef2( 0, 1 );
+ if (bRecording)
+ AddCalcUndo( std::make_unique<SdrUndoGeoObj>( *pObj ) );
+ pObj->Mirror( aRef1, aRef2 );
+ }
+ }
+ else
+ {
+ // Move instead of mirroring:
+ // New start position is negative of old end position
+ // -> move by sum of start and end position
+ tools::Rectangle aObjRect = pObj->GetSnapRect();
+ Size aMoveSize( -(aObjRect.Left() + aObjRect.Right()), 0 );
+ if (bRecording)
+ AddCalcUndo( std::make_unique<SdrUndoMoveObj>( *pObj, aMoveSize ) );
+ pObj->Move( aMoveSize );
+ }
+
+ // for cell anchored objects adapt rectangles in anchors
+ ScDrawObjData* pData = GetObjData(pObj);
+ if (pData)
+ {
+ pData->setShapeRect(GetDocument(), pObj->GetSnapRect(), pObj->IsVisible());
+ ScDrawObjData* pNoRotatedAnchor = GetNonRotatedObjData(pObj, true /*bCreate*/);
+ pNoRotatedAnchor->setShapeRect(GetDocument(), pObj->GetLogicRect(), pObj->IsVisible());
+ }
+}
+
+void ScDrawLayer::MoveRTL(SdrObject* pObj)
+{
+ tools::Rectangle aObjRect = pObj->GetSnapRect();
+ Size aMoveSize( -(aObjRect.Left() + aObjRect.Right()), 0 );
+ if (bRecording)
+ AddCalcUndo( std::make_unique<SdrUndoMoveObj>( *pObj, aMoveSize ) );
+ pObj->Move( aMoveSize );
+
+ // for cell anchored objects adapt rectangles in anchors
+ ScDrawObjData* pData = GetObjData(pObj);
+ if (pData)
+ {
+ pData->setShapeRect(GetDocument(), pObj->GetSnapRect(), pObj->IsVisible());
+ ScDrawObjData* pNoRotatedAnchor = GetNonRotatedObjData(pObj, true /*bCreate*/);
+ pNoRotatedAnchor->setShapeRect(GetDocument(), pObj->GetLogicRect(), pObj->IsVisible());
+ }
+}
+
+void ScDrawLayer::MirrorRectRTL( tools::Rectangle& rRect )
+{
+ // mirror and swap left/right
+ tools::Long nTemp = rRect.Left();
+ rRect.SetLeft( -rRect.Right() );
+ rRect.SetRight( -nTemp );
+}
+
+tools::Rectangle ScDrawLayer::GetCellRect( const ScDocument& rDoc, const ScAddress& rPos, bool bMergedCell )
+{
+ tools::Rectangle aCellRect;
+ OSL_ENSURE( rDoc.ValidColRowTab( rPos.Col(), rPos.Row(), rPos.Tab() ), "ScDrawLayer::GetCellRect - invalid cell address" );
+ if( rDoc.ValidColRowTab( rPos.Col(), rPos.Row(), rPos.Tab() ) )
+ {
+ // find top left position of passed cell address
+ Point aTopLeft;
+ for( SCCOL nCol = 0; nCol < rPos.Col(); ++nCol )
+ aTopLeft.AdjustX(rDoc.GetColWidth( nCol, rPos.Tab() ) );
+ if( rPos.Row() > 0 )
+ aTopLeft.AdjustY(rDoc.GetRowHeight( 0, rPos.Row() - 1, rPos.Tab() ) );
+
+ // find bottom-right position of passed cell address
+ ScAddress aEndPos = rPos;
+ if( bMergedCell )
+ {
+ const ScMergeAttr* pMerge = rDoc.GetAttr( rPos, ATTR_MERGE );
+ if( pMerge->GetColMerge() > 1 )
+ aEndPos.IncCol( pMerge->GetColMerge() - 1 );
+ if( pMerge->GetRowMerge() > 1 )
+ aEndPos.IncRow( pMerge->GetRowMerge() - 1 );
+ }
+ Point aBotRight = aTopLeft;
+ for( SCCOL nCol = rPos.Col(); nCol <= aEndPos.Col(); ++nCol )
+ aBotRight.AdjustX(rDoc.GetColWidth( nCol, rPos.Tab() ) );
+ aBotRight.AdjustY(rDoc.GetRowHeight( rPos.Row(), aEndPos.Row(), rPos.Tab() ) );
+
+ // twips -> 1/100 mm
+ aTopLeft = o3tl::convert(aTopLeft, o3tl::Length::twip, o3tl::Length::mm100);
+ aBotRight = o3tl::convert(aBotRight, o3tl::Length::twip, o3tl::Length::mm100);
+
+ aCellRect = tools::Rectangle( aTopLeft, aBotRight );
+ if( rDoc.IsNegativePage( rPos.Tab() ) )
+ MirrorRectRTL( aCellRect );
+ }
+ return aCellRect;
+}
+
+OUString ScDrawLayer::GetVisibleName( const SdrObject* pObj )
+{
+ OUString aName = pObj->GetName();
+ if ( pObj->GetObjIdentifier() == SdrObjKind::OLE2 )
+ {
+ // For OLE, the user defined name (GetName) is used
+ // if it's not empty (accepting possibly duplicate names),
+ // otherwise the persist name is used so every object appears
+ // in the Navigator at all.
+
+ if ( aName.isEmpty() )
+ aName = static_cast<const SdrOle2Obj*>(pObj)->GetPersistName();
+ }
+ return aName;
+}
+
+static bool IsNamedObject( const SdrObject* pObj, std::u16string_view rName )
+{
+ // sal_True if rName is the object's Name or PersistName
+ // (used to find a named object)
+
+ return ( pObj->GetName() == rName ||
+ ( pObj->GetObjIdentifier() == SdrObjKind::OLE2 &&
+ static_cast<const SdrOle2Obj*>(pObj)->GetPersistName() == rName ) );
+}
+
+SdrObject* ScDrawLayer::GetNamedObject( std::u16string_view rName, SdrObjKind nId, SCTAB& rFoundTab ) const
+{
+ sal_uInt16 nTabCount = GetPageCount();
+ for (sal_uInt16 nTab=0; nTab<nTabCount; nTab++)
+ {
+ const SdrPage* pPage = GetPage(nTab);
+ OSL_ENSURE(pPage,"Page ?");
+ if (pPage)
+ {
+ SdrObjListIter aIter( pPage, SdrIterMode::DeepWithGroups );
+ SdrObject* pObject = aIter.Next();
+ while (pObject)
+ {
+ if ( nId == SdrObjKind::NONE || pObject->GetObjIdentifier() == nId )
+ if ( IsNamedObject( pObject, rName ) )
+ {
+ rFoundTab = static_cast<SCTAB>(nTab);
+ return pObject;
+ }
+
+ pObject = aIter.Next();
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+OUString ScDrawLayer::GetNewGraphicName( tools::Long* pnCounter ) const
+{
+ OUString aBase = ScResId(STR_GRAPHICNAME) + " ";
+
+ bool bThere = true;
+ OUString aGraphicName;
+ SCTAB nDummy;
+ tools::Long nId = pnCounter ? *pnCounter : 0;
+ while (bThere)
+ {
+ ++nId;
+ aGraphicName = aBase + OUString::number( nId );
+ bThere = ( GetNamedObject( aGraphicName, SdrObjKind::NONE, nDummy ) != nullptr );
+ }
+
+ if ( pnCounter )
+ *pnCounter = nId;
+
+ return aGraphicName;
+}
+
+void ScDrawLayer::EnsureGraphicNames()
+{
+ // make sure all graphic objects have names (after Excel import etc.)
+
+ sal_uInt16 nTabCount = GetPageCount();
+ for (sal_uInt16 nTab=0; nTab<nTabCount; nTab++)
+ {
+ SdrPage* pPage = GetPage(nTab);
+ OSL_ENSURE(pPage,"Page ?");
+ if (pPage)
+ {
+ SdrObjListIter aIter( pPage, SdrIterMode::DeepWithGroups );
+ SdrObject* pObject = aIter.Next();
+
+ /* The index passed to GetNewGraphicName() will be set to
+ the used index in each call. This prevents the repeated search
+ for all names from 1 to current index. */
+ tools::Long nCounter = 0;
+
+ while (pObject)
+ {
+ if ( pObject->GetObjIdentifier() == SdrObjKind::Graphic && pObject->GetName().isEmpty())
+ pObject->SetName( GetNewGraphicName( &nCounter ) );
+
+ pObject = aIter.Next();
+ }
+ }
+ }
+}
+
+namespace
+{
+ SdrObjUserData* GetFirstUserDataOfType(const SdrObject *pObj, sal_uInt16 nId)
+ {
+ sal_uInt16 nCount = pObj ? pObj->GetUserDataCount() : 0;
+ for( sal_uInt16 i = 0; i < nCount; i++ )
+ {
+ SdrObjUserData* pData = pObj->GetUserData( i );
+ if( pData && pData->GetInventor() == SdrInventor::ScOrSwDraw && pData->GetId() == nId )
+ return pData;
+ }
+ return nullptr;
+ }
+
+ void DeleteFirstUserDataOfType(SdrObject *pObj, sal_uInt16 nId)
+ {
+ sal_uInt16 nCount = pObj ? pObj->GetUserDataCount() : 0;
+ for( sal_uInt16 i = nCount; i > 0; i-- )
+ {
+ SdrObjUserData* pData = pObj->GetUserData( i-1 );
+ if( pData && pData->GetInventor() == SdrInventor::ScOrSwDraw && pData->GetId() == nId )
+ pObj->DeleteUserData(i-1);
+ }
+ }
+}
+
+void ScDrawLayer::SetNonRotatedAnchor(SdrObject& rObj, const ScDrawObjData& rAnchor)
+{
+ ScDrawObjData* pAnchor = GetNonRotatedObjData( &rObj, true );
+ pAnchor->maStart = rAnchor.maStart;
+ pAnchor->maEnd = rAnchor.maEnd;
+ pAnchor->maStartOffset = rAnchor.maStartOffset;
+ pAnchor->maEndOffset = rAnchor.maEndOffset;
+ pAnchor->mbResizeWithCell = rAnchor.mbResizeWithCell;
+}
+
+void ScDrawLayer::SetCellAnchored( SdrObject &rObj, const ScDrawObjData &rAnchor )
+{
+ ScDrawObjData* pAnchor = GetObjData( &rObj, true );
+ pAnchor->maStart = rAnchor.maStart;
+ pAnchor->maEnd = rAnchor.maEnd;
+ pAnchor->maStartOffset = rAnchor.maStartOffset;
+ pAnchor->maEndOffset = rAnchor.maEndOffset;
+ pAnchor->mbResizeWithCell = rAnchor.mbResizeWithCell;
+}
+
+void ScDrawLayer::SetCellAnchoredFromPosition( SdrObject &rObj, const ScDocument &rDoc, SCTAB nTab,
+ bool bResizeWithCell )
+{
+ if (!rObj.IsVisible())
+ return;
+ ScDrawObjData aAnchor;
+ // set anchor in terms of the visual ( SnapRect )
+ // object ( e.g. for when object is rotated )
+ const tools::Rectangle aObjRect(rObj.GetSnapRect());
+ GetCellAnchorFromPosition(
+ aObjRect,
+ aAnchor,
+ rDoc,
+ nTab);
+
+ aAnchor.mbResizeWithCell = bResizeWithCell;
+ SetCellAnchored( rObj, aAnchor );
+
+ // absolutely necessary to set flag, ScDrawLayer::RecalcPos expects it.
+ if ( ScDrawObjData* pAnchor = GetObjData( &rObj ) )
+ {
+ pAnchor->setShapeRect(&rDoc, rObj.GetSnapRect());
+ }
+
+ // - keep also an anchor in terms of the Logic ( untransformed ) object
+ // because that's what we stored ( and still do ) to xml
+
+ // Vertical flipped custom shapes need special treatment, see comment in
+ // lcl_SetLogicRectFromAnchor
+ tools::Rectangle aObjRect2;
+ if (lcl_NeedsMirrorYCorrection(&rObj))
+ {
+ const tools::Rectangle aRect(rObj.GetSnapRect());
+ const Point aLeft(aRect.Left(), (aRect.Top() + aRect.Bottom()) >> 1);
+ const Point aRight(aLeft.X() + 1000, aLeft.Y());
+ rObj.NbcMirror(aLeft, aRight);
+ aObjRect2 = rObj.GetLogicRect();
+ rObj.NbcMirror(aLeft, aRight);
+ }
+ else if (rObj.GetObjIdentifier() == SdrObjKind::Measure)
+ {
+ // tdf#137576. A SdrMeasureObj might have a wrong logic rect here. TakeUnrotatedSnapRect
+ // calculates the current unrotated snap rectangle, sets logic rectangle and returns it.
+ static_cast<SdrMeasureObj&>(rObj).TakeUnrotatedSnapRect(aObjRect2);
+ }
+ else
+ aObjRect2 = rObj.GetLogicRect();
+
+ // Values in XML are so as if it is a LTR sheet. The object is shifted to negative page on loading
+ // so that the snap rectangle appears mirrored. For transformed objects the shifted logic rectangle
+ // is not the mirrored LTR rectangle. We calculate the mirrored LTR rectangle here.
+ if (rDoc.IsNegativePage(nTab))
+ {
+ const tools::Rectangle aSnapRect(rObj.GetSnapRect());
+ aObjRect2.Move(Size(-aSnapRect.Left() - aSnapRect.Right(), 0));
+ MirrorRectRTL(aObjRect2);
+ }
+
+ ScDrawObjData aNoRotatedAnchor;
+ GetCellAnchorFromPosition(
+ aObjRect2,
+ aNoRotatedAnchor,
+ rDoc,
+ nTab);
+
+ aNoRotatedAnchor.mbResizeWithCell = bResizeWithCell;
+ SetNonRotatedAnchor( rObj, aNoRotatedAnchor);
+ // And update maShapeRect. It is used in adjustAnchoredPosition() in ScDrawView::Notify().
+ if (ScDrawObjData* pAnchor = GetNonRotatedObjData(&rObj))
+ {
+ pAnchor->setShapeRect(&rDoc, rObj.GetLogicRect());
+ }
+}
+
+void ScDrawLayer::GetCellAnchorFromPosition(
+ const tools::Rectangle &rObjRect,
+ ScDrawObjData &rAnchor,
+ const ScDocument &rDoc,
+ SCTAB nTab,
+ bool bHiddenAsZero)
+{
+ ScRange aRange = rDoc.GetRange( nTab, rObjRect, bHiddenAsZero );
+
+ tools::Rectangle aCellRect;
+
+ rAnchor.maStart = aRange.aStart;
+ aCellRect = rDoc.GetMMRect( aRange.aStart.Col(), aRange.aStart.Row(),
+ aRange.aStart.Col(), aRange.aStart.Row(), aRange.aStart.Tab(), bHiddenAsZero );
+ rAnchor.maStartOffset.setY( rObjRect.Top()-aCellRect.Top() );
+ if (!rDoc.IsNegativePage(nTab))
+ rAnchor.maStartOffset.setX( rObjRect.Left()-aCellRect.Left() );
+ else
+ rAnchor.maStartOffset.setX( aCellRect.Right()-rObjRect.Right() );
+
+ rAnchor.maEnd = aRange.aEnd;
+ aCellRect = rDoc.GetMMRect( aRange.aEnd.Col(), aRange.aEnd.Row(),
+ aRange.aEnd.Col(), aRange.aEnd.Row(), aRange.aEnd.Tab(), bHiddenAsZero );
+ if (!rObjRect.IsEmpty())
+ rAnchor.maEndOffset.setY( rObjRect.Bottom()-aCellRect.Top() );
+ if (!rDoc.IsNegativePage(nTab))
+ {
+ if (!rObjRect.IsEmpty())
+ rAnchor.maEndOffset.setX( rObjRect.Right()-aCellRect.Left() );
+ }
+ else
+ rAnchor.maEndOffset.setX( aCellRect.Right()-rObjRect.Left() );
+}
+
+void ScDrawLayer::UpdateCellAnchorFromPositionEnd( const SdrObject &rObj, ScDrawObjData &rAnchor, const ScDocument &rDoc, SCTAB nTab, bool bUseLogicRect )
+{
+ tools::Rectangle aObjRect(bUseLogicRect ? rObj.GetLogicRect() : rObj.GetSnapRect());
+ ScRange aRange = rDoc.GetRange( nTab, aObjRect );
+
+ ScDrawObjData* pAnchor = &rAnchor;
+ pAnchor->maEnd = aRange.aEnd;
+
+ tools::Rectangle aCellRect = rDoc.GetMMRect( aRange.aEnd.Col(), aRange.aEnd.Row(),
+ aRange.aEnd.Col(), aRange.aEnd.Row(), aRange.aEnd.Tab() );
+ pAnchor->maEndOffset.setY( aObjRect.Bottom()-aCellRect.Top() );
+ if (!rDoc.IsNegativePage(nTab))
+ pAnchor->maEndOffset.setX( aObjRect.Right()-aCellRect.Left() );
+ else
+ pAnchor->maEndOffset.setX( aCellRect.Right()-aObjRect.Left() );
+}
+
+bool ScDrawLayer::IsCellAnchored( const SdrObject& rObj )
+{
+ // Cell anchored object always has a user data, to store the anchor cell
+ // info. If it doesn't then it's page-anchored.
+ return GetFirstUserDataOfType(&rObj, SC_UD_OBJDATA) != nullptr;
+}
+
+bool ScDrawLayer::IsResizeWithCell( const SdrObject& rObj )
+{
+ // Cell anchored object always has a user data, to store the anchor cell
+ // info. If it doesn't then it's page-anchored.
+ ScDrawObjData* pDrawObjData = GetObjData(const_cast<SdrObject*>(&rObj));
+ if (!pDrawObjData)
+ return false;
+
+ return pDrawObjData->mbResizeWithCell;
+}
+
+void ScDrawLayer::SetPageAnchored( SdrObject &rObj )
+{
+ DeleteFirstUserDataOfType(&rObj, SC_UD_OBJDATA);
+ DeleteFirstUserDataOfType(&rObj, SC_UD_OBJDATA);
+}
+
+ScAnchorType ScDrawLayer::GetAnchorType( const SdrObject &rObj )
+{
+ //If this object has a cell anchor associated with it
+ //then it's cell-anchored, otherwise it's page-anchored
+ const ScDrawObjData* pObjData = ScDrawLayer::GetObjData(const_cast<SdrObject*>(&rObj));
+
+ // When there is no cell anchor, it is page anchored.
+ if (!pObjData)
+ return SCA_PAGE;
+
+ // It's cell-anchored, check if the object resizes with the cell
+ if (pObjData->mbResizeWithCell)
+ return SCA_CELL_RESIZE;
+
+ return SCA_CELL;
+}
+
+std::vector<SdrObject*>
+ScDrawLayer::GetObjectsAnchoredToRows(SCTAB nTab, SCROW nStartRow, SCROW nEndRow)
+{
+ SdrPage* pPage = GetPage(static_cast<sal_uInt16>(nTab));
+ if (!pPage || pPage->GetObjCount() < 1)
+ return std::vector<SdrObject*>();
+
+ std::vector<SdrObject*> aObjects;
+ SdrObjListIter aIter( pPage, SdrIterMode::Flat );
+ SdrObject* pObject = aIter.Next();
+ ScRange aRange( 0, nStartRow, nTab, pDoc->MaxCol(), nEndRow, nTab);
+ while (pObject)
+ {
+ ScDrawObjData* pObjData = GetObjData(pObject);
+ if (pObjData && aRange.Contains(pObjData->maStart))
+ aObjects.push_back(pObject);
+ pObject = aIter.Next();
+ }
+ return aObjects;
+}
+
+std::map<SCROW, std::vector<SdrObject*>>
+ScDrawLayer::GetObjectsAnchoredToRange(SCTAB nTab, SCCOL nCol, SCROW nStartRow, SCROW nEndRow)
+{
+ SdrPage* pPage = GetPage(static_cast<sal_uInt16>(nTab));
+ if (!pPage || pPage->GetObjCount() < 1)
+ return std::map<SCROW, std::vector<SdrObject*>>();
+
+ std::map<SCROW, std::vector<SdrObject*>> aRowObjects;
+ SdrObjListIter aIter( pPage, SdrIterMode::Flat );
+ SdrObject* pObject = aIter.Next();
+ ScRange aRange( nCol, nStartRow, nTab, nCol, nEndRow, nTab);
+ while (pObject)
+ {
+ if (!dynamic_cast<SdrCaptionObj*>(pObject)) // Caption objects are handled differently
+ {
+ ScDrawObjData* pObjData = GetObjData(pObject);
+ if (pObjData && aRange.Contains(pObjData->maStart))
+ aRowObjects[pObjData->maStart.Row()].push_back(pObject);
+ }
+ pObject = aIter.Next();
+ }
+ return aRowObjects;
+}
+
+bool ScDrawLayer::HasObjectsAnchoredInRange(const ScRange& rRange)
+{
+ // This only works for one table at a time
+ assert(rRange.aStart.Tab() == rRange.aEnd.Tab());
+
+ SdrPage* pPage = GetPage(static_cast<sal_uInt16>(rRange.aStart.Tab()));
+ if (!pPage || pPage->GetObjCount() < 1)
+ return false;
+
+ SdrObjListIter aIter( pPage, SdrIterMode::Flat );
+ SdrObject* pObject = aIter.Next();
+ while (pObject)
+ {
+ if (!dynamic_cast<SdrCaptionObj*>(pObject)) // Caption objects are handled differently
+ {
+ ScDrawObjData* pObjData = GetObjData(pObject);
+ if (pObjData && rRange.Contains(pObjData->maStart)) // Object is in given range
+ return true;
+ }
+ pObject = aIter.Next();
+ }
+ return false;
+}
+
+std::vector<SdrObject*> ScDrawLayer::GetObjectsAnchoredToCols(SCTAB nTab, SCCOL nStartCol,
+ SCCOL nEndCol)
+{
+ SdrPage* pPage = GetPage(static_cast<sal_uInt16>(nTab));
+ if (!pPage || pPage->GetObjCount() < 1)
+ return std::vector<SdrObject*>();
+
+ std::vector<SdrObject*> aObjects;
+ SdrObjListIter aIter(pPage, SdrIterMode::Flat);
+ SdrObject* pObject = aIter.Next();
+ ScRange aRange(nStartCol, 0, nTab, nEndCol, pDoc->MaxRow(), nTab);
+ while (pObject)
+ {
+ ScDrawObjData* pObjData = GetObjData(pObject);
+ if (pObjData && aRange.Contains(pObjData->maStart))
+ aObjects.push_back(pObject);
+ pObject = aIter.Next();
+ }
+ return aObjects;
+}
+
+void ScDrawLayer::MoveObject(SdrObject* pObject, const ScAddress& rNewPosition)
+{
+ // Get anchor data
+ ScDrawObjData* pObjData = GetObjData(pObject, false);
+ if (!pObjData)
+ return;
+ const ScAddress aOldStart = pObjData->maStart;
+ const ScAddress aOldEnd = pObjData->maEnd;
+
+ // Set start address
+ pObjData->maStart = rNewPosition;
+
+ // Set end address
+ const SCCOL nObjectColSpan = aOldEnd.Col() - aOldStart.Col();
+ const SCROW nObjectRowSpan = aOldEnd.Row() - aOldStart.Row();
+ ScAddress aNewEnd = rNewPosition;
+ aNewEnd.IncRow(nObjectRowSpan);
+ aNewEnd.IncCol(nObjectColSpan);
+ pObjData->maEnd = aNewEnd;
+
+ // Update draw object according to new anchor
+ RecalcPos(pObject, *pObjData, false, false);
+}
+
+ScDrawObjData* ScDrawLayer::GetNonRotatedObjData( SdrObject* pObj, bool bCreate )
+{
+ sal_uInt16 nCount = pObj ? pObj->GetUserDataCount() : 0;
+ sal_uInt16 nFound = 0;
+ for( sal_uInt16 i = 0; i < nCount; i++ )
+ {
+ SdrObjUserData* pData = pObj->GetUserData( i );
+ if( pData && pData->GetInventor() == SdrInventor::ScOrSwDraw && pData->GetId() == SC_UD_OBJDATA && ++nFound == 2 )
+ return static_cast<ScDrawObjData*>(pData);
+ }
+ if( pObj && bCreate )
+ {
+ ScDrawObjData* pData = new ScDrawObjData;
+ pObj->AppendUserData(std::unique_ptr<SdrObjUserData>(pData));
+ return pData;
+ }
+ return nullptr;
+}
+
+ScDrawObjData* ScDrawLayer::GetObjData( SdrObject* pObj, bool bCreate )
+{
+ if (SdrObjUserData *pData = GetFirstUserDataOfType(pObj, SC_UD_OBJDATA))
+ return static_cast<ScDrawObjData*>(pData);
+
+ if( pObj && bCreate )
+ {
+ ScDrawObjData* pData = new ScDrawObjData;
+ pObj->AppendUserData(std::unique_ptr<SdrObjUserData>(pData));
+ return pData;
+ }
+ return nullptr;
+}
+
+ScDrawObjData* ScDrawLayer::GetObjDataTab( SdrObject* pObj, SCTAB nTab )
+{
+ ScDrawObjData* pData = GetObjData( pObj );
+ if ( pData )
+ {
+ if ( pData->maStart.IsValid() )
+ pData->maStart.SetTab( nTab );
+ if ( pData->maEnd.IsValid() )
+ pData->maEnd.SetTab( nTab );
+ }
+ return pData;
+}
+
+bool ScDrawLayer::IsNoteCaption( SdrObject* pObj )
+{
+ ScDrawObjData* pData = pObj ? GetObjData( pObj ) : nullptr;
+ return pData && pData->meType == ScDrawObjData::CellNote;
+}
+
+ScDrawObjData* ScDrawLayer::GetNoteCaptionData( SdrObject* pObj, SCTAB nTab )
+{
+ ScDrawObjData* pData = pObj ? GetObjDataTab( pObj, nTab ) : nullptr;
+ return (pData && pData->meType == ScDrawObjData::CellNote) ? pData : nullptr;
+}
+
+ScMacroInfo* ScDrawLayer::GetMacroInfo( SdrObject* pObj, bool bCreate )
+{
+ if (SdrObjUserData *pData = GetFirstUserDataOfType(pObj, SC_UD_MACRODATA))
+ return static_cast<ScMacroInfo*>(pData);
+
+ if ( bCreate )
+ {
+ ScMacroInfo* pData = new ScMacroInfo;
+ pObj->AppendUserData(std::unique_ptr<SdrObjUserData>(pData));
+ return pData;
+ }
+ return nullptr;
+}
+
+void ScDrawLayer::SetGlobalDrawPersist(SfxObjectShell* pPersist)
+{
+ OSL_ENSURE(!pGlobalDrawPersist,"Multiple SetGlobalDrawPersist");
+ pGlobalDrawPersist = pPersist;
+}
+
+void ScDrawLayer::SetChanged( bool bFlg /* = true */ )
+{
+ if ( bFlg && pDoc )
+ pDoc->SetChartListenerCollectionNeedsUpdate( true );
+ FmFormModel::SetChanged( bFlg );
+}
+
+css::uno::Reference< css::uno::XInterface > ScDrawLayer::createUnoModel()
+{
+ css::uno::Reference< css::uno::XInterface > xRet;
+ if( pDoc && pDoc->GetDocumentShell() )
+ xRet = pDoc->GetDocumentShell()->GetModel();
+
+ return xRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/edittextiterator.cxx b/sc/source/core/data/edittextiterator.cxx
new file mode 100644
index 000000000..bdf7c9934
--- /dev/null
+++ b/sc/source/core/data/edittextiterator.cxx
@@ -0,0 +1,97 @@
+/* -*- 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 <edittextiterator.hxx>
+#include <document.hxx>
+#include <table.hxx>
+#include <column.hxx>
+
+namespace sc {
+
+EditTextIterator::EditTextIterator( const ScDocument& rDoc, SCTAB nTab ) :
+ mrTable(*rDoc.maTabs.at(nTab)),
+ mnCol(0),
+ mpCells(nullptr),
+ miEnd(maPos.first)
+{
+ init();
+}
+
+void EditTextIterator::init()
+{
+ mnCol = 0;
+ if (mnCol >= mrTable.aCol.size())
+ mnCol = -1;
+
+ if (mnCol != -1)
+ {
+ mpCells = &mrTable.aCol[mnCol].maCells;
+ maPos = mpCells->position(0);
+ miEnd = mpCells->end();
+ }
+}
+
+const EditTextObject* EditTextIterator::seek()
+{
+ while (maPos.first->type != sc::element_type_edittext)
+ {
+ incBlock();
+ if (maPos.first == miEnd)
+ {
+ // Move to the next column.
+ ++mnCol;
+ if (mnCol >= mrTable.aCol.size())
+ // No more columns.
+ return nullptr;
+
+ mpCells = &mrTable.aCol[mnCol].maCells;
+ maPos = mpCells->position(0);
+ miEnd = mpCells->end();
+ }
+ }
+
+ // We are on the right block type.
+ return sc::edittext_block::at(*maPos.first->data, maPos.second);
+}
+
+void EditTextIterator::incBlock()
+{
+ ++maPos.first;
+ maPos.second = 0;
+}
+
+const EditTextObject* EditTextIterator::first()
+{
+ init();
+ if (mnCol == -1)
+ return nullptr;
+ return seek();
+}
+
+const EditTextObject* EditTextIterator::next()
+{
+ if (mnCol == -1)
+ return nullptr;
+
+ if (maPos.first == miEnd)
+ return nullptr;
+
+ // increment position by one
+ if (maPos.second + 1 < maPos.first->size)
+ // Increment within the block.
+ ++maPos.second;
+ else
+ incBlock();
+
+ return seek();
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/fillinfo.cxx b/sc/source/core/data/fillinfo.cxx
new file mode 100644
index 000000000..a6fa1b318
--- /dev/null
+++ b/sc/source/core/data/fillinfo.cxx
@@ -0,0 +1,1064 @@
+/* -*- 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 <editeng/boxitem.hxx>
+#include <editeng/lineitem.hxx>
+#include <editeng/shaditem.hxx>
+#include <editeng/brushitem.hxx>
+#include <svx/framelink.hxx>
+#include <osl/diagnose.h>
+
+#include <fillinfo.hxx>
+#include <document.hxx>
+#include <table.hxx>
+#include <attrib.hxx>
+#include <attarray.hxx>
+#include <markarr.hxx>
+#include <markdata.hxx>
+#include <patattr.hxx>
+#include <poolhelp.hxx>
+#include <docpool.hxx>
+#include <conditio.hxx>
+#include <stlpool.hxx>
+#include <cellvalue.hxx>
+#include <mtvcellfunc.hxx>
+
+#include <algorithm>
+#include <limits>
+#include <vector>
+#include <memory>
+
+// Similar as in output.cxx
+
+static void lcl_GetMergeRange( SCCOL nX, SCROW nY, SCSIZE nArrY,
+ const ScDocument* pDoc, RowInfo* pRowInfo,
+ SCCOL nX1, SCROW nY1, SCTAB nTab,
+ SCCOL& rStartX, SCROW& rStartY, SCCOL& rEndX, SCROW& rEndY )
+{
+ ScCellInfo* pInfo = &pRowInfo[nArrY].cellInfo(nX);
+
+ rStartX = nX;
+ rStartY = nY;
+ bool bHOver = pInfo->bHOverlapped;
+ bool bVOver = pInfo->bVOverlapped;
+ SCCOL nLastCol;
+ SCROW nLastRow;
+
+ while (bHOver) // nY constant
+ {
+ --rStartX;
+ if (rStartX >= nX1 && !pDoc->ColHidden(rStartX, nTab, nullptr, &nLastCol))
+ {
+ bHOver = pRowInfo[nArrY].cellInfo(rStartX).bHOverlapped;
+ bVOver = pRowInfo[nArrY].cellInfo(rStartX).bVOverlapped;
+ }
+ else
+ {
+ ScMF nOverlap = pDoc->GetAttr( rStartX, rStartY, nTab, ATTR_MERGE_FLAG )->GetValue();
+ bHOver = bool(nOverlap & ScMF::Hor);
+ bVOver = bool(nOverlap & ScMF::Ver);
+ }
+ }
+
+ while (bVOver)
+ {
+ --rStartY;
+
+ if (nArrY>0)
+ --nArrY; // local copy !
+
+ if (rStartX >= nX1 && rStartY >= nY1 &&
+ !pDoc->ColHidden(rStartX, nTab, nullptr, &nLastCol) &&
+ !pDoc->RowHidden(rStartY, nTab, nullptr, &nLastRow) &&
+ pRowInfo[nArrY].nRowNo == rStartY)
+ {
+ bVOver = pRowInfo[nArrY].cellInfo(rStartX).bVOverlapped;
+ }
+ else
+ {
+ ScMF nOverlap = pDoc->GetAttr(
+ rStartX, rStartY, nTab, ATTR_MERGE_FLAG )->GetValue();
+ bVOver = bool(nOverlap & ScMF::Ver);
+ }
+ }
+
+ const ScMergeAttr* pMerge;
+ if (rStartX >= nX1 && rStartY >= nY1 &&
+ !pDoc->ColHidden(rStartX, nTab, nullptr, &nLastCol) &&
+ !pDoc->RowHidden(rStartY, nTab, nullptr, &nLastRow) &&
+ pRowInfo[nArrY].nRowNo == rStartY)
+ {
+ pMerge = &pRowInfo[nArrY].cellInfo(rStartX).pPatternAttr->
+ GetItem(ATTR_MERGE);
+ }
+ else
+ pMerge = pDoc->GetAttr(rStartX,rStartY,nTab,ATTR_MERGE);
+
+ rEndX = rStartX + pMerge->GetColMerge() - 1;
+ rEndY = rStartY + pMerge->GetRowMerge() - 1;
+}
+
+namespace {
+
+class RowInfoFiller
+{
+ ScDocument& mrDoc;
+ SCTAB mnTab;
+ RowInfo* mpRowInfo;
+ SCCOL mnCol;
+ SCSIZE mnArrY;
+ SCCOL mnStartCol;
+ SCROW mnHiddenEndRow;
+ bool mbHiddenRow;
+
+ bool isHidden(size_t nRow)
+ {
+ SCROW nThisRow = static_cast<SCROW>(nRow);
+ if (nThisRow > mnHiddenEndRow)
+ mbHiddenRow = mrDoc.RowHidden(nThisRow, mnTab, nullptr, &mnHiddenEndRow);
+ return mbHiddenRow;
+ }
+
+ void alignArray(size_t nRow)
+ {
+ while (mpRowInfo[mnArrY].nRowNo < static_cast<SCROW>(nRow))
+ ++mnArrY;
+ }
+
+ void setInfo(size_t nRow, const ScRefCellValue& rCell)
+ {
+ alignArray(nRow);
+
+ RowInfo& rThisRowInfo = mpRowInfo[mnArrY];
+ if(mnCol >= mnStartCol-1)
+ rThisRowInfo.cellInfo(mnCol).maCell = rCell;
+ rThisRowInfo.basicCellInfo(mnCol).bEmptyCellText = false;
+ ++mnArrY;
+ }
+
+public:
+ RowInfoFiller(ScDocument& rDoc, SCTAB nTab, RowInfo* pRowInfo, SCCOL nCol, SCSIZE nArrY, SCCOL nStartCol) :
+ mrDoc(rDoc), mnTab(nTab), mpRowInfo(pRowInfo), mnCol(nCol), mnArrY(nArrY), mnStartCol(nStartCol),
+ mnHiddenEndRow(-1), mbHiddenRow(false) {}
+
+ void operator() (size_t nRow, double fVal)
+ {
+ if (!isHidden(nRow))
+ setInfo(nRow, ScRefCellValue(fVal));
+ }
+
+ void operator() (size_t nRow, const svl::SharedString& rStr)
+ {
+ if (!isHidden(nRow))
+ setInfo(nRow, ScRefCellValue(&rStr));
+ }
+
+ void operator() (size_t nRow, const EditTextObject* p)
+ {
+ if (!isHidden(nRow))
+ setInfo(nRow, ScRefCellValue(p));
+ }
+
+ void operator() (size_t nRow, const ScFormulaCell* p)
+ {
+ if (!isHidden(nRow))
+ setInfo(nRow, ScRefCellValue(const_cast<ScFormulaCell*>(p)));
+ }
+};
+
+bool isRotateItemUsed(const ScDocumentPool *pPool)
+{
+ return pPool->GetItemCount2( ATTR_ROTATE_VALUE ) > 0;
+}
+
+void initRowInfo(const ScDocument* pDoc, RowInfo* pRowInfo, const SCSIZE nMaxRow,
+ double fRowScale, SCROW nRow1, SCTAB nTab, SCROW& rYExtra, SCSIZE& rArrRow, SCROW& rRow2)
+{
+ sal_uInt16 nDocHeight = ScGlobal::nStdRowHeight;
+ SCROW nDocHeightEndRow = -1;
+ for (SCROW nSignedY=nRow1-1; nSignedY<=rYExtra; nSignedY++)
+ {
+ SCROW nY;
+ if (nSignedY >= 0)
+ nY = nSignedY;
+ else
+ nY = pDoc->MaxRow()+1; // invalid
+
+ if (nY > nDocHeightEndRow)
+ {
+ if (pDoc->ValidRow(nY))
+ nDocHeight = pDoc->GetRowHeight( nY, nTab, nullptr, &nDocHeightEndRow );
+ else
+ nDocHeight = ScGlobal::nStdRowHeight;
+ }
+
+ if ( rArrRow==0 || nDocHeight || nY > pDoc->MaxRow() )
+ {
+ RowInfo* pThisRowInfo = &pRowInfo[rArrRow];
+ // pThisRowInfo->pCellInfo is set below using allocCellInfo()
+
+ sal_uInt16 nHeight = static_cast<sal_uInt16>(
+ std::clamp(
+ nDocHeight * fRowScale, 1.0, double(std::numeric_limits<sal_uInt16>::max())));
+
+ pThisRowInfo->nRowNo = nY; //TODO: case < 0 ?
+ pThisRowInfo->nHeight = nHeight;
+ pThisRowInfo->bEmptyBack = true;
+ pThisRowInfo->bChanged = true;
+ pThisRowInfo->bAutoFilter = false;
+ pThisRowInfo->bPivotButton = false;
+ pThisRowInfo->nRotMaxCol = SC_ROTMAX_NONE;
+
+ ++rArrRow;
+ if (rArrRow >= nMaxRow)
+ {
+ OSL_FAIL("FillInfo: Range too big" );
+ rYExtra = nSignedY; // End
+ rRow2 = rYExtra - 1; // Adjust range
+ }
+ }
+ else
+ if (nSignedY == rYExtra) // hidden additional line?
+ ++rYExtra;
+ }
+}
+
+void initCellInfo(RowInfo* pRowInfo, SCSIZE nArrCount, SCCOL nStartCol, SCCOL nRotMax,
+ const SvxShadowItem* pDefShadow)
+{
+ for (SCSIZE nArrRow = 0; nArrRow < nArrCount; ++nArrRow)
+ {
+ RowInfo& rThisRowInfo = pRowInfo[nArrRow];
+ // A lot of memory (and performance allocating and initializing it) can
+ // be saved if we do not allocate CellInfo for columns before nStartCol.
+ // But code in ScOutputData::SetCellRotation(), ScOutputData::DrawRotatedFrame()
+ // and ScOutputData::SetCellRotations() accesses those. That depends on
+ // nRotMaxCol being set to something else than none, and the value is already
+ // initialized here. So allocate all those cells starting from column 0 only if needed.
+ SCCOL nMinCol = rThisRowInfo.nRotMaxCol != SC_ROTMAX_NONE ? 0 : nStartCol;
+ rThisRowInfo.allocCellInfo( nMinCol, nRotMax + 1 );
+
+ for (SCCOL nCol = nMinCol-1; nCol <= nRotMax+1; ++nCol) // Preassign cell info
+ {
+ ScCellInfo& rInfo = rThisRowInfo.cellInfo(nCol);
+ rInfo.pShadowAttr = pDefShadow;
+ }
+ }
+}
+
+void initColWidths(RowInfo* pRowInfo, const ScDocument* pDoc, double fColScale, SCTAB nTab, SCCOL nCol2, SCCOL nRotMax)
+{
+ for (SCCOL nCol=nCol2+2; nCol<=nRotMax+1; nCol++) // Add remaining widths
+ {
+ if ( pDoc->ValidCol(nCol) )
+ {
+ if (!pDoc->ColHidden(nCol, nTab))
+ {
+ sal_uInt16 nThisWidth = static_cast<sal_uInt16>(pDoc->GetColWidth( nCol, nTab ) * fColScale);
+ if (!nThisWidth)
+ nThisWidth = 1;
+
+ pRowInfo[0].basicCellInfo(nCol).nWidth = nThisWidth;
+ }
+ }
+ }
+}
+
+bool handleConditionalFormat(ScConditionalFormatList& rCondFormList, const ScCondFormatIndexes& rCondFormats,
+ ScCellInfo* pInfo, ScTableInfo* pTableInfo, ScStyleSheetPool* pStlPool,
+ const ScAddress& rAddr, bool& bHidden, bool& bHideFormula, bool bTabProtect)
+{
+ bool bFound = false;
+ bool bAnyCondition = false;
+ for(const auto& rCondFormat : rCondFormats)
+ {
+ ScConditionalFormat* pCondForm = rCondFormList.GetFormat(rCondFormat);
+ if(!pCondForm)
+ continue;
+
+ ScCondFormatData aData = pCondForm->GetData(
+ pInfo->maCell, rAddr);
+ if (!aData.aStyleName.isEmpty())
+ {
+ SfxStyleSheetBase* pStyleSheet =
+ pStlPool->Find( aData.aStyleName, SfxStyleFamily::Para );
+ if ( pStyleSheet )
+ {
+ //TODO: cache Style-Sets !!!
+ pInfo->pConditionSet = &pStyleSheet->GetItemSet();
+ bAnyCondition = true;
+
+ // TODO: moggi: looks like there is a bug around bHidden and bHideFormula
+ // They are normally for the whole pattern and not for a single cell
+ // we need to check already here for protected cells
+ const ScProtectionAttr* pProtAttr;
+ if ( bTabProtect && (pProtAttr = pInfo->pConditionSet->GetItemIfSet( ATTR_PROTECTION )) )
+ {
+ bHidden = pProtAttr->GetHideCell();
+ bHideFormula = pProtAttr->GetHideFormula();
+
+ }
+ bFound = true;
+
+ }
+ // if style is not there, treat like no condition
+ }
+
+ if(aData.mxColorScale)
+ {
+ pInfo->mxColorScale = aData.mxColorScale;
+ bFound = true;
+ }
+
+ if(aData.pDataBar)
+ {
+ pInfo->pDataBar = aData.pDataBar.get();
+ pTableInfo->addDataBarInfo(std::move(aData.pDataBar));
+ bFound = true;
+ }
+
+ if(aData.pIconSet)
+ {
+ pInfo->pIconSet = aData.pIconSet.get();
+ pTableInfo->addIconSetInfo(std::move(aData.pIconSet));
+ bFound = true;
+ }
+
+ if (bFound)
+ break;
+ }
+
+ return bAnyCondition;
+}
+
+}
+
+void ScDocument::FillInfo(
+ ScTableInfo& rTabInfo, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ SCTAB nTab, double fColScale, double fRowScale, bool bPageMode, bool bFormulaMode,
+ const ScMarkData* pMarkData )
+{
+ OSL_ENSURE( maTabs[nTab], "Table does not exist" );
+
+ bool bLayoutRTL = IsLayoutRTL( nTab );
+
+ ScDocumentPool* pPool = mxPoolHelper->GetDocPool();
+ ScStyleSheetPool* pStlPool = mxPoolHelper->GetStylePool();
+
+ RowInfo* pRowInfo = rTabInfo.mpRowInfo.get();
+
+ const SvxBrushItem* pDefBackground =
+ &pPool->GetDefaultItem( ATTR_BACKGROUND );
+ const ScMergeAttr* pDefMerge =
+ &pPool->GetDefaultItem( ATTR_MERGE );
+ const SvxShadowItem* pDefShadow =
+ &pPool->GetDefaultItem( ATTR_SHADOW );
+
+ SCSIZE nArrRow;
+ SCSIZE nArrCount;
+ bool bAnyMerged = false;
+ bool bAnyShadow = false;
+ bool bAnyCondition = false;
+ bool bAnyPreview = false;
+
+ bool bTabProtect = IsTabProtected(nTab);
+
+ // first only the entries for the entire column
+
+ nArrRow=0;
+ SCROW nYExtra = nRow2+1;
+ initRowInfo(this, pRowInfo, rTabInfo.mnArrCapacity, fRowScale, nRow1,
+ nTab, nYExtra, nArrRow, nRow2);
+ nArrCount = nArrRow; // incl. Dummys
+
+ // Rotated text...
+
+ // Is Attribute really used in document?
+ bool bAnyItem = isRotateItemUsed(pPool);
+
+ SCCOL nRotMax = nCol2;
+ if ( bAnyItem && HasAttrib( 0, nRow1, nTab, MaxCol(), nRow2+1, nTab,
+ HasAttrFlags::Rotate | HasAttrFlags::Conditional ) )
+ {
+ //TODO: check Conditionals also for HasAttrFlags::Rotate ????
+
+ OSL_ENSURE( nArrCount>2, "nArrCount too small" );
+ FindMaxRotCol( nTab, &pRowInfo[1], nArrCount-1, nCol1, nCol2 );
+ // FindMaxRotCol sets nRotMaxCol
+
+ for (nArrRow=0; nArrRow<nArrCount; nArrRow++)
+ if (pRowInfo[nArrRow].nRotMaxCol != SC_ROTMAX_NONE && pRowInfo[nArrRow].nRotMaxCol > nRotMax)
+ nRotMax = pRowInfo[nArrRow].nRotMaxCol;
+ }
+
+ // Allocate cell information only after the test rotation
+ // to nRotMax due to nRotateDir Flag
+ initCellInfo(pRowInfo, nArrCount, nCol1, nRotMax, pDefShadow);
+
+ initColWidths(pRowInfo, this, fColScale, nTab, nCol2, nRotMax);
+
+ ScConditionalFormatList* pCondFormList = GetCondFormList(nTab);
+ if (pCondFormList)
+ pCondFormList->startRendering();
+
+ SCCOL nLastHiddenCheckedCol = -2;
+ bool bColHidden = false;
+ for (SCCOL nCol=-1; nCol<=nCol2+1; nCol++) // collect basic info also for all previous cols, and left & right + 1
+ {
+ if (ValidCol(nCol))
+ {
+ // #i58049#, #i57939# Hidden columns must be skipped here, or their attributes
+ // will disturb the output
+
+ if (nCol > nLastHiddenCheckedCol)
+ bColHidden = ColHidden(nCol, nTab, nullptr, &nLastHiddenCheckedCol);
+ // TODO: Optimize this loop.
+ if (!bColHidden)
+ {
+ sal_uInt16 nColWidth = GetColWidth( nCol, nTab, false ); // false=no need to check for hidden, checked above
+ sal_uInt16 nThisWidth = static_cast<sal_uInt16>(std::clamp(nColWidth * fColScale, 1.0, double(std::numeric_limits<sal_uInt16>::max())));
+
+ pRowInfo[0].basicCellInfo(nCol).nWidth = nThisWidth; //TODO: this should be enough
+
+ const ScAttrArray* pThisAttrArr; // Attribute
+ if (nCol < maTabs[nTab]->GetAllocatedColumnsCount())
+ {
+ ScColumn* pThisCol = &maTabs[nTab]->aCol[nCol]; // Column data
+
+ nArrRow = 1;
+ // Iterate between rows nY1 and nY2 and pick up non-empty
+ // cells that are not hidden.
+ RowInfoFiller aFunc(*this, nTab, pRowInfo, nCol, nArrRow, nCol1);
+ sc::ParseAllNonEmpty(pThisCol->maCells.begin(), pThisCol->maCells, nRow1, nRow2,
+ aFunc);
+
+ pThisAttrArr = pThisCol->pAttrArray.get();
+ }
+ else
+ pThisAttrArr = &maTabs[nTab]->aDefaultColData.AttrArray();
+
+ if (nCol+1 >= nCol1) // Attribute/Blockmark from nX1-1
+ {
+ nArrRow = 0;
+
+ SCROW nCurRow=nRow1; // single rows
+ if (nCurRow>0)
+ --nCurRow; // 1 more on top
+ else
+ nArrRow = 1;
+
+ SCROW nThisRow;
+ SCSIZE nIndex;
+ if ( pThisAttrArr->Count() )
+ (void) pThisAttrArr->Search( nCurRow, nIndex );
+ else
+ nIndex = 0;
+
+ do
+ {
+ const ScPatternAttr* pPattern = nullptr;
+ if ( pThisAttrArr->Count() )
+ {
+ nThisRow = pThisAttrArr->mvData[nIndex].nEndRow; // End of range
+ pPattern = pThisAttrArr->mvData[nIndex].pPattern;
+ }
+ else
+ {
+ nThisRow = MaxRow();
+ pPattern = GetDefPattern();
+ }
+
+ const SvxBrushItem* pBackground = &pPattern->GetItem(ATTR_BACKGROUND);
+ const SvxBoxItem* pLinesAttr = &pPattern->GetItem(ATTR_BORDER);
+
+ const SvxLineItem* pTLBRLine = &pPattern->GetItem( ATTR_BORDER_TLBR );
+ const SvxLineItem* pBLTRLine = &pPattern->GetItem( ATTR_BORDER_BLTR );
+
+ const SvxShadowItem* pShadowAttr = &pPattern->GetItem(ATTR_SHADOW);
+ if (pShadowAttr != pDefShadow)
+ bAnyShadow = true;
+
+ const ScMergeAttr* pMergeAttr = &pPattern->GetItem(ATTR_MERGE);
+ bool bMerged = ( pMergeAttr != pDefMerge && *pMergeAttr != *pDefMerge );
+ ScMF nOverlap = pPattern->GetItemSet().
+ Get(ATTR_MERGE_FLAG).GetValue();
+ bool bHOverlapped(nOverlap & ScMF::Hor);
+ bool bVOverlapped(nOverlap & ScMF::Ver);
+ bool bAutoFilter(nOverlap & ScMF::Auto);
+ bool bPivotButton(nOverlap & ScMF::Button);
+ bool bScenario(nOverlap & ScMF::Scenario);
+ bool bPivotPopupButton(nOverlap & ScMF::ButtonPopup);
+ bool bFilterActive(nOverlap & ScMF::HiddenMember);
+ if (bMerged||bHOverlapped||bVOverlapped)
+ bAnyMerged = true; // internal
+
+ bool bHidden, bHideFormula;
+ if (bTabProtect)
+ {
+ const ScProtectionAttr& rProtAttr = pPattern->GetItem(ATTR_PROTECTION);
+ bHidden = rProtAttr.GetHideCell();
+ bHideFormula = rProtAttr.GetHideFormula();
+ }
+ else
+ bHidden = bHideFormula = false;
+
+ const ScCondFormatIndexes& rCondFormats = pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData();
+ bool bContainsCondFormat = !rCondFormats.empty();
+
+ do
+ {
+ SCROW nLastHiddenRow = -1;
+ bool bRowHidden = RowHidden(nCurRow, nTab, nullptr, &nLastHiddenRow);
+ if ( nArrRow==0 || !bRowHidden )
+ {
+ if ( GetPreviewCellStyle( nCol, nCurRow, nTab ) != nullptr )
+ bAnyPreview = true;
+ RowInfo* pThisRowInfo = &pRowInfo[nArrRow];
+ if (pBackground != pDefBackground) // Column background == Default ?
+ pThisRowInfo->bEmptyBack = false;
+ if (bContainsCondFormat)
+ pThisRowInfo->bEmptyBack = false;
+ if (bAutoFilter)
+ pThisRowInfo->bAutoFilter = true;
+ if (bPivotButton || bPivotPopupButton)
+ pThisRowInfo->bPivotButton = true;
+
+ ScCellInfo* pInfo = &pThisRowInfo->cellInfo(nCol);
+ ScBasicCellInfo* pBasicInfo = &pThisRowInfo->basicCellInfo(nCol);
+ pInfo->pBackground = pBackground;
+ pInfo->pPatternAttr = pPattern;
+ pInfo->bMerged = bMerged;
+ pInfo->bHOverlapped = bHOverlapped;
+ pInfo->bVOverlapped = bVOverlapped;
+ pInfo->bAutoFilter = bAutoFilter;
+ pInfo->bPivotButton = bPivotButton;
+ pInfo->bPivotPopupButton = bPivotPopupButton;
+ pInfo->bFilterActive = bFilterActive;
+ pInfo->pLinesAttr = pLinesAttr;
+ pInfo->mpTLBRLine = pTLBRLine;
+ pInfo->mpBLTRLine = pBLTRLine;
+ pInfo->pShadowAttr = pShadowAttr;
+ // nWidth is no longer set individually
+
+ if (bScenario)
+ {
+ pInfo->pBackground = ScGlobal::GetButtonBrushItem();
+ pThisRowInfo->bEmptyBack = false;
+ }
+
+ if (bContainsCondFormat && pCondFormList)
+ {
+ bAnyCondition |= handleConditionalFormat(*pCondFormList, rCondFormats,
+ pInfo, &rTabInfo, pStlPool, ScAddress(nCol, nCurRow, nTab),
+ bHidden, bHideFormula, bTabProtect);
+ }
+
+ if (bHidden || (bFormulaMode && bHideFormula && pInfo->maCell.meType == CELLTYPE_FORMULA))
+ pBasicInfo->bEmptyCellText = true;
+
+ ++nArrRow;
+ }
+ else if (nLastHiddenRow >= 0)
+ {
+ nCurRow = nLastHiddenRow;
+ if (nCurRow > nThisRow)
+ nCurRow = nThisRow;
+ }
+ ++nCurRow;
+ }
+ while (nCurRow <= nThisRow && nCurRow <= nYExtra);
+ ++nIndex;
+ }
+ while ( nIndex < pThisAttrArr->Count() && nThisRow < nYExtra );
+
+ if (pMarkData && pMarkData->IsMultiMarked())
+ {
+ // Block marks
+ ScMarkArray aThisMarkArr(pMarkData->GetMarkArray( nCol ));
+ nArrRow = 1;
+ nCurRow = nRow1; // single rows
+
+ if ( aThisMarkArr.Search( nRow1, nIndex ) )
+ {
+ do
+ {
+ nThisRow=aThisMarkArr.mvData[nIndex].nRow; // End of range
+
+ do
+ {
+ if ( !RowHidden( nCurRow,nTab ) )
+ {
+ ++nArrRow;
+ }
+ ++nCurRow;
+ }
+ while (nCurRow <= nThisRow && nCurRow <= nRow2);
+ ++nIndex;
+ }
+ while ( nIndex < aThisMarkArr.mvData.size() && nThisRow < nRow2 );
+ }
+ }
+ }
+ else // columns in front
+ {
+ for (nArrRow=1; nArrRow+1<nArrCount; nArrRow++)
+ pRowInfo[nArrRow].basicCellInfo(nCol).nWidth = nThisWidth; //TODO: or check only 0 ??
+ }
+ }
+ }
+ else
+ pRowInfo[0].basicCellInfo(nCol).nWidth = STD_COL_WIDTH;
+ // STD_COL_WIDTH farthest to the left and right is needed for DrawExtraShadow
+ }
+
+ if (pCondFormList)
+ pCondFormList->endRendering();
+
+ // evaluate conditional formatting
+ std::vector< std::unique_ptr<ScPatternAttr> > aAltPatterns;
+ // favour preview over condition
+ if (bAnyCondition || bAnyPreview)
+ {
+ for (nArrRow=0; nArrRow<nArrCount; nArrRow++)
+ {
+ for (SCCOL nCol=nCol1-1; nCol<=nCol2+1; nCol++) // 1 more left and right
+ {
+ ScCellInfo* pInfo = &pRowInfo[nArrRow].cellInfo(nCol);
+ ScPatternAttr* pModifiedPatt = nullptr;
+
+ if ( ValidCol(nCol) && pRowInfo[nArrRow].nRowNo <= MaxRow() )
+ {
+ if ( ScStyleSheet* pPreviewStyle = GetPreviewCellStyle( nCol, pRowInfo[nArrRow].nRowNo, nTab ) )
+ {
+ aAltPatterns.push_back( std::make_unique<ScPatternAttr>( *pInfo->pPatternAttr ) );
+ pModifiedPatt = aAltPatterns.back().get();
+ pModifiedPatt->SetStyleSheet( pPreviewStyle );
+ }
+ }
+ // favour preview over condition
+ const SfxItemSet* pCondSet = pModifiedPatt ? &pModifiedPatt->GetItemSet() : pInfo->pConditionSet;
+
+ if (pCondSet)
+ {
+ // Background
+ if ( const SvxBrushItem* pItem = pCondSet->GetItemIfSet( ATTR_BACKGROUND ) )
+ {
+ pInfo->pBackground = pItem;
+ pRowInfo[nArrRow].bEmptyBack = false;
+ }
+
+ // Border
+ if ( const SvxBoxItem* pItem = pCondSet->GetItemIfSet( ATTR_BORDER ) )
+ pInfo->pLinesAttr = pItem;
+
+ if ( const SvxLineItem* pItem = pCondSet->GetItemIfSet( ATTR_BORDER_TLBR ) )
+ pInfo->mpTLBRLine = pItem;
+ if ( const SvxLineItem* pItem = pCondSet->GetItemIfSet( ATTR_BORDER_BLTR ) )
+ pInfo->mpBLTRLine = pItem;
+
+ // Shadow
+ if ( const SvxShadowItem* pItem = pCondSet->GetItemIfSet( ATTR_SHADOW ) )
+ {
+ pInfo->pShadowAttr = pItem;
+ bAnyShadow = true;
+ }
+ }
+ if( bAnyCondition && pInfo->mxColorScale)
+ {
+ pRowInfo[nArrRow].bEmptyBack = false;
+ pInfo->pBackground = &pPool->Put(SvxBrushItem(*pInfo->mxColorScale, ATTR_BACKGROUND));
+ }
+ }
+ }
+ }
+
+ // End conditional formatting
+
+ // Adjust data from merged cells
+
+ if (bAnyMerged)
+ {
+ for (nArrRow=0; nArrRow<nArrCount; nArrRow++)
+ {
+ RowInfo* pThisRowInfo = &pRowInfo[nArrRow];
+ SCROW nSignedY = nArrRow ? pThisRowInfo->nRowNo : nRow1-1;
+
+ for (SCCOL nCol=nCol1-1; nCol<=nCol2+1; nCol++) // 1 more left and right
+ {
+ ScCellInfo* pInfo = &pThisRowInfo->cellInfo(nCol);
+
+ if (pInfo->bMerged || pInfo->bHOverlapped || pInfo->bVOverlapped)
+ {
+ SCCOL nStartX;
+ SCROW nStartY;
+ SCCOL nEndX;
+ SCROW nEndY;
+ lcl_GetMergeRange( nCol,nSignedY, nArrRow, this,pRowInfo, nCol1,nRow1,nTab,
+ nStartX,nStartY, nEndX,nEndY );
+ const ScPatternAttr* pStartPattern = GetPattern( nStartX,nStartY,nTab );
+ const SfxItemSet* pStartCond = GetCondResult( nStartX,nStartY,nTab );
+
+ // Copy Background (or in output.cxx)
+
+ const SvxBrushItem* pBrushItem = nullptr;
+ if ( !pStartCond ||
+ !(pBrushItem = pStartCond->GetItemIfSet(ATTR_BACKGROUND)) )
+ pBrushItem = &pStartPattern->GetItem(ATTR_BACKGROUND);
+ pInfo->pBackground = pBrushItem;
+ pRowInfo[nArrRow].bEmptyBack = false;
+
+ // Shadow
+
+ const SvxShadowItem* pShadowItem = nullptr;
+ if ( !pStartCond ||
+ !(pShadowItem = pStartCond->GetItemIfSet(ATTR_SHADOW)) )
+ pShadowItem = &pStartPattern->GetItem(ATTR_SHADOW);
+ pInfo->pShadowAttr = pShadowItem;
+ if (pInfo->pShadowAttr != pDefShadow)
+ bAnyShadow = true;
+ }
+ }
+ }
+ }
+
+ if (bAnyShadow) // distribute Shadow
+ {
+ for (nArrRow=0; nArrRow<nArrCount; nArrRow++)
+ {
+ bool bTop = ( nArrRow == 0 );
+ bool bBottom = ( nArrRow+1 == nArrCount );
+
+ for (SCCOL nCol=nCol1-1; nCol<=nCol2+1; nCol++) // 1 more left and right
+ {
+ bool bLeft = ( nCol == nCol1-1 );
+ bool bRight = ( nCol == nCol2+1 );
+
+ ScCellInfo* pInfo = &pRowInfo[nArrRow].cellInfo(nCol);
+ const SvxShadowItem* pThisAttr = pInfo->pShadowAttr;
+ SvxShadowLocation eLoc = pThisAttr ? pThisAttr->GetLocation() : SvxShadowLocation::NONE;
+ if (eLoc != SvxShadowLocation::NONE)
+ {
+ // or test on != eLoc
+
+ SCCOL nDxPos = 1;
+ SCCOL nDxNeg = -1;
+
+ while ( nCol+nDxPos < nCol2+1 && pRowInfo[0].basicCellInfo(nCol+nDxPos).nWidth == 0 )
+ ++nDxPos;
+ while ( nCol+nDxNeg > nCol1-1 && pRowInfo[0].basicCellInfo(nCol+nDxNeg).nWidth == 0 )
+ --nDxNeg;
+
+ bool bLeftDiff = !bLeft &&
+ pRowInfo[nArrRow].cellInfo(nCol+nDxNeg).pShadowAttr->GetLocation() == SvxShadowLocation::NONE;
+ bool bRightDiff = !bRight &&
+ pRowInfo[nArrRow].cellInfo(nCol+nDxPos).pShadowAttr->GetLocation() == SvxShadowLocation::NONE;
+ bool bTopDiff = !bTop &&
+ pRowInfo[nArrRow-1].cellInfo(nCol).pShadowAttr->GetLocation() == SvxShadowLocation::NONE;
+ bool bBottomDiff = !bBottom &&
+ pRowInfo[nArrRow+1].cellInfo(nCol).pShadowAttr->GetLocation() == SvxShadowLocation::NONE;
+
+ if ( bLayoutRTL )
+ {
+ switch (eLoc)
+ {
+ case SvxShadowLocation::BottomRight: eLoc = SvxShadowLocation::BottomLeft; break;
+ case SvxShadowLocation::BottomLeft: eLoc = SvxShadowLocation::BottomRight; break;
+ case SvxShadowLocation::TopRight: eLoc = SvxShadowLocation::TopLeft; break;
+ case SvxShadowLocation::TopLeft: eLoc = SvxShadowLocation::TopRight; break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+ }
+
+ switch (eLoc)
+ {
+ case SvxShadowLocation::BottomRight:
+ if (bBottomDiff)
+ {
+ pRowInfo[nArrRow+1].cellInfo(nCol).pHShadowOrigin = pThisAttr;
+ pRowInfo[nArrRow+1].cellInfo(nCol).eHShadowPart =
+ bLeftDiff ? SC_SHADOW_HSTART : SC_SHADOW_HORIZ;
+ }
+ if (bRightDiff)
+ {
+ pRowInfo[nArrRow].cellInfo(nCol+1).pVShadowOrigin = pThisAttr;
+ pRowInfo[nArrRow].cellInfo(nCol+1).eVShadowPart =
+ bTopDiff ? SC_SHADOW_VSTART : SC_SHADOW_VERT;
+ }
+ if (bBottomDiff && bRightDiff)
+ {
+ pRowInfo[nArrRow+1].cellInfo(nCol+1).pHShadowOrigin = pThisAttr;
+ pRowInfo[nArrRow+1].cellInfo(nCol+1).eHShadowPart = SC_SHADOW_CORNER;
+ }
+ break;
+
+ case SvxShadowLocation::BottomLeft:
+ if (bBottomDiff)
+ {
+ pRowInfo[nArrRow+1].cellInfo(nCol).pHShadowOrigin = pThisAttr;
+ pRowInfo[nArrRow+1].cellInfo(nCol).eHShadowPart =
+ bRightDiff ? SC_SHADOW_HSTART : SC_SHADOW_HORIZ;
+ }
+ if (bLeftDiff)
+ {
+ pRowInfo[nArrRow].cellInfo(nCol-1).pVShadowOrigin = pThisAttr;
+ pRowInfo[nArrRow].cellInfo(nCol-1).eVShadowPart =
+ bTopDiff ? SC_SHADOW_VSTART : SC_SHADOW_VERT;
+ }
+ if (bBottomDiff && bLeftDiff)
+ {
+ pRowInfo[nArrRow+1].cellInfo(nCol-1).pHShadowOrigin = pThisAttr;
+ pRowInfo[nArrRow+1].cellInfo(nCol-1).eHShadowPart = SC_SHADOW_CORNER;
+ }
+ break;
+
+ case SvxShadowLocation::TopRight:
+ if (bTopDiff)
+ {
+ pRowInfo[nArrRow-1].cellInfo(nCol).pHShadowOrigin = pThisAttr;
+ pRowInfo[nArrRow-1].cellInfo(nCol).eHShadowPart =
+ bLeftDiff ? SC_SHADOW_HSTART : SC_SHADOW_HORIZ;
+ }
+ if (bRightDiff)
+ {
+ pRowInfo[nArrRow].cellInfo(nCol+1).pVShadowOrigin = pThisAttr;
+ pRowInfo[nArrRow].cellInfo(nCol+1).eVShadowPart =
+ bBottomDiff ? SC_SHADOW_VSTART : SC_SHADOW_VERT;
+ }
+ if (bTopDiff && bRightDiff)
+ {
+ pRowInfo[nArrRow-1].cellInfo(nCol+1).pHShadowOrigin = pThisAttr;
+ pRowInfo[nArrRow-1].cellInfo(nCol+1).eHShadowPart = SC_SHADOW_CORNER;
+ }
+ break;
+
+ case SvxShadowLocation::TopLeft:
+ if (bTopDiff)
+ {
+ pRowInfo[nArrRow-1].cellInfo(nCol).pHShadowOrigin = pThisAttr;
+ pRowInfo[nArrRow-1].cellInfo(nCol).eHShadowPart =
+ bRightDiff ? SC_SHADOW_HSTART : SC_SHADOW_HORIZ;
+ }
+ if (bLeftDiff)
+ {
+ pRowInfo[nArrRow].cellInfo(nCol-1).pVShadowOrigin = pThisAttr;
+ pRowInfo[nArrRow].cellInfo(nCol-1).eVShadowPart =
+ bBottomDiff ? SC_SHADOW_VSTART : SC_SHADOW_VERT;
+ }
+ if (bTopDiff && bLeftDiff)
+ {
+ pRowInfo[nArrRow-1].cellInfo(nCol-1).pHShadowOrigin = pThisAttr;
+ pRowInfo[nArrRow-1].cellInfo(nCol-1).eHShadowPart = SC_SHADOW_CORNER;
+ }
+ break;
+
+ default:
+ OSL_FAIL("wrong Shadow-Enum");
+ }
+ }
+ }
+ }
+ }
+
+ rTabInfo.mnArrCount = sal::static_int_cast<sal_uInt16>(nArrCount);
+ rTabInfo.mbPageMode = bPageMode;
+
+ // *** create the frame border array ***
+
+ // RowInfo structs are filled in the range [ 0 , nArrCount-1 ],
+ // each RowInfo contains ScCellInfo structs in the range [ nCol1-1 , nCol2+1 ]
+ // and ScBasicCellInfo structs in the range [ -1, nCol2+1 ]
+
+ size_t nColCount = nCol2 - nCol1 + 1 + 2;
+ size_t nRowCount = nArrCount;
+
+ svx::frame::Array& rArray = rTabInfo.maArray;
+ rArray.Initialize( nColCount, nRowCount );
+
+ for( size_t nRow = 0; nRow < nRowCount; ++nRow )
+ {
+ sal_uInt16 nCellInfoY = static_cast< sal_uInt16 >( nRow );
+ RowInfo& rThisRowInfo = pRowInfo[ nCellInfoY ];
+
+ for( SCCOL nCol = nCol1 - 1; nCol <= nCol2 + 1; ++nCol ) // 1 more left and right
+ {
+ const ScCellInfo& rInfo = rThisRowInfo.cellInfo( nCol );
+ const SvxBoxItem* pBox = rInfo.pLinesAttr;
+ const SvxLineItem* pTLBR = rInfo.mpTLBRLine;
+ const SvxLineItem* pBLTR = rInfo.mpBLTRLine;
+
+ size_t colToIndex = -(nCol1 - 1);
+ // These are rArray indexes (0-based), not really rows/columns.
+ size_t nX = nCol + colToIndex;
+ size_t nFirstCol = nX;
+ size_t nFirstRow = nRow;
+
+ // *** merged cells *** -------------------------------------------
+
+ if( !rArray.IsMerged( nX, nRow ) && (rInfo.bMerged || rInfo.bHOverlapped || rInfo.bVOverlapped) )
+ {
+ // *** insert merged range in svx::frame::Array ***
+
+ /* #i69369# top-left cell of a merged range may be located in
+ a hidden column or row. Use lcl_GetMergeRange() to find the
+ complete merged range, then calculate dimensions and
+ document position of the visible range. */
+
+ // note: document rows must be looked up in RowInfo structs
+
+ // current column and row in document coordinates
+ SCCOL nCurrDocCol = nCol;
+ SCROW nCurrDocRow = static_cast< SCROW >( (nCellInfoY > 0) ? rThisRowInfo.nRowNo : (nRow1 - 1) );
+
+ // find entire merged range in document, returns signed document coordinates
+ SCCOL nFirstRealDocColS, nLastRealDocColS;
+ SCROW nFirstRealDocRowS, nLastRealDocRowS;
+ lcl_GetMergeRange( nCurrDocCol, nCurrDocRow,
+ nCellInfoY, this, pRowInfo, nCol1,nRow1,nTab,
+ nFirstRealDocColS, nFirstRealDocRowS, nLastRealDocColS, nLastRealDocRowS );
+
+ // *complete* merged range in document coordinates
+ SCCOL nFirstRealDocCol = nFirstRealDocColS;
+ SCROW nFirstRealDocRow = nFirstRealDocRowS;
+ SCCOL nLastRealDocCol = nLastRealDocColS;
+ SCROW nLastRealDocRow = nLastRealDocRowS;
+
+ // first visible column (nCol1-1 is first processed document column)
+ SCCOL nFirstDocCol = (nCol1 > 0) ? ::std::max< SCCOL >( nFirstRealDocCol, nCol1 - 1 ) : nFirstRealDocCol;
+ nFirstCol = nFirstDocCol + colToIndex;
+
+ // last visible column (nCol2+1 is last processed document column)
+ SCCOL nLastDocCol = (nCol2 < MaxCol()) ? ::std::min< SCCOL >( nLastRealDocCol, nCol2 + 1 ) : nLastRealDocCol;
+ size_t nLastCol = nLastDocCol + colToIndex;
+
+ // first visible row
+ sal_uInt16 nFirstCellInfoY = nCellInfoY;
+ while( ((nFirstCellInfoY > 1) && (pRowInfo[ nFirstCellInfoY - 1 ].nRowNo >= nFirstRealDocRow)) ||
+ ((nFirstCellInfoY == 1) && (static_cast< SCROW >( nRow1 - 1 ) >= nFirstRealDocRow)) )
+ --nFirstCellInfoY;
+ SCROW nFirstDocRow = (nFirstCellInfoY > 0) ? pRowInfo[ nFirstCellInfoY ].nRowNo : static_cast< SCROW >( nRow1 - 1 );
+ nFirstRow = static_cast< size_t >( nFirstCellInfoY );
+
+ // last visible row
+ sal_uInt16 nLastCellInfoY = nCellInfoY;
+ while( (sal::static_int_cast<SCSIZE>(nLastCellInfoY + 1) < nArrCount) &&
+ (pRowInfo[ nLastCellInfoY + 1 ].nRowNo <= nLastRealDocRow) )
+ ++nLastCellInfoY;
+ SCROW nLastDocRow = (nLastCellInfoY > 0) ? pRowInfo[ nLastCellInfoY ].nRowNo : static_cast< SCROW >( nRow1 - 1 );
+ size_t nLastRow = static_cast< size_t >( nLastCellInfoY );
+
+ // insert merged range
+ rArray.SetMergedRange( nFirstCol, nFirstRow, nLastCol, nLastRow );
+
+ // *** find additional size not included in svx::frame::Array ***
+
+ // additional space before first column
+ if( nFirstCol == 0 )
+ {
+ tools::Long nSize = 0;
+ for( SCCOL nDocCol = nFirstRealDocCol; nDocCol < nFirstDocCol; ++nDocCol )
+ nSize += std::max( tools::Long(GetColWidth( nDocCol, nTab ) * fColScale), tools::Long(1) );
+ rArray.SetAddMergedLeftSize( nX, nRow, nSize );
+ }
+ // additional space after last column
+ if( nLastCol + 1 == nColCount )
+ {
+ tools::Long nSize = 0;
+ for( SCCOL nDocCol = nLastDocCol + 1; nDocCol <= nLastRealDocCol; ++nDocCol )
+ nSize += std::max( tools::Long(GetColWidth( nDocCol, nTab ) * fColScale), tools::Long(1) );
+ rArray.SetAddMergedRightSize( nX, nRow, nSize );
+ }
+ // additional space above first row
+ if( nFirstRow == 0 )
+ {
+ tools::Long nSize = 0;
+ for( SCROW nDocRow = nFirstRealDocRow; nDocRow < nFirstDocRow; ++nDocRow )
+ nSize += std::max( tools::Long(GetRowHeight( nDocRow, nTab ) * fRowScale), tools::Long(1) );
+ rArray.SetAddMergedTopSize( nX, nRow, nSize );
+ }
+ // additional space beyond last row
+ if( nLastRow + 1 == nRowCount )
+ {
+ tools::Long nSize = 0;
+ for( SCROW nDocRow = nLastDocRow + 1; nDocRow <= nLastRealDocRow; ++nDocRow )
+ nSize += std::max( tools::Long(GetRowHeight( nDocRow, nTab ) * fRowScale), tools::Long(1) );
+ rArray.SetAddMergedBottomSize( nX, nRow, nSize );
+ }
+
+ // *** use line attributes from real origin cell ***
+
+ if( (nFirstRealDocCol != nCurrDocCol) || (nFirstRealDocRow != nCurrDocRow) )
+ {
+ if( const ScPatternAttr* pPattern = GetPattern( nFirstRealDocCol, nFirstRealDocRow, nTab ) )
+ {
+ const SfxItemSet* pCond = GetCondResult( nFirstRealDocCol, nFirstRealDocRow, nTab );
+ pBox = &pPattern->GetItem( ATTR_BORDER, pCond );
+ pTLBR = &pPattern->GetItem( ATTR_BORDER_TLBR, pCond );
+ pBLTR = &pPattern->GetItem( ATTR_BORDER_BLTR, pCond );
+ }
+ else
+ {
+ pBox = nullptr;
+ pTLBR = pBLTR = nullptr;
+ }
+ }
+ }
+
+ // *** borders *** ------------------------------------------------
+
+ if( pBox )
+ {
+ rArray.SetCellStyleLeft( nFirstCol, nFirstRow, svx::frame::Style( pBox->GetLeft(), fColScale ) );
+ rArray.SetCellStyleRight( nFirstCol, nFirstRow, svx::frame::Style( pBox->GetRight(), fColScale ) );
+ rArray.SetCellStyleTop( nFirstCol, nFirstRow, svx::frame::Style( pBox->GetTop(), fRowScale ) );
+ rArray.SetCellStyleBottom( nFirstCol, nFirstRow, svx::frame::Style( pBox->GetBottom(), fRowScale ) );
+ }
+
+ if( pTLBR )
+ rArray.SetCellStyleTLBR( nFirstCol, nFirstRow, svx::frame::Style( pTLBR->GetLine(), fRowScale ) );
+ if( pBLTR )
+ rArray.SetCellStyleBLTR( nFirstCol, nFirstRow, svx::frame::Style( pBLTR->GetLine(), fRowScale ) );
+ }
+ }
+
+ /* Mirror the entire frame array. */
+ if( bLayoutRTL )
+ rArray.MirrorSelfX();
+}
+
+ScTableInfo::ScTableInfo(const SCSIZE capacity)
+ : mpRowInfo(new RowInfo[capacity])
+ , mnArrCount(0)
+ , mnArrCapacity(capacity)
+ , mbPageMode(false)
+{
+ memset(static_cast<void*>(mpRowInfo.get()), 0, mnArrCapacity * sizeof(RowInfo));
+}
+
+ScTableInfo::~ScTableInfo()
+{
+ for( SCSIZE nIdx = 0; nIdx < mnArrCapacity; ++nIdx )
+ mpRowInfo[ nIdx ].freeCellInfo();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/formulacell.cxx b/sc/source/core/data/formulacell.cxx
new file mode 100644
index 000000000..f2d840cb9
--- /dev/null
+++ b/sc/source/core/data/formulacell.cxx
@@ -0,0 +1,5571 @@
+/* -*- 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/.
+ *
+ * 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_feature_opencl.h>
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <cassert>
+#include <cstdlib>
+
+#include <formulacell.hxx>
+#include <grouptokenconverter.hxx>
+
+#include <compiler.hxx>
+#include <document.hxx>
+#include <cellvalue.hxx>
+#include <interpre.hxx>
+#include <macromgr.hxx>
+#include <refupdat.hxx>
+#include <recursionhelper.hxx>
+#include <docoptio.hxx>
+#include <rangenam.hxx>
+#include <rangelst.hxx>
+#include <dbdata.hxx>
+#include <progress.hxx>
+#include <scmatrix.hxx>
+#include <rechead.hxx>
+#include <scitems.hxx>
+#include <validat.hxx>
+#include <editutil.hxx>
+#include <chgtrack.hxx>
+#include <tokenarray.hxx>
+
+#include <comphelper/threadpool.hxx>
+#include <editeng/editobj.hxx>
+#include <formula/errorcodes.hxx>
+#include <svl/intitem.hxx>
+#include <svl/numformat.hxx>
+#include <formulagroup.hxx>
+#include <listenercontext.hxx>
+#include <types.hxx>
+#include <scopetools.hxx>
+#include <refupdatecontext.hxx>
+#include <tokenstringcontext.hxx>
+#include <refhint.hxx>
+#include <listenerquery.hxx>
+#include <listenerqueryids.hxx>
+#include <grouparealistener.hxx>
+#include <formulalogger.hxx>
+#include <com/sun/star/sheet/FormulaLanguage.hpp>
+
+#if HAVE_FEATURE_OPENCL
+#include <opencl/openclwrapper.hxx>
+#endif
+
+#include <memory>
+#include <map>
+
+using namespace formula;
+
+#define DEBUG_CALCULATION 0
+#if DEBUG_CALCULATION
+static bool bDebugCalculationActive = false; // Set to true for global active init,
+static ScAddress aDebugCalculationTriggerAddress(1,2,0); // or on cell Sheet1.B3, whatever you like
+
+struct DebugCalculationEntry
+{
+ ScAddress maPos;
+ OUString maResult;
+ const ScDocument& mrDoc;
+ sal_uInt32 mnGroup;
+ sal_uInt16 mnRecursion;
+
+ DebugCalculationEntry( const ScAddress& rPos, ScDocument& rDoc, sal_uInt32 nGroup ) :
+ maPos(rPos),
+ mrDoc(rDoc),
+ mnGroup(nGroup),
+ mnRecursion(rDoc.GetRecursionHelper().GetRecursionCount())
+ {
+ }
+};
+
+/** Debug/dump formula cell calculation chain.
+ Either, somewhere set aDC.mbActive=true, or
+ aDC.maTrigger=ScAddress(col,row,tab) of interest from where to start.
+ This does not work for deep recursion > MAXRECURSION, the results are
+ somewhat... funny... ;)
+ */
+static struct DebugCalculation
+{
+ std::vector< DebugCalculationEntry > mvPos;
+ std::vector< DebugCalculationEntry > mvResults;
+ ScAddress maTrigger;
+ sal_uInt32 mnGroup;
+ bool mbActive;
+ bool mbSwitchOff;
+ bool mbPrint;
+ bool mbPrintResults;
+
+ DebugCalculation() : mnGroup(0), mbActive(bDebugCalculationActive), mbSwitchOff(false),
+ mbPrint(true), mbPrintResults(false) {}
+
+ /** Print chain in encountered dependency order. */
+ void print() const
+ {
+ for (auto const& it : mvPos)
+ {
+ OUString aStr( it.maPos.Format( ScRefFlags::VALID | ScRefFlags::TAB_3D, &it.mrDoc) +
+ " [" + OUString::number( it.mnRecursion) + "," + OUString::number( it.mnGroup) + "]");
+ fprintf( stderr, "%s -> ", aStr.toUtf8().getStr());
+ }
+ fprintf( stderr, "%s", "END\n");
+ }
+
+ /** Print chain results. */
+ void printResults() const
+ {
+ for (auto const& it : mvResults)
+ {
+ OUString aStr( it.maPos.Format( ScRefFlags::VALID | ScRefFlags::TAB_3D, &it.mrDoc));
+ aStr += " (" + it.maResult + ")";
+ fprintf( stderr, "%s, ", aStr.toUtf8().getStr());
+ }
+ fprintf( stderr, "%s", "END\n");
+ }
+
+ void storeResult( const svl::SharedString& rStr )
+ {
+ if (mbActive && !mvPos.empty())
+ mvPos.back().maResult = "\"" + rStr.getString() + "\"";
+ }
+
+ void storeResult( const double& fVal )
+ {
+ if (mbActive && !mvPos.empty())
+ mvPos.back().maResult = rtl::math::doubleToUString( fVal, rtl_math_StringFormat_G, 2, '.', true);
+ }
+
+ void storeResultError( FormulaError nErr )
+ {
+ if (mbActive && !mvPos.empty())
+ mvPos.back().maResult = "Err:" + OUString::number( int( nErr ));
+ }
+
+ void enterGroup()
+ {
+ ++mnGroup;
+ }
+
+ void leaveGroup()
+ {
+ --mnGroup;
+ }
+
+} aDC;
+
+struct DebugCalculationStacker
+{
+ DebugCalculationStacker( const ScAddress& rPos, ScDocument& rDoc )
+ {
+ if (!aDC.mbActive && rPos == aDC.maTrigger)
+ aDC.mbActive = aDC.mbSwitchOff = true;
+ if (aDC.mbActive)
+ {
+ aDC.mvPos.push_back( DebugCalculationEntry( rPos, rDoc, aDC.mnGroup));
+ aDC.mbPrint = true;
+ }
+ }
+
+ ~DebugCalculationStacker()
+ {
+ if (aDC.mbActive)
+ {
+ if (!aDC.mvPos.empty())
+ {
+ if (aDC.mbPrint)
+ {
+ aDC.print();
+ aDC.mbPrint = false;
+ }
+ if (aDC.mbPrintResults)
+ {
+ // Store results until final result is available, reversing order.
+ aDC.mvResults.push_back( aDC.mvPos.back());
+ }
+ aDC.mvPos.pop_back();
+ if (aDC.mbPrintResults && aDC.mvPos.empty())
+ {
+ aDC.printResults();
+ std::vector< DebugCalculationEntry >().swap( aDC.mvResults);
+ }
+ if (aDC.mbSwitchOff && aDC.mvPos.empty())
+ aDC.mbActive = false;
+ }
+ }
+ }
+};
+#endif
+
+namespace {
+
+// More or less arbitrary, of course all recursions must fit into available
+// stack space (which is what on all systems we don't know yet?). Choosing a
+// lower value may be better than trying a much higher value that also isn't
+// sufficient but temporarily leads to high memory consumption. On the other
+// hand, if the value fits all recursions, execution is quicker as no resumes
+// are necessary. Could be made a configurable option.
+// Allow for a year's calendar (366).
+const sal_uInt16 MAXRECURSION = 400;
+
+typedef SCCOLROW(*DimensionSelector)(const ScDocument&, const ScAddress&, const ScSingleRefData&);
+
+SCCOLROW lcl_GetCol(const ScDocument& rDoc, const ScAddress& rPos, const ScSingleRefData& rData)
+{
+ return rData.toAbs(rDoc, rPos).Col();
+}
+
+SCCOLROW lcl_GetRow(const ScDocument& rDoc, const ScAddress& rPos, const ScSingleRefData& rData)
+{
+ return rData.toAbs(rDoc, rPos).Row();
+}
+
+SCCOLROW lcl_GetTab(const ScDocument& rDoc, const ScAddress& rPos, const ScSingleRefData& rData)
+{
+ return rData.toAbs(rDoc, rPos).Tab();
+}
+
+/** Check if both references span the same range in selected dimension.
+ */
+bool
+lcl_checkRangeDimension(
+ const ScDocument& rDoc,
+ const ScAddress& rPos, const SingleDoubleRefProvider& rRef1, const SingleDoubleRefProvider& rRef2,
+ const DimensionSelector aWhich)
+{
+ return aWhich(rDoc, rPos, rRef1.Ref1) == aWhich(rDoc, rPos, rRef2.Ref1) &&
+ aWhich(rDoc, rPos, rRef1.Ref2) == aWhich(rDoc, rPos, rRef2.Ref2);
+}
+
+bool
+lcl_checkRangeDimensions(
+ const ScDocument& rDoc,
+ const ScAddress& rPos, const SingleDoubleRefProvider& rRef1, const SingleDoubleRefProvider& rRef2,
+ bool& bCol, bool& bRow, bool& bTab)
+{
+ const bool bSameCols(lcl_checkRangeDimension(rDoc, rPos, rRef1, rRef2, lcl_GetCol));
+ const bool bSameRows(lcl_checkRangeDimension(rDoc, rPos, rRef1, rRef2, lcl_GetRow));
+ const bool bSameTabs(lcl_checkRangeDimension(rDoc, rPos, rRef1, rRef2, lcl_GetTab));
+
+ // Test if exactly two dimensions are equal
+ if (int(bSameCols) + int(bSameRows) + int(bSameTabs) == 2)
+ {
+ bCol = !bSameCols;
+ bRow = !bSameRows;
+ bTab = !bSameTabs;
+ return true;
+ }
+ return false;
+}
+
+/** Check if references in given reference list can possibly
+ form a range. To do that, two of their dimensions must be the same.
+ */
+bool
+lcl_checkRangeDimensions(
+ const ScDocument& rDoc, const ScAddress& rPos,
+ const std::vector<formula::FormulaToken*>::const_iterator& rBegin,
+ const std::vector<formula::FormulaToken*>::const_iterator& rEnd,
+ bool& bCol, bool& bRow, bool& bTab)
+{
+ std::vector<formula::FormulaToken*>::const_iterator aCur(rBegin);
+ ++aCur;
+ const SingleDoubleRefProvider aRef(**rBegin);
+ bool bOk(false);
+ {
+ const SingleDoubleRefProvider aRefCur(**aCur);
+ bOk = lcl_checkRangeDimensions(rDoc, rPos, aRef, aRefCur, bCol, bRow, bTab);
+ }
+ while (bOk && aCur != rEnd)
+ {
+ const SingleDoubleRefProvider aRefCur(**aCur);
+ bool bColTmp(false);
+ bool bRowTmp(false);
+ bool bTabTmp(false);
+ bOk = lcl_checkRangeDimensions(rDoc, rPos, aRef, aRefCur, bColTmp, bRowTmp, bTabTmp);
+ bOk = bOk && (bCol == bColTmp && bRow == bRowTmp && bTab == bTabTmp);
+ ++aCur;
+ }
+
+ return bOk && aCur == rEnd;
+}
+
+class LessByReference
+{
+ const ScDocument& mrDoc;
+ ScAddress maPos;
+ DimensionSelector maFunc;
+public:
+ LessByReference(const ScDocument& rDoc, const ScAddress& rPos, const DimensionSelector& rFunc) :
+ mrDoc(rDoc), maPos(rPos), maFunc(rFunc) {}
+
+ bool operator() (const formula::FormulaToken* pRef1, const formula::FormulaToken* pRef2)
+ {
+ const SingleDoubleRefProvider aRef1(*pRef1);
+ const SingleDoubleRefProvider aRef2(*pRef2);
+ return maFunc(mrDoc, maPos, aRef1.Ref1) < maFunc(mrDoc, maPos, aRef2.Ref1);
+ }
+};
+
+/**
+ * Returns true if range denoted by token p2 starts immediately after range
+ * denoted by token p1. Dimension, in which the comparison takes place, is
+ * given by maFunc.
+ */
+class AdjacentByReference
+{
+ const ScDocument& mrDoc;
+ ScAddress maPos;
+ DimensionSelector maFunc;
+public:
+ AdjacentByReference(const ScDocument& rDoc, const ScAddress& rPos, DimensionSelector aFunc) :
+ mrDoc(rDoc), maPos(rPos), maFunc(aFunc) {}
+
+ bool operator() (const formula::FormulaToken* p1, const formula::FormulaToken* p2)
+ {
+ const SingleDoubleRefProvider aRef1(*p1);
+ const SingleDoubleRefProvider aRef2(*p2);
+ return maFunc(mrDoc, maPos, aRef2.Ref1) - maFunc(mrDoc, maPos, aRef1.Ref2) == 1;
+ }
+};
+
+bool
+lcl_checkIfAdjacent(
+ const ScDocument& rDoc,
+ const ScAddress& rPos, const std::vector<formula::FormulaToken*>& rReferences, const DimensionSelector aWhich)
+{
+ auto aBegin(rReferences.cbegin());
+ auto aEnd(rReferences.cend());
+ auto aBegin1(aBegin);
+ ++aBegin1;
+ --aEnd;
+ return std::equal(aBegin, aEnd, aBegin1, AdjacentByReference(rDoc, rPos, aWhich));
+}
+
+void
+lcl_fillRangeFromRefList(
+ const ScDocument& rDoc,
+ const ScAddress& aPos, const std::vector<formula::FormulaToken*>& rReferences, ScRange& rRange)
+{
+ const ScSingleRefData aStart(
+ SingleDoubleRefProvider(*rReferences.front()).Ref1);
+ rRange.aStart = aStart.toAbs(rDoc, aPos);
+ const ScSingleRefData aEnd(
+ SingleDoubleRefProvider(*rReferences.back()).Ref2);
+ rRange.aEnd = aEnd.toAbs(rDoc, aPos);
+}
+
+bool
+lcl_refListFormsOneRange(
+ const ScDocument& rDoc,
+ const ScAddress& rPos, std::vector<formula::FormulaToken*>& rReferences,
+ ScRange& rRange)
+{
+ if (rReferences.size() == 1)
+ {
+ lcl_fillRangeFromRefList(rDoc, rPos, rReferences, rRange);
+ return true;
+ }
+
+ bool bCell(false);
+ bool bRow(false);
+ bool bTab(false);
+ if (lcl_checkRangeDimensions(rDoc, rPos, rReferences.begin(), rReferences.end(), bCell, bRow, bTab))
+ {
+ DimensionSelector aWhich;
+ if (bCell)
+ {
+ aWhich = lcl_GetCol;
+ }
+ else if (bRow)
+ {
+ aWhich = lcl_GetRow;
+ }
+ else if (bTab)
+ {
+ aWhich = lcl_GetTab;
+ }
+ else
+ {
+ OSL_FAIL( "lcl_checkRangeDimensions shouldn't allow that!");
+ aWhich = lcl_GetRow; // initialize to avoid warning
+ }
+
+ // Sort the references by start of range
+ std::sort(rReferences.begin(), rReferences.end(), LessByReference(rDoc, rPos, aWhich));
+ if (lcl_checkIfAdjacent(rDoc, rPos, rReferences, aWhich))
+ {
+ lcl_fillRangeFromRefList(rDoc, rPos, rReferences, rRange);
+ return true;
+ }
+ }
+ return false;
+}
+
+bool lcl_isReference(const FormulaToken& rToken)
+{
+ return
+ rToken.GetType() == svSingleRef ||
+ rToken.GetType() == svDoubleRef;
+}
+
+void adjustRangeName(formula::FormulaToken* pToken, ScDocument& rNewDoc, const ScDocument& rOldDoc,
+ const ScAddress& rNewPos, const ScAddress& rOldPos, bool bGlobalNamesToLocal)
+{
+ ScRangeData* pRangeData = nullptr;
+ SCTAB nSheet = pToken->GetSheet();
+ sal_uInt16 nIndex = pToken->GetIndex();
+ if (!rOldDoc.CopyAdjustRangeName( nSheet, nIndex, pRangeData, rNewDoc, rNewPos, rOldPos, bGlobalNamesToLocal, true))
+ return; // nothing to do
+
+ if (!pRangeData)
+ {
+ // If this happened we have a real problem.
+ pToken->SetIndex(0);
+ assert(!"inserting the range name should not fail");
+ return;
+ }
+
+ pToken->SetIndex(nIndex);
+ pToken->SetSheet(nSheet);
+}
+
+void adjustDBRange(formula::FormulaToken* pToken, ScDocument& rNewDoc, const ScDocument& rOldDoc)
+{
+ ScDBCollection* pOldDBCollection = rOldDoc.GetDBCollection();
+ if (!pOldDBCollection)
+ return;//strange error case, don't do anything
+ ScDBCollection::NamedDBs& aOldNamedDBs = pOldDBCollection->getNamedDBs();
+ ScDBData* pDBData = aOldNamedDBs.findByIndex(pToken->GetIndex());
+ if (!pDBData)
+ return; //invalid index
+ OUString aDBName = pDBData->GetUpperName();
+
+ //search in new document
+ ScDBCollection* pNewDBCollection = rNewDoc.GetDBCollection();
+ if (!pNewDBCollection)
+ {
+ rNewDoc.SetDBCollection(std::unique_ptr<ScDBCollection>(new ScDBCollection(rNewDoc)));
+ pNewDBCollection = rNewDoc.GetDBCollection();
+ }
+ ScDBCollection::NamedDBs& aNewNamedDBs = pNewDBCollection->getNamedDBs();
+ ScDBData* pNewDBData = aNewNamedDBs.findByUpperName(aDBName);
+ if (!pNewDBData)
+ {
+ pNewDBData = new ScDBData(*pDBData);
+ bool ins = aNewNamedDBs.insert(std::unique_ptr<ScDBData>(pNewDBData));
+ assert(ins); (void)ins;
+ }
+ pToken->SetIndex(pNewDBData->GetIndex());
+}
+
+}
+
+bool AreaListenerKey::operator < ( const AreaListenerKey& r ) const
+{
+ if (maRange.aStart.Tab() != r.maRange.aStart.Tab())
+ return maRange.aStart.Tab() < r.maRange.aStart.Tab();
+ if (maRange.aStart.Col() != r.maRange.aStart.Col())
+ return maRange.aStart.Col() < r.maRange.aStart.Col();
+ if (maRange.aStart.Row() != r.maRange.aStart.Row())
+ return maRange.aStart.Row() < r.maRange.aStart.Row();
+ if (maRange.aEnd.Tab() != r.maRange.aEnd.Tab())
+ return maRange.aEnd.Tab() < r.maRange.aEnd.Tab();
+ if (maRange.aEnd.Col() != r.maRange.aEnd.Col())
+ return maRange.aEnd.Col() < r.maRange.aEnd.Col();
+ if (maRange.aEnd.Row() != r.maRange.aEnd.Row())
+ return maRange.aEnd.Row() < r.maRange.aEnd.Row();
+ if (mbStartFixed != r.mbStartFixed)
+ return r.mbStartFixed;
+ if (mbEndFixed != r.mbEndFixed)
+ return r.mbEndFixed;
+
+ return false;
+}
+
+ScFormulaCellGroup::ScFormulaCellGroup() :
+ mnRefCount(0),
+ mpTopCell(nullptr),
+ mnLength(0),
+ mnWeight(0),
+ mnFormatType(SvNumFormatType::NUMBER),
+ mbInvariant(false),
+ mbSubTotal(false),
+ mbPartOfCycle(false),
+ meCalcState(sc::GroupCalcEnabled)
+{
+}
+
+ScFormulaCellGroup::~ScFormulaCellGroup()
+{
+}
+
+void ScFormulaCellGroup::setCode( const ScTokenArray& rCode )
+{
+ mpCode = rCode.CloneValue();
+ mbInvariant = mpCode->IsInvariant();
+ mpCode->GenHash();
+}
+
+void ScFormulaCellGroup::compileCode(
+ ScDocument& rDoc, const ScAddress& rPos, FormulaGrammar::Grammar eGram )
+{
+ if (!mpCode)
+ return;
+
+ if (mpCode->GetLen() && mpCode->GetCodeError() == FormulaError::NONE && !mpCode->GetCodeLen())
+ {
+ bool bMatrixFormula = mpTopCell->GetMatrixFlag() != ScMatrixMode::NONE;
+ ScCompiler aComp(rDoc, rPos, *mpCode, eGram, true, bMatrixFormula);
+ mbSubTotal = aComp.CompileTokenArray();
+ mnFormatType = aComp.GetNumFormatType();
+ }
+ else
+ {
+ mbSubTotal = mpCode->HasOpCodeRPN( ocSubTotal ) || mpCode->HasOpCodeRPN( ocAggregate );
+ }
+}
+
+sc::FormulaGroupAreaListener* ScFormulaCellGroup::getAreaListener(
+ ScFormulaCell** ppTopCell, const ScRange& rRange, bool bStartFixed, bool bEndFixed )
+{
+ AreaListenerKey aKey(rRange, bStartFixed, bEndFixed);
+
+ AreaListenersType::iterator it = m_AreaListeners.lower_bound(aKey);
+ if (it == m_AreaListeners.end() || m_AreaListeners.key_comp()(aKey, it->first))
+ {
+ // Insert a new one.
+ it = m_AreaListeners.insert(
+ it, std::make_pair(aKey, std::make_unique<sc::FormulaGroupAreaListener>(
+ rRange, (*ppTopCell)->GetDocument(), (*ppTopCell)->aPos, mnLength, bStartFixed, bEndFixed)));
+ }
+
+ return it->second.get();
+}
+
+void ScFormulaCellGroup::endAllGroupListening( ScDocument& rDoc )
+{
+ for (const auto& rEntry : m_AreaListeners)
+ {
+ sc::FormulaGroupAreaListener *const pListener = rEntry.second.get();
+ ScRange aListenRange = pListener->getListeningRange();
+ // This "always listen" special range is never grouped.
+ bool bGroupListening = (aListenRange != BCA_LISTEN_ALWAYS);
+ rDoc.EndListeningArea(aListenRange, bGroupListening, pListener);
+ }
+
+ m_AreaListeners.clear();
+}
+
+ScFormulaCell::ScFormulaCell( ScDocument& rDoc, const ScAddress& rPos ) :
+ bDirty(false),
+ bTableOpDirty(false),
+ bChanged(false),
+ bRunning(false),
+ bCompile(false),
+ bSubTotal(false),
+ bIsIterCell(false),
+ bInChangeTrack(false),
+ bNeedListening(false),
+ mbNeedsNumberFormat(false),
+ mbAllowNumberFormatChange(false),
+ mbPostponedDirty(false),
+ mbIsExtRef(false),
+ mbSeenInPath(false),
+ mbFreeFlying(false),
+ cMatrixFlag(ScMatrixMode::NONE),
+ nSeenInIteration(0),
+ nFormatType(SvNumFormatType::NUMBER),
+ eTempGrammar(formula::FormulaGrammar::GRAM_DEFAULT),
+ pCode(new ScTokenArray(rDoc)),
+ rDocument(rDoc),
+ pPrevious(nullptr),
+ pNext(nullptr),
+ pPreviousTrack(nullptr),
+ pNextTrack(nullptr),
+ aPos(rPos)
+{
+}
+
+ScFormulaCell::ScFormulaCell( ScDocument& rDoc, const ScAddress& rPos,
+ const OUString& rFormula,
+ const FormulaGrammar::Grammar eGrammar,
+ ScMatrixMode cMatInd ) :
+ bDirty( true ), // -> Because of the use of the Auto Pilot Function was: cMatInd != 0
+ bTableOpDirty( false ),
+ bChanged( false ),
+ bRunning( false ),
+ bCompile( false ),
+ bSubTotal( false ),
+ bIsIterCell( false ),
+ bInChangeTrack( false ),
+ bNeedListening( false ),
+ mbNeedsNumberFormat( false ),
+ mbAllowNumberFormatChange(false),
+ mbPostponedDirty(false),
+ mbIsExtRef(false),
+ mbSeenInPath(false),
+ mbFreeFlying(false),
+ cMatrixFlag ( cMatInd ),
+ nSeenInIteration(0),
+ nFormatType ( SvNumFormatType::NUMBER ),
+ eTempGrammar( eGrammar),
+ pCode( nullptr ),
+ rDocument( rDoc ),
+ pPrevious(nullptr),
+ pNext(nullptr),
+ pPreviousTrack(nullptr),
+ pNextTrack(nullptr),
+ aPos(rPos)
+{
+ Compile( rFormula, true, eGrammar ); // bNoListening, Insert does that
+ if (!pCode)
+ // We need to have a non-NULL token array instance at all times.
+ pCode = new ScTokenArray(rDoc);
+}
+
+ScFormulaCell::ScFormulaCell(
+ ScDocument& rDoc, const ScAddress& rPos, std::unique_ptr<ScTokenArray> pArray,
+ const FormulaGrammar::Grammar eGrammar, ScMatrixMode cMatInd ) :
+ bDirty( true ),
+ bTableOpDirty( false ),
+ bChanged( false ),
+ bRunning( false ),
+ bCompile( false ),
+ bSubTotal( false ),
+ bIsIterCell( false ),
+ bInChangeTrack( false ),
+ bNeedListening( false ),
+ mbNeedsNumberFormat( false ),
+ mbAllowNumberFormatChange(false),
+ mbPostponedDirty(false),
+ mbIsExtRef(false),
+ mbSeenInPath(false),
+ mbFreeFlying(false),
+ cMatrixFlag ( cMatInd ),
+ nSeenInIteration(0),
+ nFormatType ( SvNumFormatType::NUMBER ),
+ eTempGrammar( eGrammar),
+ pCode(pArray.release()),
+ rDocument( rDoc ),
+ pPrevious(nullptr),
+ pNext(nullptr),
+ pPreviousTrack(nullptr),
+ pNextTrack(nullptr),
+ aPos(rPos)
+{
+ assert(pCode); // Never pass a NULL pointer here.
+
+ pCode->Finalize(); // Reduce memory usage if needed.
+
+ // Generate RPN token array.
+ if (pCode->GetLen() && pCode->GetCodeError() == FormulaError::NONE && !pCode->GetCodeLen())
+ {
+ ScCompiler aComp(rDocument, aPos, *pCode, eTempGrammar, true, cMatrixFlag != ScMatrixMode::NONE);
+ bSubTotal = aComp.CompileTokenArray();
+ nFormatType = aComp.GetNumFormatType();
+ }
+ else
+ {
+ if ( pCode->HasOpCodeRPN( ocSubTotal ) || pCode->HasOpCodeRPN( ocAggregate ) )
+ bSubTotal = true;
+ }
+
+ if (bSubTotal)
+ rDocument.AddSubTotalCell(this);
+
+ pCode->GenHash();
+}
+
+ScFormulaCell::ScFormulaCell(
+ ScDocument& rDoc, const ScAddress& rPos, const ScTokenArray& rArray,
+ const FormulaGrammar::Grammar eGrammar, ScMatrixMode cMatInd ) :
+ bDirty( true ),
+ bTableOpDirty( false ),
+ bChanged( false ),
+ bRunning( false ),
+ bCompile( false ),
+ bSubTotal( false ),
+ bIsIterCell( false ),
+ bInChangeTrack( false ),
+ bNeedListening( false ),
+ mbNeedsNumberFormat( false ),
+ mbAllowNumberFormatChange(false),
+ mbPostponedDirty(false),
+ mbIsExtRef(false),
+ mbSeenInPath(false),
+ mbFreeFlying(false),
+ cMatrixFlag ( cMatInd ),
+ nSeenInIteration(0),
+ nFormatType ( SvNumFormatType::NUMBER ),
+ eTempGrammar( eGrammar),
+ pCode(new ScTokenArray(rArray)), // also implicitly does Finalize() on the array
+ rDocument( rDoc ),
+ pPrevious(nullptr),
+ pNext(nullptr),
+ pPreviousTrack(nullptr),
+ pNextTrack(nullptr),
+ aPos(rPos)
+{
+ // RPN array generation
+ if( pCode->GetLen() && pCode->GetCodeError() == FormulaError::NONE && !pCode->GetCodeLen() )
+ {
+ ScCompiler aComp( rDocument, aPos, *pCode, eTempGrammar, true, cMatrixFlag != ScMatrixMode::NONE );
+ bSubTotal = aComp.CompileTokenArray();
+ nFormatType = aComp.GetNumFormatType();
+ }
+ else
+ {
+ if ( pCode->HasOpCodeRPN( ocSubTotal ) || pCode->HasOpCodeRPN( ocAggregate ) )
+ bSubTotal = true;
+ }
+
+ if (bSubTotal)
+ rDocument.AddSubTotalCell(this);
+
+ pCode->GenHash();
+}
+
+ScFormulaCell::ScFormulaCell(
+ ScDocument& rDoc, const ScAddress& rPos, const ScFormulaCellGroupRef& xGroup,
+ const FormulaGrammar::Grammar eGrammar, ScMatrixMode cInd ) :
+ mxGroup(xGroup),
+ bDirty(true),
+ bTableOpDirty( false ),
+ bChanged( false ),
+ bRunning( false ),
+ bCompile( false ),
+ bSubTotal(xGroup->mbSubTotal),
+ bIsIterCell( false ),
+ bInChangeTrack( false ),
+ bNeedListening( false ),
+ mbNeedsNumberFormat( false ),
+ mbAllowNumberFormatChange(false),
+ mbPostponedDirty(false),
+ mbIsExtRef(false),
+ mbSeenInPath(false),
+ mbFreeFlying(false),
+ cMatrixFlag ( cInd ),
+ nSeenInIteration(0),
+ nFormatType(xGroup->mnFormatType),
+ eTempGrammar( eGrammar),
+ pCode(xGroup->mpCode ? &*xGroup->mpCode : new ScTokenArray(rDoc)),
+ rDocument( rDoc ),
+ pPrevious(nullptr),
+ pNext(nullptr),
+ pPreviousTrack(nullptr),
+ pNextTrack(nullptr),
+ aPos(rPos)
+{
+ if (bSubTotal)
+ rDocument.AddSubTotalCell(this);
+}
+
+ScFormulaCell::ScFormulaCell(const ScFormulaCell& rCell, ScDocument& rDoc, const ScAddress& rPos, ScCloneFlags nCloneFlags) :
+ bDirty( rCell.bDirty ),
+ bTableOpDirty( false ),
+ bChanged( rCell.bChanged ),
+ bRunning( false ),
+ bCompile( rCell.bCompile ),
+ bSubTotal( rCell.bSubTotal ),
+ bIsIterCell( false ),
+ bInChangeTrack( false ),
+ bNeedListening( false ),
+ mbNeedsNumberFormat( rCell.mbNeedsNumberFormat ),
+ mbAllowNumberFormatChange(false),
+ mbPostponedDirty(false),
+ mbIsExtRef(false),
+ mbSeenInPath(false),
+ mbFreeFlying(false),
+ cMatrixFlag ( rCell.cMatrixFlag ),
+ nSeenInIteration(0),
+ nFormatType( rCell.nFormatType ),
+ aResult( rCell.aResult ),
+ eTempGrammar( rCell.eTempGrammar),
+ rDocument( rDoc ),
+ pPrevious(nullptr),
+ pNext(nullptr),
+ pPreviousTrack(nullptr),
+ pNextTrack(nullptr),
+ aPos(rPos)
+{
+ pCode = rCell.pCode->Clone().release();
+
+ // set back any errors and recompile
+ // not in the Clipboard - it must keep the received error flag
+ // Special Length=0: as bad cells are generated, then they are also retained
+ if ( pCode->GetCodeError() != FormulaError::NONE && !rDocument.IsClipboard() && pCode->GetLen() )
+ {
+ pCode->SetCodeError( FormulaError::NONE );
+ bCompile = true;
+ }
+ // Compile ColRowNames on URM_MOVE/URM_COPY _after_ UpdateReference !
+ bool bCompileLater = false;
+ bool bClipMode = rCell.rDocument.IsClipboard();
+
+ //update ScNameTokens
+ if (!rDocument.IsClipOrUndo() || rDoc.IsUndo())
+ {
+ if (!rDocument.IsClipboardSource() || aPos.Tab() != rCell.aPos.Tab())
+ {
+ bool bGlobalNamesToLocal = ((nCloneFlags & ScCloneFlags::NamesToLocal) != ScCloneFlags::Default);
+ formula::FormulaToken* pToken = nullptr;
+ formula::FormulaTokenArrayPlainIterator aIter(*pCode);
+ while((pToken = aIter.GetNextName())!= nullptr)
+ {
+ OpCode eOpCode = pToken->GetOpCode();
+ if (eOpCode == ocName)
+ adjustRangeName(pToken, rDoc, rCell.rDocument, aPos, rCell.aPos, bGlobalNamesToLocal);
+ else if (eOpCode == ocDBArea || eOpCode == ocTableRef)
+ adjustDBRange(pToken, rDoc, rCell.rDocument);
+ }
+ }
+
+ bool bCopyBetweenDocs = rDocument.GetPool() != rCell.rDocument.GetPool();
+ if (bCopyBetweenDocs && !(nCloneFlags & ScCloneFlags::NoMakeAbsExternal))
+ {
+ pCode->ReadjustAbsolute3DReferences(rCell.rDocument, rDoc, rCell.aPos);
+ }
+
+ pCode->AdjustAbsoluteRefs( rCell.rDocument, rCell.aPos, aPos, bCopyBetweenDocs );
+ }
+
+ if (!rDocument.IsClipOrUndo())
+ {
+ if (&rDocument.GetSharedStringPool() != &rCell.rDocument.GetSharedStringPool())
+ pCode->ReinternStrings( rDocument.GetSharedStringPool());
+ pCode->AdjustReferenceOnCopy( aPos);
+ }
+
+ if( !bCompile )
+ { // Name references with references and ColRowNames
+ formula::FormulaTokenArrayPlainIterator aIter(*pCode);
+ for (;;)
+ {
+ formula::FormulaToken* t = aIter.GetNextReferenceOrName();
+ if (!t || bCompile)
+ break;
+ if ( t->IsExternalRef() )
+ {
+ // External name, cell, and area references.
+ bCompile = true;
+ }
+ else if ( t->GetType() == svIndex )
+ {
+ const ScRangeData* pRangeData = rDoc.FindRangeNameBySheetAndIndex( t->GetSheet(), t->GetIndex());
+ if( pRangeData )
+ {
+ if( pRangeData->HasReferences() )
+ bCompile = true;
+ }
+ else
+ bCompile = true; // invalid reference!
+ }
+ else if ( t->GetOpCode() == ocColRowName )
+ {
+ bCompile = true; // new lookup needed
+ bCompileLater = bClipMode;
+ }
+ }
+ }
+ if( bCompile )
+ {
+ if ( !bCompileLater && bClipMode )
+ {
+ // Merging ranges needs the actual positions after UpdateReference.
+ // ColRowNames and TableRefs need new lookup after positions are
+ // adjusted.
+ bCompileLater = pCode->HasOpCode( ocRange) || pCode->HasOpCode( ocColRowName) ||
+ pCode->HasOpCode( ocTableRef);
+ }
+ if ( !bCompileLater )
+ {
+ // bNoListening, not at all if in Clipboard/Undo,
+ // and not from Clipboard either, instead after Insert(Clone) and UpdateReference.
+ CompileTokenArray( true );
+ }
+ }
+
+ if( nCloneFlags & ScCloneFlags::StartListening )
+ StartListeningTo( rDoc );
+
+ if (bSubTotal)
+ rDocument.AddSubTotalCell(this);
+}
+
+ScFormulaCell::~ScFormulaCell()
+{
+ rDocument.RemoveFromFormulaTrack( this );
+ rDocument.RemoveFromFormulaTree( this );
+ rDocument.RemoveSubTotalCell(this);
+ if (pCode->HasOpCode(ocMacro))
+ rDocument.GetMacroManager()->RemoveDependentCell(this);
+
+ if (rDocument.HasExternalRefManager())
+ rDocument.GetExternalRefManager()->removeRefCell(this);
+
+ if (!mxGroup || !mxGroup->mpCode)
+ // Formula token is not shared.
+ delete pCode;
+
+ if (mxGroup && mxGroup->mpTopCell == this)
+ mxGroup->mpTopCell = nullptr;
+}
+
+ScFormulaCell* ScFormulaCell::Clone() const
+{
+ return new ScFormulaCell(*this, rDocument, aPos);
+}
+
+ScFormulaCell* ScFormulaCell::Clone( const ScAddress& rPos ) const
+{
+ return new ScFormulaCell(*this, rDocument, rPos, ScCloneFlags::Default);
+}
+
+size_t ScFormulaCell::GetHash() const
+{
+ return pCode->GetHash();
+}
+
+OUString ScFormulaCell::GetFormula( const FormulaGrammar::Grammar eGrammar, const ScInterpreterContext* pContext ) const
+{
+ if( pCode->GetCodeError() != FormulaError::NONE && !pCode->GetLen() )
+ {
+ return ScGlobal::GetErrorString(pCode->GetCodeError());
+ }
+ OUStringBuffer buffer;
+ if( cMatrixFlag == ScMatrixMode::Reference )
+ {
+ // Reference to another cell that contains a matrix formula.
+ formula::FormulaTokenArrayPlainIterator aIter(*pCode);
+ formula::FormulaToken* p = aIter.GetNextReferenceRPN();
+ if( p )
+ {
+ /* FIXME: original GetFormula() code obtained
+ * pCell only if (!IsInChangeTrack()),
+ * GetEnglishFormula() omitted that test.
+ * Can we live without in all cases? */
+ ScFormulaCell* pCell = nullptr;
+ ScSingleRefData& rRef = *p->GetSingleRef();
+ ScAddress aAbs = rRef.toAbs(rDocument, aPos);
+ if (rDocument.ValidAddress(aAbs))
+ pCell = rDocument.GetFormulaCell(aAbs);
+
+ if (pCell)
+ {
+ return pCell->GetFormula( eGrammar, pContext );
+ }
+ else
+ {
+ ScCompiler aComp( rDocument, aPos, *pCode, eGrammar, false, false, pContext );
+ aComp.CreateStringFromTokenArray( buffer );
+ }
+ }
+ else
+ {
+ OSL_FAIL("ScFormulaCell::GetFormula: not a matrix");
+ }
+ }
+ else
+ {
+ ScCompiler aComp( rDocument, aPos, *pCode, eGrammar, false, false, pContext );
+ aComp.CreateStringFromTokenArray( buffer );
+ }
+
+ buffer.insert( 0, '=');
+ if( cMatrixFlag != ScMatrixMode::NONE )
+ {
+ buffer.insert( 0, '{');
+ buffer.append( '}');
+ }
+ return buffer.makeStringAndClear();
+}
+
+OUString ScFormulaCell::GetFormula( sc::CompileFormulaContext& rCxt, const ScInterpreterContext* pContext ) const
+{
+ OUStringBuffer aBuf;
+ if (pCode->GetCodeError() != FormulaError::NONE && !pCode->GetLen())
+ {
+ ScTokenArray aCode(rCxt.getDoc());
+ aCode.AddToken( FormulaErrorToken( pCode->GetCodeError()));
+ ScCompiler aComp(rCxt, aPos, aCode, false, false, pContext);
+ aComp.CreateStringFromTokenArray(aBuf);
+ return aBuf.makeStringAndClear();
+ }
+ else if( cMatrixFlag == ScMatrixMode::Reference )
+ {
+ // Reference to another cell that contains a matrix formula.
+ formula::FormulaTokenArrayPlainIterator aIter(*pCode);
+ formula::FormulaToken* p = aIter.GetNextReferenceRPN();
+ if( p )
+ {
+ /* FIXME: original GetFormula() code obtained
+ * pCell only if (!IsInChangeTrack()),
+ * GetEnglishFormula() omitted that test.
+ * Can we live without in all cases? */
+ ScFormulaCell* pCell = nullptr;
+ ScSingleRefData& rRef = *p->GetSingleRef();
+ ScAddress aAbs = rRef.toAbs(rDocument, aPos);
+ if (rDocument.ValidAddress(aAbs))
+ pCell = rDocument.GetFormulaCell(aAbs);
+
+ if (pCell)
+ {
+ return pCell->GetFormula(rCxt);
+ }
+ else
+ {
+ ScCompiler aComp(rCxt, aPos, *pCode, false, false, pContext);
+ aComp.CreateStringFromTokenArray(aBuf);
+ }
+ }
+ else
+ {
+ OSL_FAIL("ScFormulaCell::GetFormula: not a matrix");
+ }
+ }
+ else
+ {
+ ScCompiler aComp(rCxt, aPos, *pCode, false, false, pContext);
+ aComp.CreateStringFromTokenArray(aBuf);
+ }
+
+ aBuf.insert( 0, '=');
+ if( cMatrixFlag != ScMatrixMode::NONE )
+ {
+ aBuf.insert( 0, '{');
+ aBuf.append( '}');
+ }
+
+ return aBuf.makeStringAndClear();
+}
+
+void ScFormulaCell::GetResultDimensions( SCSIZE& rCols, SCSIZE& rRows )
+{
+ MaybeInterpret();
+
+ if (pCode->GetCodeError() == FormulaError::NONE && aResult.GetType() == svMatrixCell)
+ {
+ const ScMatrix* pMat = aResult.GetToken()->GetMatrix();
+ if (pMat)
+ {
+ pMat->GetDimensions( rCols, rRows );
+ if (pCode->IsHyperLink())
+ {
+ // Row 2 element is the URL that is not to be displayed and the
+ // result dimension not to be extended.
+ assert(rRows == 2);
+ rRows = 1;
+ }
+ return;
+ }
+ }
+ rCols = 0;
+ rRows = 0;
+}
+
+void ScFormulaCell::ResetDirty() { bDirty = bTableOpDirty = mbPostponedDirty = false; }
+void ScFormulaCell::SetNeedsListening( bool bVar ) { bNeedListening = bVar; }
+
+void ScFormulaCell::SetNeedsDirty( bool bVar )
+{
+ mbPostponedDirty = bVar;
+}
+
+void ScFormulaCell::SetNeedNumberFormat( bool bVal )
+{
+ mbNeedsNumberFormat = mbAllowNumberFormatChange = bVal;
+}
+
+void ScFormulaCell::Compile( const OUString& rFormula, bool bNoListening,
+ const FormulaGrammar::Grammar eGrammar )
+{
+ if ( rDocument.IsClipOrUndo() )
+ return;
+ bool bWasInFormulaTree = rDocument.IsInFormulaTree( this );
+ if ( bWasInFormulaTree )
+ rDocument.RemoveFromFormulaTree( this );
+ // pCode may not deleted for queries, but must be empty
+ if ( pCode )
+ pCode->Clear();
+ ScTokenArray* pCodeOld = pCode;
+ ScCompiler aComp( rDocument, aPos, eGrammar);
+ pCode = aComp.CompileString( rFormula ).release();
+ assert(!mxGroup);
+ delete pCodeOld;
+ if( pCode->GetCodeError() == FormulaError::NONE )
+ {
+ if ( !pCode->GetLen() && !aResult.GetHybridFormula().isEmpty() && rFormula == aResult.GetHybridFormula() )
+ { // not recursive CompileTokenArray/Compile/CompileTokenArray
+ if ( rFormula[0] == '=' )
+ pCode->AddBad( rFormula.copy(1) );
+ else
+ pCode->AddBad( rFormula );
+ }
+ bCompile = true;
+ CompileTokenArray( bNoListening );
+ }
+ else
+ bChanged = true;
+
+ if ( bWasInFormulaTree )
+ rDocument.PutInFormulaTree( this );
+}
+
+void ScFormulaCell::Compile(
+ sc::CompileFormulaContext& rCxt, const OUString& rFormula, bool bNoListening )
+{
+ if ( rDocument.IsClipOrUndo() )
+ return;
+ bool bWasInFormulaTree = rDocument.IsInFormulaTree( this );
+ if ( bWasInFormulaTree )
+ rDocument.RemoveFromFormulaTree( this );
+ // pCode may not deleted for queries, but must be empty
+ if ( pCode )
+ pCode->Clear();
+ ScTokenArray* pCodeOld = pCode;
+ ScCompiler aComp(rCxt, aPos);
+ pCode = aComp.CompileString( rFormula ).release();
+ assert(!mxGroup);
+ delete pCodeOld;
+ if( pCode->GetCodeError() == FormulaError::NONE )
+ {
+ if ( !pCode->GetLen() && !aResult.GetHybridFormula().isEmpty() && rFormula == aResult.GetHybridFormula() )
+ { // not recursive CompileTokenArray/Compile/CompileTokenArray
+ if ( rFormula[0] == '=' )
+ pCode->AddBad( rFormula.copy(1) );
+ else
+ pCode->AddBad( rFormula );
+ }
+ bCompile = true;
+ CompileTokenArray(rCxt, bNoListening);
+ }
+ else
+ bChanged = true;
+
+ if ( bWasInFormulaTree )
+ rDocument.PutInFormulaTree( this );
+}
+
+void ScFormulaCell::CompileTokenArray( bool bNoListening )
+{
+ // Not already compiled?
+ if( !pCode->GetLen() && !aResult.GetHybridFormula().isEmpty() )
+ {
+ Compile( aResult.GetHybridFormula(), bNoListening, eTempGrammar);
+ }
+ else if( bCompile && !rDocument.IsClipOrUndo() && pCode->GetCodeError() == FormulaError::NONE )
+ {
+ // RPN length may get changed
+ bool bWasInFormulaTree = rDocument.IsInFormulaTree( this );
+ if ( bWasInFormulaTree )
+ rDocument.RemoveFromFormulaTree( this );
+
+ // Loading from within filter? No listening yet!
+ if( rDocument.IsInsertingFromOtherDoc() )
+ bNoListening = true;
+
+ if( !bNoListening && pCode->GetCodeLen() )
+ EndListeningTo( rDocument );
+ ScCompiler aComp(rDocument, aPos, *pCode, rDocument.GetGrammar(), true, cMatrixFlag != ScMatrixMode::NONE);
+ bSubTotal = aComp.CompileTokenArray();
+ if( pCode->GetCodeError() == FormulaError::NONE )
+ {
+ nFormatType = aComp.GetNumFormatType();
+ bChanged = true;
+ aResult.SetToken( nullptr);
+ bCompile = false;
+ if ( !bNoListening )
+ StartListeningTo( rDocument );
+ }
+ if ( bWasInFormulaTree )
+ rDocument.PutInFormulaTree( this );
+
+ if (bSubTotal)
+ rDocument.AddSubTotalCell(this);
+ }
+}
+
+void ScFormulaCell::CompileTokenArray( sc::CompileFormulaContext& rCxt, bool bNoListening )
+{
+ // Not already compiled?
+ if( !pCode->GetLen() && !aResult.GetHybridFormula().isEmpty() )
+ {
+ rCxt.setGrammar(eTempGrammar);
+ Compile(rCxt, aResult.GetHybridFormula(), bNoListening);
+ }
+ else if( bCompile && !rDocument.IsClipOrUndo() && pCode->GetCodeError() == FormulaError::NONE)
+ {
+ // RPN length may get changed
+ bool bWasInFormulaTree = rDocument.IsInFormulaTree( this );
+ if ( bWasInFormulaTree )
+ rDocument.RemoveFromFormulaTree( this );
+
+ // Loading from within filter? No listening yet!
+ if( rDocument.IsInsertingFromOtherDoc() )
+ bNoListening = true;
+
+ if( !bNoListening && pCode->GetCodeLen() )
+ EndListeningTo( rDocument );
+ ScCompiler aComp(rCxt, aPos, *pCode, true, cMatrixFlag != ScMatrixMode::NONE);
+ bSubTotal = aComp.CompileTokenArray();
+ if( pCode->GetCodeError() == FormulaError::NONE )
+ {
+ nFormatType = aComp.GetNumFormatType();
+ bChanged = true;
+ aResult.SetToken( nullptr);
+ bCompile = false;
+ if ( !bNoListening )
+ StartListeningTo( rDocument );
+ }
+ if ( bWasInFormulaTree )
+ rDocument.PutInFormulaTree( this );
+
+ if (bSubTotal)
+ rDocument.AddSubTotalCell(this);
+ }
+}
+
+void ScFormulaCell::CompileXML( sc::CompileFormulaContext& rCxt, ScProgress& rProgress )
+{
+ if ( cMatrixFlag == ScMatrixMode::Reference )
+ { // is already token code via ScDocFunc::EnterMatrix, ScDocument::InsertMatrixFormula
+ // just establish listeners
+ StartListeningTo( rDocument );
+ return ;
+ }
+
+ // Error constant formula cell stays as is.
+ if (!pCode->GetLen() && pCode->GetCodeError() != FormulaError::NONE)
+ return;
+
+ // Compilation changes RPN count, remove and reinsert to FormulaTree if it
+ // was in to update its count.
+ bool bWasInFormulaTree = rDocument.IsInFormulaTree( this);
+ if (bWasInFormulaTree)
+ rDocument.RemoveFromFormulaTree( this);
+ rCxt.setGrammar(eTempGrammar);
+ ScCompiler aComp(rCxt, aPos, *pCode, true, cMatrixFlag != ScMatrixMode::NONE);
+ OUString aFormula, aFormulaNmsp;
+ aComp.CreateStringFromXMLTokenArray( aFormula, aFormulaNmsp );
+ rDocument.DecXMLImportedFormulaCount( aFormula.getLength() );
+ rProgress.SetStateCountDownOnPercent( rDocument.GetXMLImportedFormulaCount() );
+ // pCode may not deleted for queries, but must be empty
+ pCode->Clear();
+
+ bool bDoCompile = true;
+
+ if ( !mxGroup && aFormulaNmsp.isEmpty() ) // optimization
+ {
+ ScAddress aPreviousCell( aPos );
+ aPreviousCell.IncRow( -1 );
+ ScFormulaCell *pPreviousCell = rDocument.GetFormulaCell( aPreviousCell );
+ if (pPreviousCell && pPreviousCell->GetCode()->IsShareable())
+ {
+ // Build formula string using the tokens from the previous cell,
+ // but use the current cell position.
+ ScCompiler aBackComp( rCxt, aPos, *(pPreviousCell->pCode) );
+ OUStringBuffer aShouldBeBuf;
+ aBackComp.CreateStringFromTokenArray( aShouldBeBuf );
+
+ // The initial '=' is optional in ODFF.
+ const sal_Int32 nLeadingEqual = (aFormula.getLength() > 0 && aFormula[0] == '=') ? 1 : 0;
+ OUString aShouldBe = aShouldBeBuf.makeStringAndClear();
+ if (aFormula.getLength() == aShouldBe.getLength() + nLeadingEqual &&
+ aFormula.match( aShouldBe, nLeadingEqual))
+ {
+ // Put them in the same formula group.
+ ScFormulaCellGroupRef xGroup = pPreviousCell->GetCellGroup();
+ if (!xGroup) // Last cell is not grouped yet. Start a new group.
+ xGroup = pPreviousCell->CreateCellGroup(1, false);
+ ++xGroup->mnLength;
+ SetCellGroup( xGroup );
+
+ // Do setup here based on previous cell.
+
+ nFormatType = pPreviousCell->nFormatType;
+ bSubTotal = pPreviousCell->bSubTotal;
+ bChanged = true;
+ bCompile = false;
+
+ if (bSubTotal)
+ rDocument.AddSubTotalCell(this);
+
+ bDoCompile = false;
+ pCode = pPreviousCell->pCode;
+ if (pPreviousCell->mbIsExtRef)
+ rDocument.GetExternalRefManager()->insertRefCellFromTemplate( pPreviousCell, this );
+ }
+ }
+ }
+
+ if (bDoCompile)
+ {
+ ScTokenArray* pCodeOld = pCode;
+ pCode = aComp.CompileString( aFormula, aFormulaNmsp ).release();
+ assert(!mxGroup);
+ delete pCodeOld;
+
+ if( pCode->GetCodeError() == FormulaError::NONE )
+ {
+ if ( !pCode->GetLen() )
+ {
+ if ( !aFormula.isEmpty() && aFormula[0] == '=' )
+ pCode->AddBad( aFormula.copy( 1 ) );
+ else
+ pCode->AddBad( aFormula );
+ }
+ bSubTotal = aComp.CompileTokenArray();
+ if( pCode->GetCodeError() == FormulaError::NONE )
+ {
+ nFormatType = aComp.GetNumFormatType();
+ bChanged = true;
+ bCompile = false;
+ }
+
+ if (bSubTotal)
+ rDocument.AddSubTotalCell(this);
+ }
+ else
+ bChanged = true;
+ }
+
+ // After loading, it must be known if ocDde/ocWebservice is in any formula
+ // (for external links warning, CompileXML is called at the end of loading XML file)
+ rDocument.CheckLinkFormulaNeedingCheck(*pCode);
+
+ //volatile cells must be added here for import
+ if( !pCode->IsRecalcModeNormal() || pCode->IsRecalcModeForced())
+ {
+ // During load, only those cells that are marked explicitly dirty get
+ // recalculated. So we need to set it dirty here.
+ SetDirtyVar();
+ rDocument.AppendToFormulaTrack(this);
+ // Do not call TrackFormulas() here, not all listeners may have been
+ // established, postponed until ScDocument::CompileXML() finishes.
+ }
+ else if (bWasInFormulaTree)
+ rDocument.PutInFormulaTree(this);
+}
+
+void ScFormulaCell::CalcAfterLoad( sc::CompileFormulaContext& rCxt, bool bStartListening )
+{
+ bool bNewCompiled = false;
+ // If a Calc 1.0-doc is read, we have a result, but no token array
+ if( !pCode->GetLen() && !aResult.GetHybridFormula().isEmpty() )
+ {
+ rCxt.setGrammar(eTempGrammar);
+ Compile(rCxt, aResult.GetHybridFormula(), true);
+ aResult.SetToken( nullptr);
+ bDirty = true;
+ bNewCompiled = true;
+ }
+ // The RPN array is not created when a Calc 3.0-Doc has been read as the Range Names exist until now.
+ if( pCode->GetLen() && !pCode->GetCodeLen() && pCode->GetCodeError() == FormulaError::NONE )
+ {
+ ScCompiler aComp(rCxt, aPos, *pCode, true, cMatrixFlag != ScMatrixMode::NONE);
+ bSubTotal = aComp.CompileTokenArray();
+ nFormatType = aComp.GetNumFormatType();
+ bDirty = true;
+ bCompile = false;
+ bNewCompiled = true;
+
+ if (bSubTotal)
+ rDocument.AddSubTotalCell(this);
+ }
+
+ // On OS/2 with broken FPU exception, we can somehow store /0 without Err503. Later on in
+ // the BLC Lib NumberFormatter crashes when doing a fabs (NAN) (# 32739 #).
+ // We iron this out here for all systems, such that we also have an Err503 here.
+ if ( aResult.IsValue() && !std::isfinite( aResult.GetDouble() ) )
+ {
+ OSL_FAIL("Formula cell INFINITY!!! Where does this document come from?");
+ aResult.SetResultError( FormulaError::IllegalFPOperation );
+ bDirty = true;
+ }
+
+ // DoubleRefs for binary operators were always a Matrix before version v5.0.
+ // Now this is only the case when in an array formula, otherwise it's an implicit intersection
+ if ( ScDocument::GetSrcVersion() < SC_MATRIX_DOUBLEREF &&
+ GetMatrixFlag() == ScMatrixMode::NONE && pCode->HasMatrixDoubleRefOps() )
+ {
+ cMatrixFlag = ScMatrixMode::Formula;
+ SetMatColsRows( 1, 1);
+ }
+
+ // Do the cells need to be calculated? After Load cells can contain an error code, and then start
+ // the listener and Recalculate (if needed) if not ScRecalcMode::NORMAL
+ if( !bNewCompiled || pCode->GetCodeError() == FormulaError::NONE )
+ {
+ if (bStartListening)
+ StartListeningTo(rDocument);
+
+ if( !pCode->IsRecalcModeNormal() )
+ bDirty = true;
+ }
+ if ( pCode->IsRecalcModeAlways() )
+ { // random(), today(), now() always stay in the FormulaTree, so that they are calculated
+ // for each F9
+ bDirty = true;
+ }
+ // No SetDirty yet, as no all Listeners are known yet (only in SetDirtyAfterLoad)
+}
+
+bool ScFormulaCell::MarkUsedExternalReferences()
+{
+ return pCode && rDocument.MarkUsedExternalReferences(*pCode, aPos);
+}
+
+namespace {
+class RecursionCounter
+{
+ ScRecursionHelper& rRec;
+ bool bStackedInIteration;
+#if defined DBG_UTIL && !defined NDEBUG
+ const ScFormulaCell* cell;
+#endif
+public:
+ RecursionCounter( ScRecursionHelper& r, ScFormulaCell* p )
+ : rRec(r)
+#if defined DBG_UTIL && !defined NDEBUG
+ , cell(p)
+#endif
+ {
+ bStackedInIteration = rRec.IsDoingIteration();
+ if (bStackedInIteration)
+ rRec.GetRecursionInIterationStack().push( p);
+ rRec.IncRecursionCount();
+ }
+ ~RecursionCounter()
+ {
+ rRec.DecRecursionCount();
+ if (bStackedInIteration)
+ {
+#if defined DBG_UTIL && !defined NDEBUG
+ assert(rRec.GetRecursionInIterationStack().top() == cell);
+#endif
+ rRec.GetRecursionInIterationStack().pop();
+ }
+ }
+};
+
+// Forced calculation: OpenCL and threads require formula groups, so force even single cells to be a "group".
+// Remove the group again at the end, since there are some places throughout the code
+// that do not handle well groups with just 1 cell. Remove the groups only when the recursion level
+// reaches 0 again (groups contain some info such as disabling threading because of cycles, so removing
+// a group immediately would remove the info), for this reason affected cells are stored in the recursion
+// helper.
+struct TemporaryCellGroupMaker
+{
+ TemporaryCellGroupMaker( ScFormulaCell* cell, bool enable )
+ : mCell( cell )
+ , mEnabled( enable )
+ {
+ if( mEnabled && mCell->GetCellGroup() == nullptr )
+ {
+ mCell->CreateCellGroup( 1, false );
+ mCell->GetDocument().GetRecursionHelper().AddTemporaryGroupCell( mCell );
+ }
+ }
+ ~TemporaryCellGroupMaker() COVERITY_NOEXCEPT_FALSE
+ {
+ if( mEnabled )
+ mCell->GetDocument().GetRecursionHelper().CleanTemporaryGroupCells();
+ }
+ ScFormulaCell* mCell;
+ const bool mEnabled;
+};
+
+} // namespace
+
+bool ScFormulaCell::Interpret(SCROW nStartOffset, SCROW nEndOffset)
+{
+ ScRecursionHelper& rRecursionHelper = rDocument.GetRecursionHelper();
+ bool bGroupInterpreted = false;
+
+ // The result would possibly depend on a cell without a valid value, bail out
+ // the entire dependency computation.
+ if (rRecursionHelper.IsAbortingDependencyComputation())
+ return false;
+
+ if ((mxGroup && !rRecursionHelper.CheckFGIndependence(mxGroup.get())) || !rRecursionHelper.AreGroupsIndependent())
+ return bGroupInterpreted;
+
+ static ForceCalculationType forceType = ScCalcConfig::getForceCalculationType();
+ TemporaryCellGroupMaker cellGroupMaker( this, forceType != ForceCalculationNone && forceType != ForceCalculationCore );
+
+ ScFormulaCell* pTopCell = mxGroup ? mxGroup->mpTopCell : this;
+
+ if (pTopCell->mbSeenInPath && rRecursionHelper.GetDepComputeLevel() &&
+ rRecursionHelper.AnyCycleMemberInDependencyEvalMode(pTopCell))
+ {
+ // This call arose from a dependency calculation and we just found a cycle.
+ // This will mark all elements in the cycle as parts-of-cycle.
+ ScFormulaGroupCycleCheckGuard aCycleCheckGuard(rRecursionHelper, pTopCell);
+ // Reaching here does not necessarily mean a circular reference, so don't set Err:522 here yet.
+ // If there is a genuine circular reference, it will be marked so when all groups
+ // in the cycle get out of dependency evaluation mode.
+ // But returning without calculation a new value means other cells depending
+ // on this one would use a possibly invalid value, so ensure the dependency
+ // computation is aborted without resetting the dirty flag of any cell.
+ rRecursionHelper.AbortDependencyComputation();
+ return bGroupInterpreted;
+ }
+
+#if DEBUG_CALCULATION
+ static bool bDebugCalculationInit = true;
+ if (bDebugCalculationInit)
+ {
+ aDC.maTrigger = aDebugCalculationTriggerAddress;
+ aDC.mbPrintResults = true;
+ bDebugCalculationInit = false;
+ }
+ DebugCalculationStacker aDebugEntry(aPos, rDocument);
+#endif
+
+ if (!IsDirtyOrInTableOpDirty() || rRecursionHelper.IsInReturn())
+ return bGroupInterpreted; // no double/triple processing
+
+ //FIXME:
+ // If the call originates from a Reschedule in DdeLink update, leave dirty
+ // Better: Do a Dde Link Update without Reschedule or do it completely asynchronously!
+ if ( rDocument.IsInDdeLinkUpdate() )
+ return bGroupInterpreted;
+
+ if (bRunning)
+ {
+ if (!rDocument.GetDocOptions().IsIter())
+ {
+ aResult.SetResultError( FormulaError::CircularReference );
+ return bGroupInterpreted;
+ }
+
+ if (aResult.GetResultError() == FormulaError::CircularReference)
+ aResult.SetResultError( FormulaError::NONE );
+
+ // Start or add to iteration list.
+ if (!rRecursionHelper.IsDoingIteration() ||
+ !rRecursionHelper.GetRecursionInIterationStack().top()->bIsIterCell)
+ rRecursionHelper.SetInIterationReturn( true);
+
+ return bGroupInterpreted;
+ }
+ // no multiple interprets for GetErrCode, IsValue, GetValue and
+ // different entry point recursions. Would also lead to premature
+ // convergence in iterations.
+ if (rRecursionHelper.GetIteration() && nSeenInIteration ==
+ rRecursionHelper.GetIteration())
+ return bGroupInterpreted;
+
+ bool bOldRunning = bRunning;
+ if (rRecursionHelper.GetRecursionCount() > MAXRECURSION)
+ {
+ bRunning = true;
+ rRecursionHelper.SetInRecursionReturn( true);
+ }
+ else
+ {
+ rDocument.IncInterpretLevel();
+
+#if DEBUG_CALCULATION
+ aDC.enterGroup();
+#endif
+ bool bPartOfCycleBefore = mxGroup && mxGroup->mbPartOfCycle;
+ bGroupInterpreted = InterpretFormulaGroup(nStartOffset, nEndOffset);
+ bool bPartOfCycleAfter = mxGroup && mxGroup->mbPartOfCycle;
+
+#if DEBUG_CALCULATION
+ aDC.leaveGroup();
+#endif
+ if (!bGroupInterpreted)
+ {
+ // This call resulted from a dependency calculation for a multigroup-threading attempt,
+ // but found dependency among the groups.
+ if (!rRecursionHelper.AreGroupsIndependent())
+ {
+ rDocument.DecInterpretLevel();
+ return bGroupInterpreted;
+ }
+ // Dependency calc inside InterpretFormulaGroup() failed due to
+ // detection of a cycle and there are parent FG's in the cycle.
+ // Skip InterpretTail() in such cases, only run InterpretTail for the "cycle-starting" FG
+ if (!bPartOfCycleBefore && bPartOfCycleAfter && rRecursionHelper.AnyParentFGInCycle())
+ {
+ rDocument.DecInterpretLevel();
+ return bGroupInterpreted;
+ }
+
+ ScFormulaGroupCycleCheckGuard aCycleCheckGuard(rRecursionHelper, this);
+ ScInterpreterContextGetterGuard aContextGetterGuard(rDocument, rDocument.GetFormatTable());
+ InterpretTail( *aContextGetterGuard.GetInterpreterContext(), SCITP_NORMAL);
+ }
+
+ rDocument.DecInterpretLevel();
+ }
+
+ // While leaving a recursion or iteration stack, insert its cells to the
+ // recursion list in reverse order.
+ if (rRecursionHelper.IsInReturn())
+ {
+ bool bFreeFlyingInserted = false;
+ if (rRecursionHelper.GetRecursionCount() > 0 || !rRecursionHelper.IsDoingRecursion())
+ {
+ rRecursionHelper.Insert( this, bOldRunning, aResult);
+ bFreeFlyingInserted = mbFreeFlying;
+ }
+ bool bIterationFromRecursion = false;
+ bool bResumeIteration = false;
+ do
+ {
+ if ((rRecursionHelper.IsInIterationReturn() &&
+ rRecursionHelper.GetRecursionCount() == 0 &&
+ !rRecursionHelper.IsDoingIteration()) ||
+ bIterationFromRecursion || bResumeIteration)
+ {
+ bool & rDone = rRecursionHelper.GetConvergingReference();
+ rDone = false;
+ if (!bIterationFromRecursion && bResumeIteration)
+ {
+ bResumeIteration = false;
+ // Resuming iteration expands the range.
+ ScFormulaRecursionList::const_iterator aOldStart(
+ rRecursionHelper.GetLastIterationStart());
+ rRecursionHelper.ResumeIteration();
+ // Mark new cells being in iteration.
+ for (ScFormulaRecursionList::const_iterator aIter(
+ rRecursionHelper.GetIterationStart()); aIter !=
+ aOldStart; ++aIter)
+ {
+ ScFormulaCell* pIterCell = (*aIter).pCell;
+ pIterCell->bIsIterCell = true;
+ }
+ // Mark older cells dirty again, in case they converted
+ // without accounting for all remaining cells in the circle
+ // that weren't touched so far, e.g. conditional. Restore
+ // backupped result.
+ sal_uInt16 nIteration = rRecursionHelper.GetIteration();
+ for (ScFormulaRecursionList::const_iterator aIter(
+ aOldStart); aIter !=
+ rRecursionHelper.GetIterationEnd(); ++aIter)
+ {
+ ScFormulaCell* pIterCell = (*aIter).pCell;
+ if (pIterCell->nSeenInIteration == nIteration)
+ {
+ if (!pIterCell->bDirty || aIter == aOldStart)
+ {
+ pIterCell->aResult = (*aIter).aPreviousResult;
+ }
+ --pIterCell->nSeenInIteration;
+ }
+ pIterCell->bDirty = true;
+ }
+ }
+ else
+ {
+ bResumeIteration = false;
+ // Close circle once. If 'this' is self-referencing only
+ // (e.g. counter or self-adder) then it is already
+ // implicitly closed.
+ /* TODO: does this even make sense anymore? The last cell
+ * added above with rRecursionHelper.Insert() should always
+ * be 'this', shouldn't it? */
+ if (rRecursionHelper.GetList().size() > 1)
+ {
+ ScFormulaCell* pLastCell = rRecursionHelper.GetList().back().pCell;
+ if (pLastCell != this)
+ {
+ rDocument.IncInterpretLevel();
+ ScInterpreterContextGetterGuard aContextGetterGuard(rDocument, rDocument.GetFormatTable());
+ pLastCell->InterpretTail(
+ *aContextGetterGuard.GetInterpreterContext(), SCITP_CLOSE_ITERATION_CIRCLE);
+ rDocument.DecInterpretLevel();
+ }
+ }
+ // Start at 1, init things.
+ rRecursionHelper.StartIteration();
+ // Mark all cells being in iteration. Reset results to
+ // original values, formula cells have been interpreted
+ // already, discard that step.
+ for (ScFormulaRecursionList::const_iterator aIter(
+ rRecursionHelper.GetIterationStart()); aIter !=
+ rRecursionHelper.GetIterationEnd(); ++aIter)
+ {
+ ScFormulaCell* pIterCell = (*aIter).pCell;
+ pIterCell->aResult = (*aIter).aPreviousResult;
+ pIterCell->bIsIterCell = true;
+ }
+ }
+ bIterationFromRecursion = false;
+ sal_uInt16 nIterMax = rDocument.GetDocOptions().GetIterCount();
+ for ( ; rRecursionHelper.GetIteration() <= nIterMax && !rDone;
+ rRecursionHelper.IncIteration())
+ {
+ rDone = false;
+ bool bFirst = true;
+ for ( ScFormulaRecursionList::iterator aIter(
+ rRecursionHelper.GetIterationStart()); aIter !=
+ rRecursionHelper.GetIterationEnd() &&
+ !rRecursionHelper.IsInReturn(); ++aIter)
+ {
+ ScFormulaCell* pIterCell = (*aIter).pCell;
+ if (pIterCell->IsDirtyOrInTableOpDirty() &&
+ rRecursionHelper.GetIteration() !=
+ pIterCell->GetSeenInIteration())
+ {
+ (*aIter).aPreviousResult = pIterCell->aResult;
+ rDocument.IncInterpretLevel();
+ ScInterpreterContextGetterGuard aContextGetterGuard(rDocument, rDocument.GetFormatTable());
+ pIterCell->InterpretTail( *aContextGetterGuard.GetInterpreterContext(), SCITP_FROM_ITERATION);
+ rDocument.DecInterpretLevel();
+ }
+ if (bFirst)
+ {
+ rDone = !pIterCell->IsDirtyOrInTableOpDirty();
+ bFirst = false;
+ }
+ else if (rDone)
+ {
+ rDone = !pIterCell->IsDirtyOrInTableOpDirty();
+ }
+ }
+ if (rRecursionHelper.IsInReturn())
+ {
+ bResumeIteration = true;
+ break; // for
+ // Don't increment iteration.
+ }
+ }
+ if (!bResumeIteration)
+ {
+ if (rDone)
+ {
+ for (ScFormulaRecursionList::const_iterator aIter(
+ rRecursionHelper.GetIterationStart());
+ aIter != rRecursionHelper.GetIterationEnd();
+ ++aIter)
+ {
+ ScFormulaCell* pIterCell = (*aIter).pCell;
+ pIterCell->bIsIterCell = false;
+ pIterCell->nSeenInIteration = 0;
+ pIterCell->bRunning = (*aIter).bOldRunning;
+ }
+ }
+ else
+ {
+ for (ScFormulaRecursionList::const_iterator aIter(
+ rRecursionHelper.GetIterationStart());
+ aIter != rRecursionHelper.GetIterationEnd();
+ ++aIter)
+ {
+ ScFormulaCell* pIterCell = (*aIter).pCell;
+ pIterCell->bIsIterCell = false;
+ pIterCell->nSeenInIteration = 0;
+ pIterCell->bRunning = (*aIter).bOldRunning;
+ pIterCell->ResetDirty();
+ // The difference to Excel is that Excel does not
+ // produce an error for non-convergence thus a
+ // delta of 0.001 still works to execute the
+ // maximum number of iterations and display the
+ // results no matter if the result anywhere reached
+ // near delta, but also never indicates whether the
+ // result actually makes sense in case of
+ // non-counter context. Calc does check the delta
+ // in every case. If we wanted to support what
+ // Excel does then add another option "indicate
+ // non-convergence error" (default on) and execute
+ // the following block only if set.
+#if 1
+ // If one cell didn't converge, all cells of this
+ // circular dependency don't, no matter whether
+ // single cells did.
+ pIterCell->aResult.SetResultError( FormulaError::NoConvergence);
+ pIterCell->bChanged = true;
+#endif
+ }
+ }
+ // End this iteration and remove entries.
+ rRecursionHelper.EndIteration();
+ bResumeIteration = rRecursionHelper.IsDoingIteration();
+ }
+ }
+ if (rRecursionHelper.IsInRecursionReturn() &&
+ rRecursionHelper.GetRecursionCount() == 0 &&
+ !rRecursionHelper.IsDoingRecursion())
+ {
+ bIterationFromRecursion = false;
+ // Iterate over cells known so far, start with the last cell
+ // encountered, inserting new cells if another recursion limit
+ // is reached. Repeat until solved.
+ rRecursionHelper.SetDoingRecursion( true);
+ do
+ {
+ rRecursionHelper.SetInRecursionReturn( false);
+ for (ScFormulaRecursionList::const_iterator aIter(
+ rRecursionHelper.GetIterationStart());
+ !rRecursionHelper.IsInReturn() && aIter !=
+ rRecursionHelper.GetIterationEnd(); ++aIter)
+ {
+ ScFormulaCell* pCell = (*aIter).pCell;
+ if (pCell->IsDirtyOrInTableOpDirty())
+ {
+ rDocument.IncInterpretLevel();
+ ScInterpreterContextGetterGuard aContextGetterGuard(rDocument, rDocument.GetFormatTable());
+ pCell->InterpretTail( *aContextGetterGuard.GetInterpreterContext(), SCITP_NORMAL);
+ rDocument.DecInterpretLevel();
+ if (!pCell->IsDirtyOrInTableOpDirty() && !pCell->IsIterCell())
+ pCell->bRunning = (*aIter).bOldRunning;
+ }
+ }
+ } while (rRecursionHelper.IsInRecursionReturn());
+ rRecursionHelper.SetDoingRecursion( false);
+ if (rRecursionHelper.IsInIterationReturn())
+ {
+ if (!bResumeIteration)
+ bIterationFromRecursion = true;
+ }
+ else if (bResumeIteration ||
+ rRecursionHelper.IsDoingIteration())
+ rRecursionHelper.GetList().erase(
+ rRecursionHelper.GetIterationStart(),
+ rRecursionHelper.GetLastIterationStart());
+ else
+ rRecursionHelper.Clear();
+ }
+ } while (bIterationFromRecursion || bResumeIteration);
+
+ if (bFreeFlyingInserted)
+ {
+ // Remove this from recursion list, it may get deleted.
+ // It additionally also should mean that the recursion/iteration
+ // ends here as it must had been triggered by this free-flying
+ // out-of-sheets cell
+ /* TODO: replace by a simple rRecursionHelper.EndIteration() call
+ * if the assertions hold. */
+ const bool bOnlyThis = (rRecursionHelper.GetList().size() == 1);
+ assert(bOnlyThis);
+ rRecursionHelper.GetList().remove_if([this](const ScFormulaRecursionEntry& r){return r.pCell == this;});
+ if (bOnlyThis)
+ {
+ assert(rRecursionHelper.GetList().empty());
+ if (rRecursionHelper.GetList().empty())
+ rRecursionHelper.EndIteration();
+ }
+ }
+ }
+
+#if DEBUG_CALCULATION
+ FormulaError nErr = aResult.GetResultError();
+ if (nErr != FormulaError::NONE)
+ aDC.storeResultError( nErr);
+ else if (aResult.IsValue())
+ aDC.storeResult( aResult.GetDouble());
+ else
+ aDC.storeResult( aResult.GetString());
+#endif
+
+ return bGroupInterpreted;
+}
+
+void ScFormulaCell::InterpretTail( ScInterpreterContext& rContext, ScInterpretTailParameter eTailParam )
+{
+ RecursionCounter aRecursionCounter( rDocument.GetRecursionHelper(), this);
+ // TODO If this cell is not an iteration cell, add it to the list of iteration cells?
+ if(bIsIterCell)
+ nSeenInIteration = rDocument.GetRecursionHelper().GetIteration();
+ if( !pCode->GetCodeLen() && pCode->GetCodeError() == FormulaError::NONE )
+ {
+ // #i11719# no RPN and no error and no token code but result string present
+ // => interpretation of this cell during name-compilation and unknown names
+ // => can't exchange underlying code array in CompileTokenArray() /
+ // Compile() because interpreter's token iterator would crash or pCode
+ // would be deleted twice if this cell was interpreted during
+ // compilation.
+ // This should only be a temporary condition and, since we set an
+ // error, if ran into it again we'd bump into the dirty-clearing
+ // condition further down.
+ if ( !pCode->GetLen() && !aResult.GetHybridFormula().isEmpty() )
+ {
+ pCode->SetCodeError( FormulaError::NoCode );
+ // This is worth an assertion; if encountered in daily work
+ // documents we might need another solution. Or just confirm correctness.
+ return;
+ }
+ CompileTokenArray();
+ }
+
+ if( pCode->GetCodeLen() )
+ {
+ std::unique_ptr<ScInterpreter> pScopedInterpreter;
+ ScInterpreter* pInterpreter;
+ if (rContext.pInterpreter)
+ {
+ pInterpreter = rContext.pInterpreter;
+ pInterpreter->Init(this, aPos, *pCode);
+ }
+ else
+ {
+ pScopedInterpreter.reset(new ScInterpreter( this, rDocument, rContext, aPos, *pCode ));
+ pInterpreter = pScopedInterpreter.get();
+ }
+
+ FormulaError nOldErrCode = aResult.GetResultError();
+ if ( nSeenInIteration == 0 )
+ { // Only the first time
+ // With bChanged=false, if a newly compiled cell has a result of
+ // 0.0, no change is detected and the cell will not be repainted.
+ // bChanged = false;
+ aResult.SetResultError( FormulaError::NONE );
+ }
+
+ switch ( aResult.GetResultError() )
+ {
+ case FormulaError::CircularReference : // will be determined again if so
+ aResult.SetResultError( FormulaError::NONE );
+ break;
+ default: break;
+ }
+
+ bool bOldRunning = bRunning;
+ bRunning = true;
+ pInterpreter->Interpret();
+ if (rDocument.GetRecursionHelper().IsInReturn() && eTailParam != SCITP_CLOSE_ITERATION_CIRCLE)
+ {
+ if (nSeenInIteration > 0)
+ --nSeenInIteration; // retry when iteration is resumed
+
+ if ( aResult.GetType() == formula::svUnknown )
+ aResult.SetToken( pInterpreter->GetResultToken().get() );
+
+ return;
+ }
+ bRunning = bOldRunning;
+
+ // The result may be invalid or depend on another invalid result, just abort
+ // without updating the cell value. Since the dirty flag will not be reset,
+ // the proper value will be computed later.
+ if(rDocument.GetRecursionHelper().IsAbortingDependencyComputation())
+ return;
+
+ // #i102616# For single-sheet saving consider only content changes, not format type,
+ // because format type isn't set on loading (might be changed later)
+ bool bContentChanged = false;
+
+ // Do not create a HyperLink() cell if the formula results in an error.
+ if( pInterpreter->GetError() != FormulaError::NONE && pCode->IsHyperLink())
+ pCode->SetHyperLink(false);
+
+ if( pInterpreter->GetError() != FormulaError::NONE && pInterpreter->GetError() != FormulaError::CircularReference)
+ {
+ bChanged = true;
+
+ if (pInterpreter->GetError() == FormulaError::RetryCircular)
+ {
+ // Array formula matrix calculation corner case. Keep dirty
+ // state, do not remove from formula tree or anything else, but
+ // store FormulaError::CircularReference in case this cell does not get
+ // recalculated.
+ aResult.SetResultError( FormulaError::CircularReference);
+ return;
+ }
+
+ ResetDirty();
+ }
+
+ if (eTailParam == SCITP_FROM_ITERATION && IsDirtyOrInTableOpDirty())
+ {
+ bool bIsValue = aResult.IsValue(); // the previous type
+ // Did it converge?
+ if ((bIsValue && pInterpreter->GetResultType() == svDouble && fabs(
+ pInterpreter->GetNumResult() - aResult.GetDouble()) <=
+ rDocument.GetDocOptions().GetIterEps()) ||
+ (!bIsValue && pInterpreter->GetResultType() == svString &&
+ pInterpreter->GetStringResult() == aResult.GetString()))
+ {
+ // A convergence in the first iteration doesn't necessarily
+ // mean that it's done, it may be as not all related cells
+ // of a circle changed their values yet. If the set really
+ // converges it will do so also during the next iteration. This
+ // fixes situations like of #i44115#. If this wasn't wanted an
+ // initial "uncalculated" value would be needed for all cells
+ // of a circular dependency => graph needed before calculation.
+ if (nSeenInIteration > 1 ||
+ rDocument.GetDocOptions().GetIterCount() == 1)
+ {
+ ResetDirty();
+ }
+ }
+ }
+
+ // New error code?
+ if( pInterpreter->GetError() != nOldErrCode )
+ {
+ bChanged = true;
+ // bContentChanged only has to be set if the file content would be changed
+ if ( aResult.GetCellResultType() != svUnknown )
+ bContentChanged = true;
+ }
+
+ ScFormulaResult aNewResult( pInterpreter->GetResultToken().get());
+
+ // For IF() and other jumps or changed formatted source data the result
+ // format may change for different runs, e.g. =IF(B1,B1) with first
+ // B1:0 boolean FALSE next B1:23 numeric 23, we don't want the 23
+ // displayed as TRUE. Do not force a general format though if
+ // mbNeedsNumberFormat is set (because there was a general format..).
+ // Note that nFormatType may be out of sync here if a format was
+ // applied or cleared after the last run, but obtaining the current
+ // format always just to check would be expensive. There may be
+ // cases where the format should be changed but is not. If that turns
+ // out to be a real problem then obtain the current format type after
+ // the initial check when needed.
+ bool bForceNumberFormat = (mbAllowNumberFormatChange && !mbNeedsNumberFormat &&
+ !SvNumberFormatter::IsCompatible( nFormatType, pInterpreter->GetRetFormatType()));
+
+ // We have some requirements additionally to IsCompatible().
+ // * Do not apply a NumberFormat::LOGICAL if the result value is not
+ // 1.0 or 0.0
+ // * Do not override an already set numeric number format if the result
+ // is of type NumberFormat::LOGICAL, it could be user applied.
+ // On the other hand, for an empty jump path instead of FALSE an
+ // unexpected for example 0% could be displayed. YMMV.
+ // * Never override a non-standard number format that indicates user
+ // applied.
+ // * NumberFormat::TEXT does not force a change.
+ if (bForceNumberFormat)
+ {
+ sal_uInt32 nOldFormatIndex = NUMBERFORMAT_ENTRY_NOT_FOUND;
+ const SvNumFormatType nRetType = pInterpreter->GetRetFormatType();
+ if (nRetType == SvNumFormatType::LOGICAL)
+ {
+ double fVal = aNewResult.GetDouble();
+ if (fVal != 1.0 && fVal != 0.0)
+ bForceNumberFormat = false;
+ else
+ {
+ nOldFormatIndex = rDocument.GetNumberFormat( rContext, aPos);
+ nFormatType = rContext.GetFormatTable()->GetType( nOldFormatIndex);
+ switch (nFormatType)
+ {
+ case SvNumFormatType::PERCENT:
+ case SvNumFormatType::CURRENCY:
+ case SvNumFormatType::SCIENTIFIC:
+ case SvNumFormatType::FRACTION:
+ bForceNumberFormat = false;
+ break;
+ case SvNumFormatType::NUMBER:
+ if ((nOldFormatIndex % SV_COUNTRY_LANGUAGE_OFFSET) != 0)
+ bForceNumberFormat = false;
+ break;
+ default: break;
+ }
+ }
+ }
+ else if (nRetType == SvNumFormatType::TEXT)
+ {
+ bForceNumberFormat = false;
+ }
+ if (bForceNumberFormat)
+ {
+ if (nOldFormatIndex == NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ nOldFormatIndex = rDocument.GetNumberFormat( rContext, aPos);
+ nFormatType = rContext.GetFormatTable()->GetType( nOldFormatIndex);
+ }
+ if (nOldFormatIndex !=
+ ScGlobal::GetStandardFormat( *rContext.GetFormatTable(), nOldFormatIndex, nFormatType))
+ bForceNumberFormat = false;
+ }
+ }
+
+ if( mbNeedsNumberFormat || bForceNumberFormat )
+ {
+ bool bSetFormat = true;
+ const SvNumFormatType nOldFormatType = nFormatType;
+ nFormatType = pInterpreter->GetRetFormatType();
+ sal_uInt32 nFormatIndex = pInterpreter->GetRetFormatIndex();
+
+ if (nFormatType == SvNumFormatType::TEXT)
+ {
+ // Don't set text format as hard format.
+ bSetFormat = false;
+ }
+ else if (nFormatType == SvNumFormatType::LOGICAL && cMatrixFlag != ScMatrixMode::NONE)
+ {
+ // In a matrix range do not set an (inherited) logical format
+ // as hard format if the value does not represent a strict TRUE
+ // or FALSE value. But do set for a top left error value so
+ // following matrix cells can inherit for non-error values.
+ // This solves a problem with IF() expressions in array context
+ // where incidentally the top left element results in logical
+ // type but some others don't. It still doesn't solve the
+ // reverse case though, where top left is not logical type but
+ // some other elements should be. We'd need to transport type
+ // or format information on arrays.
+ StackVar eNewCellResultType = aNewResult.GetCellResultType();
+ if (eNewCellResultType != svError || cMatrixFlag == ScMatrixMode::Reference)
+ {
+ if (eNewCellResultType != svDouble)
+ {
+ bSetFormat = false;
+ nFormatType = nOldFormatType; // that? or number?
+ }
+ else
+ {
+ double fVal = aNewResult.GetDouble();
+ if (fVal != 1.0 && fVal != 0.0)
+ {
+ bSetFormat = false;
+ nFormatType = SvNumFormatType::NUMBER;
+ }
+ }
+ }
+ }
+
+ if (bSetFormat && (bForceNumberFormat || ((nFormatIndex % SV_COUNTRY_LANGUAGE_OFFSET) == 0)))
+ nFormatIndex = ScGlobal::GetStandardFormat(*rContext.GetFormatTable(),
+ nFormatIndex, nFormatType);
+
+ // Do not replace a General format (which was the reason why
+ // mbNeedsNumberFormat was set) with a General format.
+ // 1. setting a format has quite some overhead in the
+ // ScPatternAttr/ScAttrArray handling, even if identical.
+ // 2. the General formats may be of different locales.
+ // XXX if mbNeedsNumberFormat was set even if the current format
+ // was not General then we'd have to obtain the current format here
+ // and check at least the types.
+ const bool bSetNumberFormat = bSetFormat && (bForceNumberFormat || ((nFormatIndex % SV_COUNTRY_LANGUAGE_OFFSET) != 0));
+ if (bSetNumberFormat && !rDocument.IsInLayoutStrings())
+ {
+ // set number format explicitly
+ if (!rDocument.IsThreadedGroupCalcInProgress())
+ rDocument.SetNumberFormat( aPos, nFormatIndex );
+ else
+ {
+ // SetNumberFormat() is not thread-safe (modifies ScAttrArray), delay the work
+ // to the main thread. Since thread calculations operate on formula groups,
+ // it's enough to store just the row.
+ DelayedSetNumberFormat data = { aPos.Col(), aPos.Row(), nFormatIndex };
+ rContext.maDelayedSetNumberFormat.push_back( data );
+ }
+ bChanged = true;
+ }
+
+ // Currently (2019-05-10) nothing else can cope with a duration
+ // format type, change to time as it was before.
+ if (nFormatType == SvNumFormatType::DURATION)
+ nFormatType = SvNumFormatType::TIME;
+
+ mbNeedsNumberFormat = false;
+ }
+
+ // In case of changes just obtain the result, no temporary and
+ // comparison needed anymore.
+ if (bChanged)
+ {
+ // #i102616# Compare anyway if the sheet is still marked unchanged for single-sheet saving
+ // Also handle special cases of initial results after loading.
+ if ( !bContentChanged && rDocument.IsStreamValid(aPos.Tab()) )
+ {
+ StackVar eOld = aResult.GetCellResultType();
+ StackVar eNew = aNewResult.GetCellResultType();
+ if ( eOld == svUnknown && ( eNew == svError || ( eNew == svDouble && aNewResult.GetDouble() == 0.0 ) ) )
+ {
+ // ScXMLTableRowCellContext::EndElement doesn't call SetFormulaResultDouble for 0
+ // -> no change
+ }
+ else
+ {
+ if ( eOld == svHybridCell ) // string result from SetFormulaResultString?
+ eOld = svString; // ScHybridCellToken has a valid GetString method
+
+ // #i106045# use approxEqual to compare with stored value
+ bContentChanged = (eOld != eNew ||
+ (eNew == svDouble && !rtl::math::approxEqual( aResult.GetDouble(), aNewResult.GetDouble() )) ||
+ (eNew == svString && aResult.GetString() != aNewResult.GetString()));
+ }
+ }
+
+ aResult.SetToken( pInterpreter->GetResultToken().get() );
+ }
+ else
+ {
+ StackVar eOld = aResult.GetCellResultType();
+ StackVar eNew = aNewResult.GetCellResultType();
+ bChanged = (eOld != eNew ||
+ (eNew == svDouble && aResult.GetDouble() != aNewResult.GetDouble()) ||
+ (eNew == svString && aResult.GetString() != aNewResult.GetString()));
+
+ // #i102616# handle special cases of initial results after loading
+ // (only if the sheet is still marked unchanged)
+ if ( bChanged && !bContentChanged && rDocument.IsStreamValid(aPos.Tab()) )
+ {
+ if ((eOld == svUnknown && (eNew == svError || (eNew == svDouble && aNewResult.GetDouble() == 0.0))) ||
+ ((eOld == svHybridCell) &&
+ eNew == svString && aResult.GetString() == aNewResult.GetString()) ||
+ (eOld == svDouble && eNew == svDouble &&
+ rtl::math::approxEqual( aResult.GetDouble(), aNewResult.GetDouble())))
+ {
+ // no change, see above
+ }
+ else
+ bContentChanged = true;
+ }
+
+ aResult.Assign( aNewResult);
+ }
+
+ // Precision as shown?
+ if ( aResult.IsValue() && pInterpreter->GetError() == FormulaError::NONE
+ && rDocument.GetDocOptions().IsCalcAsShown()
+ && nFormatType != SvNumFormatType::DATE
+ && nFormatType != SvNumFormatType::TIME
+ && nFormatType != SvNumFormatType::DATETIME )
+ {
+ sal_uInt32 nFormat = rDocument.GetNumberFormat( rContext, aPos );
+ aResult.SetDouble( rDocument.RoundValueAsShown(
+ aResult.GetDouble(), nFormat, &rContext));
+ }
+ if (eTailParam == SCITP_NORMAL)
+ {
+ ResetDirty();
+ }
+ if( aResult.GetMatrix() )
+ {
+ // If the formula wasn't entered as a matrix formula, live on with
+ // the upper left corner and let reference counting delete the matrix.
+ if( cMatrixFlag != ScMatrixMode::Formula && !pCode->IsHyperLink() )
+ aResult.SetToken( aResult.GetCellResultToken().get());
+ }
+ if ( aResult.IsValue() && !std::isfinite( aResult.GetDouble() ) )
+ {
+ // Coded double error may occur via filter import.
+ FormulaError nErr = GetDoubleErrorValue( aResult.GetDouble());
+ aResult.SetResultError( nErr);
+ bChanged = bContentChanged = true;
+ }
+
+ if (bContentChanged && rDocument.IsStreamValid(aPos.Tab()))
+ {
+ // pass bIgnoreLock=true, because even if called from pending row height update,
+ // a changed result must still reset the stream flag
+ rDocument.SetStreamValid(aPos.Tab(), false, true);
+ }
+ if ( !rDocument.IsThreadedGroupCalcInProgress() && !pCode->IsRecalcModeAlways() )
+ rDocument.RemoveFromFormulaTree( this );
+
+ // FORCED cells also immediately tested for validity (start macro possibly)
+
+ if ( pCode->IsRecalcModeForced() )
+ {
+ sal_uLong nValidation = rDocument.GetAttr(
+ aPos.Col(), aPos.Row(), aPos.Tab(), ATTR_VALIDDATA )->GetValue();
+ if ( nValidation )
+ {
+ const ScValidationData* pData = rDocument.GetValidationEntry( nValidation );
+ ScRefCellValue aTmpCell(this);
+ if ( pData && !pData->IsDataValid(aTmpCell, aPos))
+ pData->DoCalcError( this );
+ }
+ }
+
+ // Reschedule slows the whole thing down considerably, thus only execute on percent change
+ if (!rDocument.IsThreadedGroupCalcInProgress())
+ {
+ ScProgress *pProgress = ScProgress::GetInterpretProgress();
+ if (pProgress && pProgress->Enabled())
+ {
+ pProgress->SetStateCountDownOnPercent(
+ rDocument.GetFormulaCodeInTree()/MIN_NO_CODES_PER_PROGRESS_UPDATE );
+ }
+
+ switch (pInterpreter->GetVolatileType())
+ {
+ case ScInterpreter::VOLATILE:
+ // Volatile via built-in volatile functions. No actions needed.
+ break;
+ case ScInterpreter::VOLATILE_MACRO:
+ // The formula contains a volatile macro.
+ pCode->SetExclusiveRecalcModeAlways();
+ rDocument.PutInFormulaTree(this);
+ StartListeningTo(rDocument);
+ break;
+ case ScInterpreter::NOT_VOLATILE:
+ if (pCode->IsRecalcModeAlways())
+ {
+ // The formula was previously volatile, but no more.
+ EndListeningTo(rDocument);
+ pCode->SetExclusiveRecalcModeNormal();
+ }
+ else
+ {
+ // non-volatile formula. End listening to the area in case
+ // it's listening due to macro module change.
+ rDocument.EndListeningArea(BCA_LISTEN_ALWAYS, false, this);
+ }
+ rDocument.RemoveFromFormulaTree(this);
+ break;
+ default:
+ ;
+ }
+ }
+ }
+ else
+ {
+ // Cells with compiler errors should not be marked dirty forever
+ OSL_ENSURE( pCode->GetCodeError() != FormulaError::NONE, "no RPN code and no errors ?!?!" );
+ ResetDirty();
+ }
+}
+
+void ScFormulaCell::HandleStuffAfterParallelCalculation(ScInterpreter* pInterpreter)
+{
+ if( !pCode->GetCodeLen() )
+ return;
+
+ if ( !pCode->IsRecalcModeAlways() )
+ rDocument.RemoveFromFormulaTree( this );
+
+ std::unique_ptr<ScInterpreter> pScopedInterpreter;
+ if (pInterpreter)
+ pInterpreter->Init(this, aPos, *pCode);
+ else
+ {
+ pScopedInterpreter.reset(new ScInterpreter( this, rDocument, rDocument.GetNonThreadedContext(), aPos, *pCode ));
+ pInterpreter = pScopedInterpreter.get();
+ }
+
+ switch (pInterpreter->GetVolatileType())
+ {
+ case ScInterpreter::VOLATILE_MACRO:
+ // The formula contains a volatile macro.
+ pCode->SetExclusiveRecalcModeAlways();
+ rDocument.PutInFormulaTree(this);
+ StartListeningTo(rDocument);
+ break;
+ case ScInterpreter::NOT_VOLATILE:
+ if (pCode->IsRecalcModeAlways())
+ {
+ // The formula was previously volatile, but no more.
+ EndListeningTo(rDocument);
+ pCode->SetExclusiveRecalcModeNormal();
+ }
+ else
+ {
+ // non-volatile formula. End listening to the area in case
+ // it's listening due to macro module change.
+ rDocument.EndListeningArea(BCA_LISTEN_ALWAYS, false, this);
+ }
+ rDocument.RemoveFromFormulaTree(this);
+ break;
+ default:
+ ;
+ }
+}
+
+void ScFormulaCell::SetCompile( bool bVal )
+{
+ bCompile = bVal;
+}
+
+void ScFormulaCell::SetMatColsRows( SCCOL nCols, SCROW nRows )
+{
+ ScMatrixFormulaCellToken* pMat = aResult.GetMatrixFormulaCellTokenNonConst();
+ if (pMat)
+ pMat->SetMatColsRows( nCols, nRows );
+ else if (nCols || nRows)
+ {
+ aResult.SetToken( new ScMatrixFormulaCellToken( nCols, nRows));
+ // Setting the new token actually forces an empty result at this top
+ // left cell, so have that recalculated.
+ SetDirty();
+ }
+}
+
+void ScFormulaCell::GetMatColsRows( SCCOL & nCols, SCROW & nRows ) const
+{
+ const ScMatrixFormulaCellToken* pMat = aResult.GetMatrixFormulaCellToken();
+ if (pMat)
+ pMat->GetMatColsRows( nCols, nRows);
+ else
+ {
+ nCols = 0;
+ nRows = 0;
+ }
+}
+
+void ScFormulaCell::SetInChangeTrack( bool bVal )
+{
+ bInChangeTrack = bVal;
+}
+
+void ScFormulaCell::Notify( const SfxHint& rHint )
+{
+ if (rDocument.IsInDtorClear())
+ return;
+
+ const SfxHintId nHint = rHint.GetId();
+ if (nHint == SfxHintId::ScReference)
+ {
+ const sc::RefHint& rRefHint = static_cast<const sc::RefHint&>(rHint);
+
+ switch (rRefHint.getType())
+ {
+ case sc::RefHint::ColumnReordered:
+ {
+ const sc::RefColReorderHint& rRefColReorder =
+ static_cast<const sc::RefColReorderHint&>(rRefHint);
+ if (!IsShared() || IsSharedTop())
+ pCode->MoveReferenceColReorder(
+ aPos, rRefColReorder.getTab(),
+ rRefColReorder.getStartRow(),
+ rRefColReorder.getEndRow(),
+ rRefColReorder.getColMap());
+ }
+ break;
+ case sc::RefHint::RowReordered:
+ {
+ const sc::RefRowReorderHint& rRefRowReorder =
+ static_cast<const sc::RefRowReorderHint&>(rRefHint);
+ if (!IsShared() || IsSharedTop())
+ pCode->MoveReferenceRowReorder(
+ aPos, rRefRowReorder.getTab(),
+ rRefRowReorder.getStartColumn(),
+ rRefRowReorder.getEndColumn(),
+ rRefRowReorder.getRowMap());
+ }
+ break;
+ case sc::RefHint::StartListening:
+ {
+ StartListeningTo(rDocument);
+ }
+ break;
+ case sc::RefHint::StopListening:
+ {
+ EndListeningTo(rDocument);
+ }
+ break;
+ default:
+ ;
+ }
+
+ return;
+ }
+
+ if ( rDocument.GetHardRecalcState() != ScDocument::HardRecalcState::OFF )
+ return;
+
+ if (!(nHint == SfxHintId::ScDataChanged || nHint == SfxHintId::ScTableOpDirty || (bSubTotal && nHint == SfxHintId::ScHiddenRowsChanged)))
+ return;
+
+ bool bForceTrack = false;
+ if ( nHint == SfxHintId::ScTableOpDirty )
+ {
+ bForceTrack = !bTableOpDirty;
+ if ( !bTableOpDirty )
+ {
+ rDocument.AddTableOpFormulaCell( this );
+ bTableOpDirty = true;
+ }
+ }
+ else
+ {
+ bForceTrack = !bDirty;
+ SetDirtyVar();
+ }
+ // Don't remove from FormulaTree to put in FormulaTrack to
+ // put in FormulaTree again and again, only if necessary.
+ // Any other means except ScRecalcMode::ALWAYS by which a cell could
+ // be in FormulaTree if it would notify other cells through
+ // FormulaTrack which weren't in FormulaTrack/FormulaTree before?!?
+ // Yes. The new TableOpDirty made it necessary to have a
+ // forced mode where formulas may still be in FormulaTree from
+ // TableOpDirty but have to notify dependents for normal dirty.
+ if ( (bForceTrack || !rDocument.IsInFormulaTree( this )
+ || pCode->IsRecalcModeAlways())
+ && !rDocument.IsInFormulaTrack( this ) )
+ rDocument.AppendToFormulaTrack( this );
+}
+
+void ScFormulaCell::Query( SvtListener::QueryBase& rQuery ) const
+{
+ switch (rQuery.getId())
+ {
+ case SC_LISTENER_QUERY_FORMULA_GROUP_POS:
+ {
+ sc::RefQueryFormulaGroup& rRefQuery =
+ static_cast<sc::RefQueryFormulaGroup&>(rQuery);
+ if (IsShared())
+ rRefQuery.add(aPos);
+ }
+ break;
+ default:
+ ;
+ }
+}
+
+void ScFormulaCell::SetDirty( bool bDirtyFlag )
+{
+ if (IsInChangeTrack())
+ return;
+
+ if ( rDocument.GetHardRecalcState() != ScDocument::HardRecalcState::OFF )
+ {
+ SetDirtyVar();
+ rDocument.SetStreamValid(aPos.Tab(), false);
+ return;
+ }
+
+ // Avoid multiple formula tracking in Load() and in CompileAll()
+ // after CopyScenario() and CopyBlockFromClip().
+ // If unconditional formula tracking is needed, set bDirty=false
+ // before calling SetDirty(), for example in CompileTokenArray().
+ if ( !bDirty || mbPostponedDirty || !rDocument.IsInFormulaTree( this ) )
+ {
+ if( bDirtyFlag )
+ SetDirtyVar();
+ rDocument.AppendToFormulaTrack( this );
+
+ // While loading a document listeners have not been established yet.
+ // Tracking would remove this cell from the FormulaTrack and add it to
+ // the FormulaTree, once in there it would be assumed that its
+ // dependents already had been tracked and it would be skipped on a
+ // subsequent notify. Postpone tracking until all listeners are set.
+ if (!rDocument.IsImportingXML())
+ rDocument.TrackFormulas();
+ }
+
+ rDocument.SetStreamValid(aPos.Tab(), false);
+}
+
+void ScFormulaCell::SetDirtyVar()
+{
+ bDirty = true;
+ mbPostponedDirty = false;
+ if (mxGroup && mxGroup->meCalcState == sc::GroupCalcRunning)
+ {
+ mxGroup->meCalcState = sc::GroupCalcEnabled;
+ mxGroup->mbPartOfCycle = false;
+ }
+
+ // mark the sheet of this cell to be calculated
+ //#FIXME do we need to revert this remnant of old fake vba events? rDocument.AddCalculateTable( aPos.Tab() );
+}
+
+void ScFormulaCell::SetDirtyAfterLoad()
+{
+ bDirty = true;
+ if ( rDocument.GetHardRecalcState() == ScDocument::HardRecalcState::OFF )
+ rDocument.PutInFormulaTree( this );
+}
+
+void ScFormulaCell::ResetTableOpDirtyVar()
+{
+ bTableOpDirty = false;
+}
+
+void ScFormulaCell::SetTableOpDirty()
+{
+ if ( IsInChangeTrack() )
+ return;
+
+ if ( rDocument.GetHardRecalcState() != ScDocument::HardRecalcState::OFF )
+ bTableOpDirty = true;
+ else
+ {
+ if ( !bTableOpDirty || !rDocument.IsInFormulaTree( this ) )
+ {
+ if ( !bTableOpDirty )
+ {
+ rDocument.AddTableOpFormulaCell( this );
+ bTableOpDirty = true;
+ }
+ rDocument.AppendToFormulaTrack( this );
+ rDocument.TrackFormulas( SfxHintId::ScTableOpDirty );
+ }
+ }
+}
+
+void ScFormulaCell::SetResultDouble( double n )
+{
+ aResult.SetDouble(n);
+}
+
+void ScFormulaCell::SetResultToken( const formula::FormulaToken* pToken )
+{
+ aResult.SetToken(pToken);
+}
+
+const svl::SharedString & ScFormulaCell::GetResultString() const
+{
+ return aResult.GetString();
+}
+
+bool ScFormulaCell::HasHybridStringResult() const
+{
+ return aResult.GetType() == formula::svHybridCell && !aResult.GetString().isEmpty();
+}
+
+void ScFormulaCell::SetResultMatrix( SCCOL nCols, SCROW nRows, const ScConstMatrixRef& pMat, const formula::FormulaToken* pUL )
+{
+ aResult.SetMatrix(nCols, nRows, pMat, pUL);
+}
+
+void ScFormulaCell::SetErrCode( FormulaError n )
+{
+ /* FIXME: check the numerous places where ScTokenArray::GetCodeError() is
+ * used whether it is solely for transport of a simple result error and get
+ * rid of that abuse. */
+ pCode->SetCodeError( n );
+ // Hard set errors are transported as result type value per convention,
+ // e.g. via clipboard. ScFormulaResult::IsValue() and
+ // ScFormulaResult::GetDouble() handle that.
+ aResult.SetResultError( n );
+}
+
+void ScFormulaCell::SetResultError( FormulaError n )
+{
+ aResult.SetResultError( n );
+}
+
+void ScFormulaCell::AddRecalcMode( ScRecalcMode nBits )
+{
+ if ( (nBits & ScRecalcMode::EMask) != ScRecalcMode::NORMAL )
+ SetDirtyVar();
+ if ( nBits & ScRecalcMode::ONLOAD_ONCE )
+ { // OnLoadOnce is used only to set Dirty after filter import.
+ nBits = (nBits & ~ScRecalcMode::EMask) | ScRecalcMode::NORMAL;
+ }
+ pCode->AddRecalcMode( nBits );
+}
+
+void ScFormulaCell::SetHybridDouble( double n )
+{
+ aResult.SetHybridDouble( n);
+}
+
+void ScFormulaCell::SetHybridString( const svl::SharedString& r )
+{
+ aResult.SetHybridString( r);
+}
+
+void ScFormulaCell::SetHybridEmptyDisplayedAsString()
+{
+ aResult.SetHybridEmptyDisplayedAsString();
+}
+
+void ScFormulaCell::SetHybridFormula( const OUString& r,
+ const formula::FormulaGrammar::Grammar eGrammar )
+{
+ aResult.SetHybridFormula( r); eTempGrammar = eGrammar;
+}
+
+OUString ScFormulaCell::GetHybridFormula() const
+{
+ return aResult.GetHybridFormula();
+}
+
+// Dynamically create the URLField on a mouse-over action on a hyperlink() cell.
+void ScFormulaCell::GetURLResult( OUString& rURL, OUString& rCellText )
+{
+ OUString aCellString;
+
+ const Color* pColor;
+
+ // Cell Text uses the Cell format while the URL uses
+ // the default format for the type.
+ const sal_uInt32 nCellFormat = rDocument.GetNumberFormat( aPos );
+ SvNumberFormatter* pFormatter = rDocument.GetFormatTable();
+
+ const sal_uInt32 nURLFormat = ScGlobal::GetStandardFormat( *pFormatter, nCellFormat, SvNumFormatType::NUMBER);
+
+ if ( IsValue() )
+ {
+ double fValue = GetValue();
+ pFormatter->GetOutputString( fValue, nCellFormat, rCellText, &pColor );
+ }
+ else
+ {
+ aCellString = GetString().getString();
+ pFormatter->GetOutputString( aCellString, nCellFormat, rCellText, &pColor );
+ }
+ ScConstMatrixRef xMat( aResult.GetMatrix());
+ if (xMat)
+ {
+ // determine if the matrix result is a string or value.
+ if (!xMat->IsValue(0, 1))
+ rURL = xMat->GetString(0, 1).getString();
+ else
+ pFormatter->GetOutputString(
+ xMat->GetDouble(0, 1), nURLFormat, rURL, &pColor);
+ }
+
+ if(rURL.isEmpty())
+ {
+ if(IsValue())
+ pFormatter->GetOutputString( GetValue(), nURLFormat, rURL, &pColor );
+ else
+ pFormatter->GetOutputString( aCellString, nURLFormat, rURL, &pColor );
+ }
+}
+
+bool ScFormulaCell::IsMultilineResult()
+{
+ if (!IsValue())
+ return aResult.IsMultiline();
+ return false;
+}
+
+bool ScFormulaCell::IsHyperLinkCell() const
+{
+ return pCode && pCode->IsHyperLink();
+}
+
+std::unique_ptr<EditTextObject> ScFormulaCell::CreateURLObject()
+{
+ OUString aCellText;
+ OUString aURL;
+ GetURLResult( aURL, aCellText );
+
+ return ScEditUtil::CreateURLObjectFromURL( rDocument, aURL, aCellText );
+}
+
+bool ScFormulaCell::IsEmpty()
+{
+ MaybeInterpret();
+ return aResult.GetCellResultType() == formula::svEmptyCell;
+}
+
+bool ScFormulaCell::IsEmptyDisplayedAsString()
+{
+ MaybeInterpret();
+ return aResult.IsEmptyDisplayedAsString();
+}
+
+bool ScFormulaCell::IsValue()
+{
+ MaybeInterpret();
+ return aResult.IsValue();
+}
+
+bool ScFormulaCell::IsValueNoError()
+{
+ MaybeInterpret();
+ if (pCode->GetCodeError() != FormulaError::NONE)
+ return false;
+
+ return aResult.IsValueNoError();
+}
+
+bool ScFormulaCell::IsValueNoError() const
+{
+ if (NeedsInterpret())
+ // false if the cell is dirty & needs to be interpreted.
+ return false;
+
+ if (pCode->GetCodeError() != FormulaError::NONE)
+ return false;
+
+ return aResult.IsValueNoError();
+}
+
+double ScFormulaCell::GetValue()
+{
+ MaybeInterpret();
+ return GetRawValue();
+}
+
+const svl::SharedString & ScFormulaCell::GetString()
+{
+ MaybeInterpret();
+ return GetRawString();
+}
+
+double ScFormulaCell::GetRawValue() const
+{
+ if ((pCode->GetCodeError() == FormulaError::NONE) &&
+ aResult.GetResultError() == FormulaError::NONE)
+ return aResult.GetDouble();
+ return 0.0;
+}
+
+const svl::SharedString & ScFormulaCell::GetRawString() const
+{
+ if ((pCode->GetCodeError() == FormulaError::NONE) &&
+ aResult.GetResultError() == FormulaError::NONE)
+ return aResult.GetString();
+
+ return svl::SharedString::getEmptyString();
+}
+
+const ScMatrix* ScFormulaCell::GetMatrix()
+{
+ if ( rDocument.GetAutoCalc() )
+ {
+ if( IsDirtyOrInTableOpDirty()
+ // Was stored !bDirty but an accompanying matrix cell was bDirty?
+ || (!bDirty && cMatrixFlag == ScMatrixMode::Formula && !aResult.GetMatrix()))
+ Interpret();
+ }
+ return aResult.GetMatrix().get();
+}
+
+bool ScFormulaCell::GetMatrixOrigin( const ScDocument& rDoc, ScAddress& rPos ) const
+{
+ switch ( cMatrixFlag )
+ {
+ case ScMatrixMode::Formula :
+ rPos = aPos;
+ return true;
+ case ScMatrixMode::Reference :
+ {
+ formula::FormulaTokenArrayPlainIterator aIter(*pCode);
+ formula::FormulaToken* t = aIter.GetNextReferenceRPN();
+ if( t )
+ {
+ ScSingleRefData& rRef = *t->GetSingleRef();
+ ScAddress aAbs = rRef.toAbs(rDoc, aPos);
+ if (rDoc.ValidAddress(aAbs))
+ {
+ rPos = aAbs;
+ return true;
+ }
+ }
+ }
+ break;
+ default: break;
+ }
+ return false;
+}
+
+sc::MatrixEdge ScFormulaCell::GetMatrixEdge( const ScDocument& rDoc, ScAddress& rOrgPos ) const
+{
+ switch ( cMatrixFlag )
+ {
+ case ScMatrixMode::Formula :
+ case ScMatrixMode::Reference :
+ {
+ static thread_local SCCOL nC;
+ static thread_local SCROW nR;
+ ScAddress aOrg;
+ if ( !GetMatrixOrigin( rDoc, aOrg ) )
+ return sc::MatrixEdge::Nothing;
+ if ( aOrg != rOrgPos )
+ { // First time or a different matrix than last time.
+ rOrgPos = aOrg;
+ const ScFormulaCell* pFCell;
+ if ( cMatrixFlag == ScMatrixMode::Reference )
+ pFCell = rDocument.GetFormulaCell(aOrg);
+ else
+ pFCell = this; // this ScMatrixMode::Formula
+ // There's only one this, don't compare pFCell==this.
+ if (pFCell && pFCell->cMatrixFlag == ScMatrixMode::Formula)
+ {
+ pFCell->GetMatColsRows( nC, nR );
+ if ( nC == 0 || nR == 0 )
+ {
+ // No ScMatrixFormulaCellToken available yet, calculate new.
+ nC = 1;
+ nR = 1;
+ ScAddress aTmpOrg;
+ ScFormulaCell* pCell;
+ ScAddress aAdr( aOrg );
+ aAdr.IncCol();
+ bool bCont = true;
+ do
+ {
+ pCell = rDocument.GetFormulaCell(aAdr);
+ if (pCell && pCell->cMatrixFlag == ScMatrixMode::Reference &&
+ pCell->GetMatrixOrigin(rDocument, aTmpOrg) && aTmpOrg == aOrg)
+ {
+ nC++;
+ aAdr.IncCol();
+ }
+ else
+ bCont = false;
+ } while ( bCont );
+ aAdr = aOrg;
+ aAdr.IncRow();
+ bCont = true;
+ do
+ {
+ pCell = rDocument.GetFormulaCell(aAdr);
+ if (pCell && pCell->cMatrixFlag == ScMatrixMode::Reference &&
+ pCell->GetMatrixOrigin(rDocument, aTmpOrg) && aTmpOrg == aOrg)
+ {
+ nR++;
+ aAdr.IncRow();
+ }
+ else
+ bCont = false;
+ } while ( bCont );
+
+ const_cast<ScFormulaCell*>(pFCell)->SetMatColsRows(nC, nR);
+ }
+ }
+ else
+ {
+#if OSL_DEBUG_LEVEL > 0
+ SAL_WARN( "sc", "broken Matrix, no MatFormula at origin, Pos: "
+ << aPos.Format(ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID, &rDocument)
+ << ", MatOrg: "
+ << aOrg.Format(ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID, &rDocument) );
+#endif
+ return sc::MatrixEdge::Nothing;
+ }
+ }
+ // here we are, healthy and clean, somewhere in between
+ SCCOL dC = aPos.Col() - aOrg.Col();
+ SCROW dR = aPos.Row() - aOrg.Row();
+ sc::MatrixEdge nEdges = sc::MatrixEdge::Nothing;
+ if ( dC >= 0 && dR >= 0 && dC < nC && dR < nR )
+ {
+ if ( dC == 0 )
+ nEdges |= sc::MatrixEdge::Left;
+ if ( dC+1 == nC )
+ nEdges |= sc::MatrixEdge::Right;
+ if ( dR == 0 )
+ nEdges |= sc::MatrixEdge::Top;
+ if ( dR+1 == nR )
+ nEdges |= sc::MatrixEdge::Bottom;
+ if ( nEdges == sc::MatrixEdge::Nothing )
+ nEdges = sc::MatrixEdge::Inside;
+ }
+ else
+ {
+ SAL_WARN( "sc", "broken Matrix, Pos: "
+ << aPos.Format(ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID, &rDocument)
+ << ", MatOrg: "
+ << aOrg.Format(ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID, &rDocument)
+ << ", MatCols: " << static_cast<sal_Int32>( nC )
+ << ", MatRows: " << static_cast<sal_Int32>( nR )
+ << ", DiffCols: " << static_cast<sal_Int32>( dC )
+ << ", DiffRows: " << static_cast<sal_Int32>( dR ));
+ }
+ return nEdges;
+ }
+ default:
+ return sc::MatrixEdge::Nothing;
+ }
+}
+
+FormulaError ScFormulaCell::GetErrCode()
+{
+ MaybeInterpret();
+
+ /* FIXME: If ScTokenArray::SetCodeError() was really only for code errors
+ * and not also abused for signaling other error conditions we could bail
+ * out even before attempting to interpret broken code. */
+ FormulaError nErr = pCode->GetCodeError();
+ if (nErr != FormulaError::NONE)
+ return nErr;
+ return aResult.GetResultError();
+}
+
+FormulaError ScFormulaCell::GetRawError() const
+{
+ FormulaError nErr = pCode->GetCodeError();
+ if (nErr != FormulaError::NONE)
+ return nErr;
+ return aResult.GetResultError();
+}
+
+bool ScFormulaCell::GetErrorOrValue( FormulaError& rErr, double& rVal )
+{
+ MaybeInterpret();
+
+ rErr = pCode->GetCodeError();
+ if (rErr != FormulaError::NONE)
+ return true;
+
+ return aResult.GetErrorOrDouble(rErr, rVal);
+}
+
+sc::FormulaResultValue ScFormulaCell::GetResult()
+{
+ MaybeInterpret();
+
+ FormulaError nErr = pCode->GetCodeError();
+ if (nErr != FormulaError::NONE)
+ return sc::FormulaResultValue(nErr);
+
+ return aResult.GetResult();
+}
+
+sc::FormulaResultValue ScFormulaCell::GetResult() const
+{
+ FormulaError nErr = pCode->GetCodeError();
+ if (nErr != FormulaError::NONE)
+ return sc::FormulaResultValue(nErr);
+
+ return aResult.GetResult();
+}
+
+bool ScFormulaCell::HasOneReference( ScRange& r ) const
+{
+ formula::FormulaTokenArrayPlainIterator aIter(*pCode);
+ formula::FormulaToken* p = aIter.GetNextReferenceRPN();
+ if( p && !aIter.GetNextReferenceRPN() ) // only one!
+ {
+ SingleDoubleRefProvider aProv( *p );
+ r.aStart = aProv.Ref1.toAbs(rDocument, aPos);
+ r.aEnd = aProv.Ref2.toAbs(rDocument, aPos);
+ return true;
+ }
+ else
+ return false;
+}
+
+bool
+ScFormulaCell::HasRefListExpressibleAsOneReference(ScRange& rRange) const
+{
+ /* If there appears just one reference in the formula, it's the same
+ as HasOneReference(). If there are more of them, they can denote
+ one range if they are (sole) arguments of one function.
+ Union of these references must form one range and their
+ intersection must be empty set.
+ */
+
+ // Detect the simple case of exactly one reference in advance without all
+ // overhead.
+ // #i107741# Doing so actually makes outlines using SUBTOTAL(x;reference)
+ // work again, where the function does not have only references.
+ if (HasOneReference( rRange))
+ return true;
+
+ // Get first reference, if any
+ formula::FormulaTokenArrayPlainIterator aIter(*pCode);
+ formula::FormulaToken* const pFirstReference(aIter.GetNextReferenceRPN());
+ if (pFirstReference)
+ {
+ // Collect all consecutive references, starting by the one
+ // already found
+ std::vector<formula::FormulaToken*> aReferences { pFirstReference };
+ FormulaToken* pToken(aIter.NextRPN());
+ FormulaToken* pFunction(nullptr);
+ while (pToken)
+ {
+ if (lcl_isReference(*pToken))
+ {
+ aReferences.push_back(pToken);
+ pToken = aIter.NextRPN();
+ }
+ else
+ {
+ if (pToken->IsFunction())
+ {
+ pFunction = pToken;
+ }
+ break;
+ }
+ }
+ if (pFunction && !aIter.GetNextReferenceRPN()
+ && (pFunction->GetParamCount() == aReferences.size()))
+ {
+ return lcl_refListFormsOneRange(rDocument, aPos, aReferences, rRange);
+ }
+ }
+ return false;
+}
+
+ScFormulaCell::RelNameRef ScFormulaCell::HasRelNameReference() const
+{
+ RelNameRef eRelNameRef = RelNameRef::NONE;
+ formula::FormulaTokenArrayPlainIterator aIter(*pCode);
+ formula::FormulaToken* t;
+ while ( ( t = aIter.GetNextReferenceRPN() ) != nullptr )
+ {
+ switch (t->GetType())
+ {
+ case formula::svSingleRef:
+ if (t->GetSingleRef()->IsRelName() && eRelNameRef == RelNameRef::NONE)
+ eRelNameRef = RelNameRef::SINGLE;
+ break;
+ case formula::svDoubleRef:
+ if (t->GetDoubleRef()->Ref1.IsRelName() || t->GetDoubleRef()->Ref2.IsRelName())
+ // May originate from individual cell names, in which case
+ // it needs recompilation.
+ return RelNameRef::DOUBLE;
+ /* TODO: have an extra flag at ScComplexRefData if range was
+ * extended? or too cumbersome? might narrow recompilation to
+ * only needed cases.
+ * */
+ break;
+ default:
+ ; // nothing
+ }
+ }
+ return eRelNameRef;
+}
+
+bool ScFormulaCell::UpdatePosOnShift( const sc::RefUpdateContext& rCxt )
+{
+ if (rCxt.meMode != URM_INSDEL)
+ // Just in case...
+ return false;
+
+ if (!rCxt.mnColDelta && !rCxt.mnRowDelta && !rCxt.mnTabDelta)
+ // No movement.
+ return false;
+
+ if (!rCxt.maRange.Contains(aPos))
+ return false;
+
+ // This formula cell itself is being shifted during cell range
+ // insertion or deletion. Update its position.
+ ScAddress aErrorPos( ScAddress::UNINITIALIZED );
+ if (!aPos.Move(rCxt.mnColDelta, rCxt.mnRowDelta, rCxt.mnTabDelta, aErrorPos, rCxt.mrDoc))
+ {
+ assert(!"can't move ScFormulaCell");
+ }
+
+ return true;
+}
+
+namespace {
+
+/**
+ * Check if we need to re-compile column or row names.
+ */
+bool checkCompileColRowName(
+ const sc::RefUpdateContext& rCxt, ScDocument& rDoc, const ScTokenArray& rCode,
+ const ScAddress& aOldPos, const ScAddress& aPos, bool bValChanged)
+{
+ switch (rCxt.meMode)
+ {
+ case URM_INSDEL:
+ {
+ if (rCxt.mnColDelta <= 0 && rCxt.mnRowDelta <= 0)
+ return false;
+
+ formula::FormulaTokenArrayPlainIterator aIter(rCode);
+ formula::FormulaToken* t;
+ ScRangePairList* pColList = rDoc.GetColNameRanges();
+ ScRangePairList* pRowList = rDoc.GetRowNameRanges();
+ while ((t = aIter.GetNextColRowName()) != nullptr)
+ {
+ ScSingleRefData& rRef = *t->GetSingleRef();
+ if (rCxt.mnRowDelta > 0 && rRef.IsColRel())
+ { // ColName
+ ScAddress aAdr = rRef.toAbs(rDoc, aPos);
+ ScRangePair* pR = pColList->Find( aAdr );
+ if ( pR )
+ { // defined
+ if (pR->GetRange(1).aStart.Row() == rCxt.maRange.aStart.Row())
+ return true;
+ }
+ else
+ { // on the fly
+ if (aAdr.Row() + 1 == rCxt.maRange.aStart.Row())
+ return true;
+ }
+ }
+ if (rCxt.mnColDelta > 0 && rRef.IsRowRel())
+ { // RowName
+ ScAddress aAdr = rRef.toAbs(rDoc, aPos);
+ ScRangePair* pR = pRowList->Find( aAdr );
+ if ( pR )
+ { // defined
+ if ( pR->GetRange(1).aStart.Col() == rCxt.maRange.aStart.Col())
+ return true;
+ }
+ else
+ { // on the fly
+ if (aAdr.Col() + 1 == rCxt.maRange.aStart.Col())
+ return true;
+ }
+ }
+ }
+ }
+ break;
+ case URM_MOVE:
+ { // Recompile for Move/D&D when ColRowName was moved or this Cell
+ // points to one and was moved.
+ bool bMoved = (aPos != aOldPos);
+ if (bMoved)
+ return true;
+
+ formula::FormulaTokenArrayPlainIterator aIter(rCode);
+ const formula::FormulaToken* t = aIter.GetNextColRowName();
+ for (; t; t = aIter.GetNextColRowName())
+ {
+ const ScSingleRefData& rRef = *t->GetSingleRef();
+ ScAddress aAbs = rRef.toAbs(rDoc, aPos);
+ if (rDoc.ValidAddress(aAbs))
+ {
+ if (rCxt.maRange.Contains(aAbs))
+ return true;
+ }
+ }
+ }
+ break;
+ case URM_COPY:
+ return bValChanged;
+ default:
+ ;
+ }
+
+ return false;
+}
+
+void setOldCodeToUndo(
+ ScDocument& rUndoDoc, const ScAddress& aUndoPos, const ScTokenArray* pOldCode, FormulaGrammar::Grammar eTempGrammar, ScMatrixMode cMatrixFlag)
+{
+ // Copy the cell to aUndoPos, which is its current position in the document,
+ // so this works when UpdateReference is called before moving the cells
+ // (InsertCells/DeleteCells - aPos is changed above) as well as when UpdateReference
+ // is called after moving the cells (MoveBlock/PasteFromClip - aOldPos is changed).
+
+ // If there is already a formula cell in the undo document, don't overwrite it,
+ // the first (oldest) is the important cell.
+ if (rUndoDoc.GetCellType(aUndoPos) == CELLTYPE_FORMULA)
+ return;
+
+ ScFormulaCell* pFCell =
+ new ScFormulaCell(
+ rUndoDoc, aUndoPos, pOldCode ? *pOldCode : ScTokenArray(rUndoDoc), eTempGrammar, cMatrixFlag);
+
+ pFCell->SetResultToken(nullptr); // to recognize it as changed later (Cut/Paste!)
+ rUndoDoc.SetFormulaCell(aUndoPos, pFCell);
+}
+
+}
+
+bool ScFormulaCell::UpdateReferenceOnShift(
+ const sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc, const ScAddress* pUndoCellPos )
+{
+ if (rCxt.meMode != URM_INSDEL)
+ // Just in case...
+ return false;
+
+ bool bCellStateChanged = false;
+ ScAddress aUndoPos( aPos ); // position for undo cell in pUndoDoc
+ if ( pUndoCellPos )
+ aUndoPos = *pUndoCellPos;
+ ScAddress aOldPos( aPos );
+ bCellStateChanged = UpdatePosOnShift(rCxt);
+
+ // Check presence of any references or column row names.
+ bool bHasRefs = pCode->HasReferences();
+ bool bHasColRowNames = false;
+ if (!bHasRefs)
+ {
+ bHasColRowNames = (formula::FormulaTokenArrayPlainIterator(*pCode).GetNextColRowName() != nullptr);
+ bHasRefs = bHasColRowNames;
+ }
+ bool bOnRefMove = pCode->IsRecalcModeOnRefMove();
+
+ if (!bHasRefs && !bOnRefMove)
+ // This formula cell contains no references, nor needs recalculating
+ // on reference update. Bail out.
+ return bCellStateChanged;
+
+ std::unique_ptr<ScTokenArray> pOldCode;
+ if (pUndoDoc)
+ pOldCode = pCode->Clone();
+
+ bool bValChanged = false;
+ bool bRefModified = false;
+ bool bRecompile = bCompile;
+
+ if (bHasRefs)
+ {
+ // Update cell or range references.
+ sc::RefUpdateResult aRes = pCode->AdjustReferenceOnShift(rCxt, aOldPos);
+ bRefModified = aRes.mbReferenceModified;
+ bValChanged = aRes.mbValueChanged;
+ if (aRes.mbNameModified)
+ bRecompile = true;
+ }
+
+ if (bValChanged || bRefModified)
+ bCellStateChanged = true;
+
+ if (bOnRefMove)
+ // Cell may reference itself, e.g. ocColumn, ocRow without parameter
+ bOnRefMove = (bValChanged || (aPos != aOldPos) || bRefModified);
+
+ bool bNewListening = false;
+ bool bInDeleteUndo = false;
+
+ if (bHasRefs)
+ {
+ // Upon Insert ColRowNames have to be recompiled in case the
+ // insertion occurs right in front of the range.
+ if (bHasColRowNames && !bRecompile)
+ bRecompile = checkCompileColRowName(rCxt, rDocument, *pCode, aOldPos, aPos, bValChanged);
+
+ ScChangeTrack* pChangeTrack = rDocument.GetChangeTrack();
+ bInDeleteUndo = (pChangeTrack && pChangeTrack->IsInDeleteUndo());
+
+ // RelNameRefs are always moved
+ bool bHasRelName = false;
+ if (!bRecompile)
+ {
+ RelNameRef eRelNameRef = HasRelNameReference();
+ bHasRelName = (eRelNameRef != RelNameRef::NONE);
+ bRecompile = (eRelNameRef == RelNameRef::DOUBLE);
+ }
+ // Reference changed and new listening needed?
+ // Except in Insert/Delete without specialities.
+ bNewListening = (bRefModified || bRecompile
+ || (bValChanged && bInDeleteUndo) || bHasRelName);
+
+ if ( bNewListening )
+ EndListeningTo(rDocument, pOldCode.get(), aOldPos);
+ }
+
+ // NeedDirty for changes except for Copy and Move/Insert without RelNames
+ bool bNeedDirty = (bValChanged || bRecompile || bOnRefMove);
+
+ if (pUndoDoc && (bValChanged || bOnRefMove))
+ setOldCodeToUndo(*pUndoDoc, aUndoPos, pOldCode.get(), eTempGrammar, cMatrixFlag);
+
+ bCompile |= bRecompile;
+ if (bCompile)
+ {
+ CompileTokenArray( bNewListening ); // no Listening
+ bNeedDirty = true;
+ }
+
+ if ( !bInDeleteUndo )
+ { // In ChangeTrack Delete-Reject listeners are established in
+ // InsertCol/InsertRow
+ if ( bNewListening )
+ {
+ // Inserts/Deletes re-establish listeners after all
+ // UpdateReference calls.
+ // All replaced shared formula listeners have to be
+ // established after an Insert or Delete. Do nothing here.
+ SetNeedsListening( true);
+ }
+ }
+
+ if (bNeedDirty)
+ { // Cut off references, invalid or similar?
+ // Postpone SetDirty() until all listeners have been re-established in
+ // Inserts/Deletes.
+ mbPostponedDirty = true;
+ }
+
+ return bCellStateChanged;
+}
+
+bool ScFormulaCell::UpdateReferenceOnMove(
+ const sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc, const ScAddress* pUndoCellPos )
+{
+ if (rCxt.meMode != URM_MOVE)
+ return false;
+
+ ScAddress aUndoPos( aPos ); // position for undo cell in pUndoDoc
+ if ( pUndoCellPos )
+ aUndoPos = *pUndoCellPos;
+ ScAddress aOldPos( aPos );
+
+ bool bCellInMoveTarget = rCxt.maRange.Contains(aPos);
+
+ if ( bCellInMoveTarget )
+ {
+ // The cell is being moved or copied to a new position. I guess the
+ // position has been updated prior to this call? Determine
+ // its original position before the move which will be used to adjust
+ // relative references later.
+ aOldPos.Set(aPos.Col() - rCxt.mnColDelta, aPos.Row() - rCxt.mnRowDelta, aPos.Tab() - rCxt.mnTabDelta);
+ }
+
+ // Check presence of any references or column row names.
+ bool bHasRefs = pCode->HasReferences();
+ bool bHasColRowNames = false;
+ if (!bHasRefs)
+ {
+ bHasColRowNames = (formula::FormulaTokenArrayPlainIterator(*pCode).GetNextColRowName() != nullptr);
+ bHasRefs = bHasColRowNames;
+ }
+ bool bOnRefMove = pCode->IsRecalcModeOnRefMove();
+
+ if (!bHasRefs && !bOnRefMove)
+ // This formula cell contains no references, nor needs recalculating
+ // on reference update. Bail out.
+ return false;
+
+ bool bCellStateChanged = false;
+ std::unique_ptr<ScTokenArray> pOldCode;
+ if (pUndoDoc)
+ pOldCode = pCode->Clone();
+
+ bool bValChanged = false;
+ bool bRefModified = false;
+
+ if (bHasRefs)
+ {
+ // Update cell or range references.
+ sc::RefUpdateResult aRes = pCode->AdjustReferenceOnMove(rCxt, aOldPos, aPos);
+ bRefModified = aRes.mbReferenceModified || aRes.mbNameModified;
+ bValChanged = aRes.mbValueChanged;
+ if (aRes.mbNameModified)
+ // Re-compile to get the RPN token regenerated to reflect updated names.
+ bCompile = true;
+ }
+
+ if (bValChanged || bRefModified)
+ bCellStateChanged = true;
+
+ if (bOnRefMove)
+ // Cell may reference itself, e.g. ocColumn, ocRow without parameter
+ bOnRefMove = (bValChanged || (aPos != aOldPos));
+
+ bool bColRowNameCompile = false;
+ bool bHasRelName = false;
+ bool bNewListening = false;
+ bool bInDeleteUndo = false;
+
+ if (bHasRefs)
+ {
+ // Upon Insert ColRowNames have to be recompiled in case the
+ // insertion occurs right in front of the range.
+ if (bHasColRowNames)
+ bColRowNameCompile = checkCompileColRowName(rCxt, rDocument, *pCode, aOldPos, aPos, bValChanged);
+
+ ScChangeTrack* pChangeTrack = rDocument.GetChangeTrack();
+ bInDeleteUndo = (pChangeTrack && pChangeTrack->IsInDeleteUndo());
+
+ // RelNameRefs are always moved
+ RelNameRef eRelNameRef = HasRelNameReference();
+ bHasRelName = (eRelNameRef != RelNameRef::NONE);
+ bCompile |= (eRelNameRef == RelNameRef::DOUBLE);
+ // Reference changed and new listening needed?
+ // Except in Insert/Delete without specialties.
+ bNewListening = (bRefModified || bColRowNameCompile
+ || bValChanged || bHasRelName)
+ // #i36299# Don't duplicate action during cut&paste / drag&drop
+ // on a cell in the range moved, start/end listeners is done
+ // via ScDocument::DeleteArea() and ScDocument::CopyFromClip().
+ && !(rDocument.IsInsertingFromOtherDoc() && rCxt.maRange.Contains(aPos));
+
+ if ( bNewListening )
+ EndListeningTo(rDocument, pOldCode.get(), aOldPos);
+ }
+
+ bool bNeedDirty = false;
+ // NeedDirty for changes except for Copy and Move/Insert without RelNames
+ if ( bRefModified || bColRowNameCompile ||
+ (bValChanged && bHasRelName ) || bOnRefMove)
+ bNeedDirty = true;
+
+ if (pUndoDoc && !bCellInMoveTarget && (bValChanged || bRefModified || bOnRefMove))
+ setOldCodeToUndo(*pUndoDoc, aUndoPos, pOldCode.get(), eTempGrammar, cMatrixFlag);
+
+ bValChanged = false;
+
+ bCompile = (bCompile || bValChanged || bColRowNameCompile);
+ if ( bCompile )
+ {
+ CompileTokenArray( bNewListening ); // no Listening
+ bNeedDirty = true;
+ }
+
+ if ( !bInDeleteUndo )
+ { // In ChangeTrack Delete-Reject listeners are established in
+ // InsertCol/InsertRow
+ if ( bNewListening )
+ {
+ StartListeningTo( rDocument );
+ }
+ }
+
+ if (bNeedDirty)
+ { // Cut off references, invalid or similar?
+ sc::AutoCalcSwitch aACSwitch(rDocument, false);
+ SetDirty();
+ }
+
+ return bCellStateChanged;
+}
+
+bool ScFormulaCell::UpdateReferenceOnCopy(
+ const sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc, const ScAddress* pUndoCellPos )
+{
+ if (rCxt.meMode != URM_COPY)
+ return false;
+
+ ScAddress aUndoPos( aPos ); // position for undo cell in pUndoDoc
+ if ( pUndoCellPos )
+ aUndoPos = *pUndoCellPos;
+ ScAddress aOldPos( aPos );
+
+ if (rCxt.maRange.Contains(aPos))
+ {
+ // The cell is being moved or copied to a new position. I guess the
+ // position has been updated prior to this call? Determine
+ // its original position before the move which will be used to adjust
+ // relative references later.
+ aOldPos.Set(aPos.Col() - rCxt.mnColDelta, aPos.Row() - rCxt.mnRowDelta, aPos.Tab() - rCxt.mnTabDelta);
+ }
+
+ // Check presence of any references or column row names.
+ bool bHasRefs = pCode->HasReferences();
+ bool bHasColRowNames = (formula::FormulaTokenArrayPlainIterator(*pCode).GetNextColRowName() != nullptr);
+ bHasRefs = bHasRefs || bHasColRowNames;
+ bool bOnRefMove = pCode->IsRecalcModeOnRefMove();
+
+ if (!bHasRefs && !bOnRefMove)
+ // This formula cell contains no references, nor needs recalculating
+ // on reference update. Bail out.
+ return false;
+
+ std::unique_ptr<ScTokenArray> pOldCode;
+ if (pUndoDoc)
+ pOldCode = pCode->Clone();
+
+ if (bOnRefMove)
+ // Cell may reference itself, e.g. ocColumn, ocRow without parameter
+ bOnRefMove = (aPos != aOldPos);
+
+ bool bNeedDirty = bOnRefMove;
+
+ if (pUndoDoc && bOnRefMove)
+ setOldCodeToUndo(*pUndoDoc, aUndoPos, pOldCode.get(), eTempGrammar, cMatrixFlag);
+
+ if (bCompile)
+ {
+ CompileTokenArray(); // no Listening
+ bNeedDirty = true;
+ }
+
+ if (bNeedDirty)
+ { // Cut off references, invalid or similar?
+ sc::AutoCalcSwitch aACSwitch(rDocument, false);
+ SetDirty();
+ }
+
+ return false;
+}
+
+bool ScFormulaCell::UpdateReference(
+ const sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc, const ScAddress* pUndoCellPos )
+{
+ if (rDocument.IsClipOrUndo())
+ return false;
+
+ if (mxGroup && mxGroup->mpTopCell != this)
+ {
+ // This is not a top cell of a formula group. Don't update references.
+
+ switch (rCxt.meMode)
+ {
+ case URM_INSDEL:
+ return UpdatePosOnShift(rCxt);
+ default:
+ ;
+ }
+ return false;
+ }
+
+ switch (rCxt.meMode)
+ {
+ case URM_INSDEL:
+ return UpdateReferenceOnShift(rCxt, pUndoDoc, pUndoCellPos);
+ case URM_MOVE:
+ return UpdateReferenceOnMove(rCxt, pUndoDoc, pUndoCellPos);
+ case URM_COPY:
+ return UpdateReferenceOnCopy(rCxt, pUndoDoc, pUndoCellPos);
+ default:
+ ;
+ }
+
+ return false;
+}
+
+void ScFormulaCell::UpdateInsertTab( const sc::RefUpdateInsertTabContext& rCxt )
+{
+ // Adjust tokens only when it's not grouped or grouped top cell.
+ bool bAdjustCode = !mxGroup || mxGroup->mpTopCell == this;
+ bool bPosChanged = (rCxt.mnInsertPos <= aPos.Tab());
+ if (rDocument.IsClipOrUndo() || !pCode->HasReferences())
+ {
+ if (bPosChanged)
+ aPos.IncTab(rCxt.mnSheets);
+
+ return;
+ }
+
+ EndListeningTo( rDocument );
+ ScAddress aOldPos = aPos;
+ // IncTab _after_ EndListeningTo and _before_ Compiler UpdateInsertTab!
+ if (bPosChanged)
+ aPos.IncTab(rCxt.mnSheets);
+
+ if (!bAdjustCode)
+ return;
+
+ sc::RefUpdateResult aRes = pCode->AdjustReferenceOnInsertedTab(rCxt, aOldPos);
+ if (aRes.mbNameModified)
+ // Re-compile after new sheet(s) have been inserted.
+ bCompile = true;
+
+ // no StartListeningTo because the new sheets have not been inserted yet.
+}
+
+void ScFormulaCell::UpdateDeleteTab( const sc::RefUpdateDeleteTabContext& rCxt )
+{
+ // Adjust tokens only when it's not grouped or grouped top cell.
+ bool bAdjustCode = !mxGroup || mxGroup->mpTopCell == this;
+ bool bPosChanged = (aPos.Tab() >= rCxt.mnDeletePos + rCxt.mnSheets);
+ if (rDocument.IsClipOrUndo() || !pCode->HasReferences())
+ {
+ if (bPosChanged)
+ aPos.IncTab(-1*rCxt.mnSheets);
+ return;
+ }
+
+ EndListeningTo( rDocument );
+ // IncTab _after_ EndListeningTo and _before_ Compiler UpdateDeleteTab!
+ ScAddress aOldPos = aPos;
+ if (bPosChanged)
+ aPos.IncTab(-1*rCxt.mnSheets);
+
+ if (!bAdjustCode)
+ return;
+
+ sc::RefUpdateResult aRes = pCode->AdjustReferenceOnDeletedTab(rCxt, aOldPos);
+ if (aRes.mbNameModified)
+ // Re-compile after sheet(s) have been deleted.
+ bCompile = true;
+}
+
+void ScFormulaCell::UpdateMoveTab( const sc::RefUpdateMoveTabContext& rCxt, SCTAB nTabNo )
+{
+ // Adjust tokens only when it's not grouped or grouped top cell.
+ bool bAdjustCode = !mxGroup || mxGroup->mpTopCell == this;
+
+ if (!pCode->HasReferences() || rDocument.IsClipOrUndo())
+ {
+ aPos.SetTab(nTabNo);
+ return;
+ }
+
+ EndListeningTo(rDocument);
+ ScAddress aOldPos = aPos;
+ // SetTab _after_ EndListeningTo and _before_ Compiler UpdateMoveTab !
+ aPos.SetTab(nTabNo);
+
+ // no StartListeningTo because pTab[nTab] not yet correct!
+
+ if (!bAdjustCode)
+ return;
+
+ sc::RefUpdateResult aRes = pCode->AdjustReferenceOnMovedTab(rCxt, aOldPos);
+ if (aRes.mbNameModified)
+ // Re-compile after sheet(s) have been deleted.
+ bCompile = true;
+}
+
+void ScFormulaCell::UpdateInsertTabAbs(SCTAB nTable)
+{
+ if (rDocument.IsClipOrUndo())
+ return;
+
+ bool bAdjustCode = !mxGroup || mxGroup->mpTopCell == this;
+ if (!bAdjustCode)
+ return;
+
+ formula::FormulaTokenArrayPlainIterator aIter(*pCode);
+ formula::FormulaToken* p = aIter.GetNextReferenceRPN();
+ while (p)
+ {
+ ScSingleRefData& rRef1 = *p->GetSingleRef();
+ if (!rRef1.IsTabRel() && nTable <= rRef1.Tab())
+ rRef1.IncTab(1);
+ if (p->GetType() == formula::svDoubleRef)
+ {
+ ScSingleRefData& rRef2 = p->GetDoubleRef()->Ref2;
+ if (!rRef2.IsTabRel() && nTable <= rRef2.Tab())
+ rRef2.IncTab(1);
+ }
+ p = aIter.GetNextReferenceRPN();
+ }
+}
+
+bool ScFormulaCell::TestTabRefAbs(SCTAB nTable)
+{
+ if (rDocument.IsClipOrUndo())
+ return false;
+
+ bool bAdjustCode = !mxGroup || mxGroup->mpTopCell == this;
+ if (!bAdjustCode)
+ return false;
+
+ bool bRet = false;
+ formula::FormulaTokenArrayPlainIterator aIter(*pCode);
+ formula::FormulaToken* p = aIter.GetNextReferenceRPN();
+ while (p)
+ {
+ ScSingleRefData& rRef1 = *p->GetSingleRef();
+ if (!rRef1.IsTabRel())
+ {
+ if (nTable != rRef1.Tab())
+ bRet = true;
+ else if (nTable != aPos.Tab())
+ rRef1.SetAbsTab(aPos.Tab());
+ }
+ if (p->GetType() == formula::svDoubleRef)
+ {
+ ScSingleRefData& rRef2 = p->GetDoubleRef()->Ref2;
+ if (!rRef2.IsTabRel())
+ {
+ if(nTable != rRef2.Tab())
+ bRet = true;
+ else if (nTable != aPos.Tab())
+ rRef2.SetAbsTab(aPos.Tab());
+ }
+ }
+ p = aIter.GetNextReferenceRPN();
+ }
+ return bRet;
+}
+
+void ScFormulaCell::UpdateCompile( bool bForceIfNameInUse )
+{
+ if ( bForceIfNameInUse && !bCompile )
+ bCompile = pCode->HasNameOrColRowName();
+ if ( bCompile )
+ pCode->SetCodeError( FormulaError::NONE ); // make sure it will really be compiled
+ CompileTokenArray();
+}
+
+static void lcl_TransposeReference(ScSingleRefData& rRef)
+{
+ // References to or over filtered rows are not adjusted
+ // analog to the normal (non-transposed) case
+ SCCOLROW nTemp = rRef.Col();
+ rRef.SetRelCol(rRef.Row());
+ rRef.SetRelRow(nTemp);
+}
+
+// Reference transposition is only called in Clipboard Document
+void ScFormulaCell::TransposeReference()
+{
+ bool bFound = false;
+ formula::FormulaTokenArrayPlainIterator aIter(*pCode);
+ formula::FormulaToken* t;
+ while ( ( t = aIter.GetNextReference() ) != nullptr )
+ {
+ ScSingleRefData& rRef1 = *t->GetSingleRef();
+ if ( rRef1.IsColRel() && rRef1.IsRowRel() )
+ {
+ bool bDouble = (t->GetType() == formula::svDoubleRef);
+ ScSingleRefData& rRef2 = (bDouble ? t->GetDoubleRef()->Ref2 : rRef1);
+ if ( !bDouble || (rRef2.IsColRel() && rRef2.IsRowRel()) )
+ {
+ lcl_TransposeReference(rRef1);
+
+ if ( bDouble )
+ lcl_TransposeReference(rRef2);
+
+ bFound = true;
+ }
+ }
+ }
+
+ if (bFound)
+ bCompile = true;
+}
+
+void ScFormulaCell::UpdateTranspose( const ScRange& rSource, const ScAddress& rDest,
+ ScDocument* pUndoDoc )
+{
+ EndListeningTo( rDocument );
+
+ ScAddress aOldPos = aPos;
+ bool bPosChanged = false; // Whether this cell has been moved
+
+ // Dest range is transposed
+ ScRange aDestRange( rDest, ScAddress(
+ static_cast<SCCOL>(rDest.Col() + rSource.aEnd.Row() - rSource.aStart.Row()),
+ static_cast<SCROW>(rDest.Row() + rSource.aEnd.Col() - rSource.aStart.Col()),
+ rDest.Tab() + rSource.aEnd.Tab() - rSource.aStart.Tab() ) );
+
+ // cell within range
+ if ( aDestRange.Contains( aOldPos ) )
+ {
+ // References of these cells were not changed by ScTokenArray::AdjustReferenceOnMove()
+ // Count back Positions
+ SCCOL nRelPosX = aOldPos.Col();
+ SCROW nRelPosY = aOldPos.Row();
+ SCTAB nRelPosZ = aOldPos.Tab();
+ ScRefUpdate::DoTranspose( nRelPosX, nRelPosY, nRelPosZ, rDocument, aDestRange, rSource.aStart );
+ aOldPos.Set( nRelPosX, nRelPosY, nRelPosZ );
+ bPosChanged = true;
+ }
+
+ std::unique_ptr<ScTokenArray> pOld;
+ if (pUndoDoc)
+ pOld = pCode->Clone();
+ bool bRefChanged = false;
+
+ formula::FormulaTokenArrayPlainIterator aIter(*pCode);
+ formula::FormulaToken* t;
+ while( (t = aIter.GetNextReferenceOrName()) != nullptr )
+ {
+ if( t->GetOpCode() == ocName )
+ {
+ const ScRangeData* pName = rDocument.FindRangeNameBySheetAndIndex( t->GetSheet(), t->GetIndex());
+ if (pName && pName->IsModified())
+ bRefChanged = true;
+ }
+ else if( t->GetType() != svIndex )
+ {
+ SingleDoubleRefModifier aMod(*t);
+ ScComplexRefData& rRef = aMod.Ref();
+ ScRange aAbs = rRef.toAbs(rDocument, aOldPos);
+ bool bMod = (ScRefUpdate::UpdateTranspose(rDocument, rSource, rDest, aAbs) != UR_NOTHING || bPosChanged);
+ if (bMod)
+ {
+ rRef.SetRange(rDocument.GetSheetLimits(), aAbs, aPos); // based on the new anchor position.
+ bRefChanged = true;
+
+ // Absolute sheet reference => set 3D flag.
+ // More than one sheet referenced => has to have both 3D flags.
+ // If end part has 3D flag => start part must have it too.
+ // The same behavior as in ScTokenArray::AdjustReferenceOnMove() is used for 3D-Flags.
+ rRef.Ref2.SetFlag3D(aAbs.aStart.Tab() != aAbs.aEnd.Tab() || !rRef.Ref2.IsTabRel());
+ rRef.Ref1.SetFlag3D(
+ (rSource.aStart.Tab() != rDest.Tab() && !bPosChanged)
+ || !rRef.Ref1.IsTabRel() || rRef.Ref2.IsFlag3D());
+ }
+ }
+ }
+
+ if (bRefChanged)
+ {
+ if (pUndoDoc)
+ {
+ // Similar to setOldCodeToUndo(), but it cannot be used due to the check
+ // pUndoDoc->GetCellType(aPos) == CELLTYPE_FORMULA
+ ScFormulaCell* pFCell = new ScFormulaCell(
+ *pUndoDoc, aPos, pOld ? *pOld : ScTokenArray(*pUndoDoc), eTempGrammar, cMatrixFlag);
+
+ pFCell->aResult.SetToken( nullptr); // to recognize it as changed later (Cut/Paste!)
+ pUndoDoc->SetFormulaCell(aPos, pFCell);
+ }
+
+ bCompile = true;
+ CompileTokenArray(); // also call StartListeningTo
+ SetDirty();
+ }
+ else
+ StartListeningTo( rDocument ); // Listener as previous
+}
+
+void ScFormulaCell::UpdateGrow( const ScRange& rArea, SCCOL nGrowX, SCROW nGrowY )
+{
+ EndListeningTo( rDocument );
+
+ bool bRefChanged = false;
+
+ formula::FormulaTokenArrayPlainIterator aIter(*pCode);
+ formula::FormulaToken* t;
+
+ while( (t = aIter.GetNextReferenceOrName()) != nullptr )
+ {
+ if( t->GetOpCode() == ocName )
+ {
+ const ScRangeData* pName = rDocument.FindRangeNameBySheetAndIndex( t->GetSheet(), t->GetIndex());
+ if (pName && pName->IsModified())
+ bRefChanged = true;
+ }
+ else if( t->GetType() != svIndex )
+ {
+ SingleDoubleRefModifier aMod(*t);
+ ScComplexRefData& rRef = aMod.Ref();
+ ScRange aAbs = rRef.toAbs(rDocument, aPos);
+ bool bMod = (ScRefUpdate::UpdateGrow(rArea, nGrowX, nGrowY, aAbs) != UR_NOTHING);
+ if (bMod)
+ {
+ rRef.SetRange(rDocument.GetSheetLimits(), aAbs, aPos);
+ bRefChanged = true;
+ }
+ }
+ }
+
+ if (bRefChanged)
+ {
+ bCompile = true;
+ CompileTokenArray(); // Also call StartListeningTo
+ SetDirty();
+ }
+ else
+ StartListeningTo( rDocument ); // Listener as previous
+}
+
+// See also ScDocument::FindRangeNamesReferencingSheet()
+static void lcl_FindRangeNamesInUse(sc::UpdatedRangeNames& rIndexes, const ScTokenArray* pCode, const ScDocument& rDoc,
+ int nRecursion)
+{
+ FormulaTokenArrayPlainIterator aIter(*pCode);
+ for (FormulaToken* p = aIter.First(); p; p = aIter.Next())
+ {
+ if (p->GetOpCode() == ocName)
+ {
+ sal_uInt16 nTokenIndex = p->GetIndex();
+ SCTAB nTab = p->GetSheet();
+ rIndexes.setUpdatedName( nTab, nTokenIndex);
+
+ if (nRecursion < 126) // whatever... 42*3
+ {
+ ScRangeData* pSubName = rDoc.FindRangeNameBySheetAndIndex( nTab, nTokenIndex);
+ if (pSubName)
+ lcl_FindRangeNamesInUse(rIndexes, pSubName->GetCode(), rDoc, nRecursion+1);
+ }
+ }
+ }
+}
+
+void ScFormulaCell::FindRangeNamesInUse(sc::UpdatedRangeNames& rIndexes) const
+{
+ lcl_FindRangeNamesInUse( rIndexes, pCode, rDocument, 0);
+}
+
+void ScFormulaCell::SetChanged(bool b)
+{
+ bChanged = b;
+}
+
+void ScFormulaCell::SetCode( std::unique_ptr<ScTokenArray> pNew )
+{
+ assert(!mxGroup); // Don't call this if it's shared.
+ delete pCode;
+ pCode = pNew.release(); // takes ownership.
+}
+
+void ScFormulaCell::SetRunning( bool bVal )
+{
+ bRunning = bVal;
+}
+
+void ScFormulaCell::CompileDBFormula( sc::CompileFormulaContext& rCxt )
+{
+ FormulaTokenArrayPlainIterator aIter(*pCode);
+ for( FormulaToken* p = aIter.First(); p; p = aIter.Next() )
+ {
+ OpCode eOp = p->GetOpCode();
+ if ( eOp == ocDBArea || eOp == ocTableRef )
+ {
+ bCompile = true;
+ CompileTokenArray(rCxt);
+ SetDirty();
+ break;
+ }
+ }
+}
+
+void ScFormulaCell::CompileColRowNameFormula( sc::CompileFormulaContext& rCxt )
+{
+ FormulaTokenArrayPlainIterator aIter(*pCode);
+ for ( FormulaToken* p = aIter.First(); p; p = aIter.Next() )
+ {
+ if ( p->GetOpCode() == ocColRowName )
+ {
+ bCompile = true;
+ CompileTokenArray(rCxt);
+ SetDirty();
+ break;
+ }
+ }
+}
+
+void ScFormulaCell::SetPrevious( ScFormulaCell* pF ) { pPrevious = pF; }
+void ScFormulaCell::SetNext( ScFormulaCell* pF ) { pNext = pF; }
+void ScFormulaCell::SetPreviousTrack( ScFormulaCell* pF ) { pPreviousTrack = pF; }
+void ScFormulaCell::SetNextTrack( ScFormulaCell* pF ) { pNextTrack = pF; }
+
+ScFormulaCellGroupRef ScFormulaCell::CreateCellGroup( SCROW nLen, bool bInvariant )
+{
+ if (mxGroup)
+ {
+ // You can't create a new group if the cell is already a part of a group.
+ // Is this a sign of some inconsistent or incorrect data structures? Or normal?
+ SAL_INFO("sc.opencl", "You can't create a new group if the cell is already a part of a group");
+ return ScFormulaCellGroupRef();
+ }
+
+ mxGroup.reset(new ScFormulaCellGroup);
+ mxGroup->mpTopCell = this;
+ mxGroup->mbInvariant = bInvariant;
+ mxGroup->mnLength = nLen;
+ mxGroup->mpCode = std::move(*pCode); // Move this to the shared location.
+ delete pCode;
+ pCode = &*mxGroup->mpCode;
+ return mxGroup;
+}
+
+void ScFormulaCell::SetCellGroup( const ScFormulaCellGroupRef &xRef )
+{
+ if (!xRef)
+ {
+ // Make this cell a non-grouped cell.
+ if (mxGroup)
+ pCode = mxGroup->mpCode->Clone().release();
+
+ mxGroup = xRef;
+ return;
+ }
+
+ // Group object has shared token array.
+ if (!mxGroup)
+ // Currently not shared. Delete the existing token array first.
+ delete pCode;
+
+ mxGroup = xRef;
+ pCode = &*mxGroup->mpCode;
+ mxGroup->mnWeight = 0; // invalidate
+}
+
+ScFormulaCell::CompareState ScFormulaCell::CompareByTokenArray( const ScFormulaCell& rOther ) const
+{
+ // no Matrix formulae yet.
+ if ( GetMatrixFlag() != ScMatrixMode::NONE )
+ return NotEqual;
+
+ // are these formulas at all similar ?
+ if ( GetHash() != rOther.GetHash() )
+ return NotEqual;
+
+ if (!pCode->IsShareable() || !rOther.pCode->IsShareable())
+ return NotEqual;
+
+ FormulaToken **pThis = pCode->GetCode();
+ sal_uInt16 nThisLen = pCode->GetCodeLen();
+ FormulaToken **pOther = rOther.pCode->GetCode();
+ sal_uInt16 nOtherLen = rOther.pCode->GetCodeLen();
+
+ if ( !pThis || !pOther )
+ {
+ // Error: no compiled code for cells !"
+ return NotEqual;
+ }
+
+ if ( nThisLen != nOtherLen )
+ return NotEqual;
+
+ // No tokens can be an error cell so check error code, otherwise we could
+ // end up with a series of equal error values instead of individual error
+ // values. Also if for any reason different errors are set even if all
+ // tokens are equal, the cells are not equal.
+ if (pCode->GetCodeError() != rOther.pCode->GetCodeError())
+ return NotEqual;
+
+ bool bInvariant = true;
+
+ // check we are basically the same function
+ for ( sal_uInt16 i = 0; i < nThisLen; i++ )
+ {
+ formula::FormulaToken *pThisTok = pThis[i];
+ formula::FormulaToken *pOtherTok = pOther[i];
+
+ if ( pThisTok->GetType() != pOtherTok->GetType() ||
+ pThisTok->GetOpCode() != pOtherTok->GetOpCode() ||
+ pThisTok->GetParamCount() != pOtherTok->GetParamCount() )
+ {
+ // Incompatible type, op-code or param counts.
+ return NotEqual;
+ }
+
+ switch (pThisTok->GetType())
+ {
+ case formula::svMatrix:
+ case formula::svExternalSingleRef:
+ case formula::svExternalDoubleRef:
+ // Ignoring matrix and external references for now.
+ return NotEqual;
+
+ case formula::svSingleRef:
+ {
+ // Single cell reference.
+ const ScSingleRefData& rRef = *pThisTok->GetSingleRef();
+ if (rRef != *pOtherTok->GetSingleRef())
+ return NotEqual;
+
+ if (rRef.IsRowRel())
+ bInvariant = false;
+ }
+ break;
+ case formula::svDoubleRef:
+ {
+ // Range reference.
+ const ScSingleRefData& rRef1 = *pThisTok->GetSingleRef();
+ const ScSingleRefData& rRef2 = *pThisTok->GetSingleRef2();
+ if (rRef1 != *pOtherTok->GetSingleRef())
+ return NotEqual;
+
+ if (rRef2 != *pOtherTok->GetSingleRef2())
+ return NotEqual;
+
+ if (rRef1.IsRowRel())
+ bInvariant = false;
+
+ if (rRef2.IsRowRel())
+ bInvariant = false;
+ }
+ break;
+ case formula::svDouble:
+ {
+ if(!rtl::math::approxEqual(pThisTok->GetDouble(), pOtherTok->GetDouble()))
+ return NotEqual;
+ }
+ break;
+ case formula::svString:
+ {
+ if(pThisTok->GetString() != pOtherTok->GetString())
+ return NotEqual;
+ }
+ break;
+ case formula::svIndex:
+ {
+ if(pThisTok->GetIndex() != pOtherTok->GetIndex() || pThisTok->GetSheet() != pOtherTok->GetSheet())
+ return NotEqual;
+ }
+ break;
+ case formula::svByte:
+ {
+ if(pThisTok->GetByte() != pOtherTok->GetByte())
+ return NotEqual;
+ }
+ break;
+ case formula::svExternal:
+ {
+ if (pThisTok->GetExternal() != pOtherTok->GetExternal())
+ return NotEqual;
+
+ if (pThisTok->GetByte() != pOtherTok->GetByte())
+ return NotEqual;
+ }
+ break;
+ case formula::svError:
+ {
+ if (pThisTok->GetError() != pOtherTok->GetError())
+ return NotEqual;
+ }
+ break;
+ default:
+ ;
+ }
+ }
+
+ // If still the same, check lexical names as different names may result in
+ // identical RPN code.
+
+ pThis = pCode->GetArray();
+ nThisLen = pCode->GetLen();
+ pOther = rOther.pCode->GetArray();
+ nOtherLen = rOther.pCode->GetLen();
+
+ if ( !pThis || !pOther )
+ {
+ // Error: no code for cells !"
+ return NotEqual;
+ }
+
+ if ( nThisLen != nOtherLen )
+ return NotEqual;
+
+ for ( sal_uInt16 i = 0; i < nThisLen; i++ )
+ {
+ formula::FormulaToken *pThisTok = pThis[i];
+ formula::FormulaToken *pOtherTok = pOther[i];
+
+ if ( pThisTok->GetType() != pOtherTok->GetType() ||
+ pThisTok->GetOpCode() != pOtherTok->GetOpCode() ||
+ pThisTok->GetParamCount() != pOtherTok->GetParamCount() )
+ {
+ // Incompatible type, op-code or param counts.
+ return NotEqual;
+ }
+
+ switch (pThisTok->GetType())
+ {
+ // ScCompiler::HandleIIOpCode() may optimize some refs only in RPN code,
+ // resulting in identical RPN references that could lead to creating
+ // a formula group from formulas that should not be merged into a group,
+ // so check also the formula itself.
+ case formula::svSingleRef:
+ {
+ // Single cell reference.
+ const ScSingleRefData& rRef = *pThisTok->GetSingleRef();
+ if (rRef != *pOtherTok->GetSingleRef())
+ return NotEqual;
+
+ if (rRef.IsRowRel())
+ bInvariant = false;
+ }
+ break;
+ case formula::svDoubleRef:
+ {
+ // Range reference.
+ const ScSingleRefData& rRef1 = *pThisTok->GetSingleRef();
+ const ScSingleRefData& rRef2 = *pThisTok->GetSingleRef2();
+ if (rRef1 != *pOtherTok->GetSingleRef())
+ return NotEqual;
+
+ if (rRef2 != *pOtherTok->GetSingleRef2())
+ return NotEqual;
+
+ if (rRef1.IsRowRel())
+ bInvariant = false;
+
+ if (rRef2.IsRowRel())
+ bInvariant = false;
+ }
+ break;
+ // All index tokens are names. Different categories already had
+ // different OpCode values.
+ case formula::svIndex:
+ {
+ if (pThisTok->GetIndex() != pOtherTok->GetIndex())
+ return NotEqual;
+ switch (pThisTok->GetOpCode())
+ {
+ case ocTableRef:
+ // nothing, sheet value assumed as -1, silence
+ // ScTableRefToken::GetSheet() SAL_WARN about
+ // unhandled
+ ;
+ break;
+ default: // ocName, ocDBArea
+ if (pThisTok->GetSheet() != pOtherTok->GetSheet())
+ return NotEqual;
+ }
+ }
+ break;
+ default:
+ ;
+ }
+ }
+
+ return bInvariant ? EqualInvariant : EqualRelativeRef;
+}
+
+namespace {
+
+// Split N into optimally equal-sized pieces, each not larger than K.
+// Return value P is number of pieces. A returns the number of pieces
+// one larger than N/P, 0..P-1.
+
+int splitup(int N, int K, int& A)
+{
+ assert(N > 0);
+ assert(K > 0);
+
+ A = 0;
+
+ if (N <= K)
+ return 1;
+
+ const int ideal_num_parts = N / K;
+ if (ideal_num_parts * K == N)
+ return ideal_num_parts;
+
+ const int num_parts = ideal_num_parts + 1;
+ const int nominal_part_size = N / num_parts;
+
+ A = N - num_parts * nominal_part_size;
+
+ return num_parts;
+}
+
+struct ScDependantsCalculator
+{
+ ScDocument& mrDoc;
+ const ScTokenArray& mrCode;
+ const ScFormulaCellGroupRef& mxGroup;
+ const SCROW mnLen;
+ const ScAddress& mrPos;
+ const bool mFromFirstRow;
+ const SCROW mnStartOffset;
+ const SCROW mnEndOffset;
+ const SCROW mnSpanLen;
+
+ ScDependantsCalculator(ScDocument& rDoc, const ScTokenArray& rCode, const ScFormulaCell& rCell,
+ const ScAddress& rPos, bool fromFirstRow, SCROW nStartOffset, SCROW nEndOffset) :
+ mrDoc(rDoc),
+ mrCode(rCode),
+ mxGroup(rCell.GetCellGroup()),
+ mnLen(mxGroup->mnLength),
+ mrPos(rPos),
+ // ScColumn::FetchVectorRefArray() always fetches data from row 0, even if the data is used
+ // only from further rows. This data fetching could also lead to Interpret() calls, so
+ // in OpenCL mode the formula in practice depends on those cells too.
+ mFromFirstRow(fromFirstRow),
+ mnStartOffset(nStartOffset),
+ mnEndOffset(nEndOffset),
+ mnSpanLen(nEndOffset - nStartOffset + 1)
+ {
+ }
+
+ // FIXME: copy-pasted from ScGroupTokenConverter. factor out somewhere else
+ // (note already modified a bit, mFromFirstRow)
+
+ // I think what this function does is to check whether the relative row reference nRelRow points
+ // to a row that is inside the range of rows covered by the formula group.
+
+ bool isSelfReferenceRelative(const ScAddress& rRefPos, SCROW nRelRow)
+ {
+ if (rRefPos.Col() != mrPos.Col() || rRefPos.Tab() != mrPos.Tab())
+ return false;
+
+ SCROW nEndRow = mrPos.Row() + mnLen - 1;
+
+ if (nRelRow <= 0)
+ {
+ SCROW nTest = nEndRow;
+ nTest += nRelRow;
+ if (nTest >= mrPos.Row())
+ return true;
+ }
+ else
+ {
+ SCROW nTest = mrPos.Row(); // top row.
+ nTest += nRelRow;
+ if (nTest <= nEndRow)
+ return true;
+ // If pointing below the formula, it's always included if going from first row.
+ if (mFromFirstRow)
+ return true;
+ }
+
+ return false;
+ }
+
+ // FIXME: another copy-paste
+
+ // And this correspondingly checks whether an absolute row is inside the range of rows covered
+ // by the formula group.
+
+ bool isSelfReferenceAbsolute(const ScAddress& rRefPos)
+ {
+ if (rRefPos.Col() != mrPos.Col() || rRefPos.Tab() != mrPos.Tab())
+ return false;
+
+ SCROW nEndRow = mrPos.Row() + mnLen - 1;
+
+ if (rRefPos.Row() < mrPos.Row())
+ return false;
+
+ // If pointing below the formula, it's always included if going from first row.
+ if (rRefPos.Row() > nEndRow && !mFromFirstRow)
+ return false;
+
+ return true;
+ }
+
+ // Checks if the doubleref engulfs all of formula group cells
+ // Note : does not check if there is a partial overlap, that can be done by calling
+ // isSelfReference[Absolute|Relative]() on both the start and end of the double ref
+ bool isDoubleRefSpanGroupRange(const ScRange& rAbs, bool bIsRef1RowRel, bool bIsRef2RowRel)
+ {
+ if (rAbs.aStart.Col() > mrPos.Col() || rAbs.aEnd.Col() < mrPos.Col()
+ || rAbs.aStart.Tab() > mrPos.Tab() || rAbs.aEnd.Tab() < mrPos.Tab())
+ {
+ return false;
+ }
+
+ SCROW nStartRow = mrPos.Row();
+ SCROW nEndRow = nStartRow + mnLen - 1;
+ SCROW nRefStartRow = rAbs.aStart.Row();
+ SCROW nRefEndRow = rAbs.aEnd.Row();
+
+ if (bIsRef1RowRel && bIsRef2RowRel &&
+ ((nRefStartRow <= nStartRow && nRefEndRow >= nEndRow) ||
+ ((nRefStartRow + mnLen - 1) <= nStartRow &&
+ (nRefEndRow + mnLen - 1) >= nEndRow)))
+ return true;
+
+ if (!bIsRef1RowRel && nRefStartRow <= nStartRow &&
+ (nRefEndRow >= nEndRow || (nRefEndRow + mnLen - 1) >= nEndRow))
+ return true;
+
+ if (!bIsRef2RowRel &&
+ nRefStartRow <= nStartRow && nRefEndRow >= nEndRow)
+ return true;
+
+ // If going from first row, the referenced range must be entirely above the formula,
+ // otherwise the formula would be included.
+ if (mFromFirstRow && nRefEndRow >= nStartRow)
+ return true;
+
+ return false;
+ }
+
+ // FIXME: another copy-paste
+ SCROW trimLength(SCTAB nTab, SCCOL nCol1, SCCOL nCol2, SCROW nRow, SCROW nRowLen)
+ {
+ SCROW nLastRow = nRow + nRowLen - 1; // current last row.
+ nLastRow = mrDoc.GetLastDataRow(nTab, nCol1, nCol2, nLastRow);
+ if (nLastRow < (nRow + nRowLen - 1))
+ {
+ // This can end up negative! Was that the original intent, or
+ // is it accidental? Was it not like that originally but the
+ // surrounding conditions changed?
+ nRowLen = nLastRow - nRow + 1;
+ // Anyway, let's assume it doesn't make sense to return a
+ // negative or zero value here.
+ if (nRowLen <= 0)
+ nRowLen = 1;
+ }
+ else if (nLastRow == 0)
+ // Column is empty.
+ nRowLen = 1;
+
+ return nRowLen;
+ }
+
+ bool DoIt()
+ {
+ // Partially from ScGroupTokenConverter::convert in sc/source/core/data/grouptokenconverter.cxx
+
+ ScRangeList aRangeList;
+
+ // Self references should be checked by considering the entire formula-group not just the provided span.
+ bool bHasSelfReferences = false;
+ bool bInDocShellRecalc = mrDoc.IsInDocShellRecalc();
+
+ FormulaToken** pRPNArray = mrCode.GetCode();
+ sal_uInt16 nCodeLen = mrCode.GetCodeLen();
+ for (sal_Int32 nTokenIdx = nCodeLen-1; nTokenIdx >= 0; --nTokenIdx)
+ {
+ auto p = pRPNArray[nTokenIdx];
+ if (!bInDocShellRecalc)
+ {
+ // The dependency evaluator evaluates all arguments of IF/IFS/SWITCH irrespective
+ // of the result of the condition expression.
+ // This is a perf problem if we *don't* intent on recalc'ing all dirty cells
+ // in the document. So lets disable threading and stop dependency evaluation if
+ // the call did not originate from ScDocShell::DoRecalc()/ScDocShell::DoHardRecalc()
+ // for formulae with IF/IFS/SWITCH
+ OpCode nOpCode = p->GetOpCode();
+ if (nOpCode == ocIf || nOpCode == ocIfs_MS || nOpCode == ocSwitch_MS)
+ return false;
+ }
+
+ switch (p->GetType())
+ {
+ case svSingleRef:
+ {
+ ScSingleRefData aRef = *p->GetSingleRef(); // =Sheet1!A1
+ if( aRef.IsDeleted())
+ return false;
+ ScAddress aRefPos = aRef.toAbs(mrDoc, mrPos);
+
+ if (!mrDoc.TableExists(aRefPos.Tab()))
+ return false; // or true?
+
+ if (aRef.IsRowRel())
+ {
+ if (isSelfReferenceRelative(aRefPos, aRef.Row()))
+ {
+ bHasSelfReferences = true;
+ continue;
+ }
+
+ // Trim data array length to actual data range.
+ SCROW nTrimLen = trimLength(aRefPos.Tab(), aRefPos.Col(), aRefPos.Col(), aRefPos.Row() + mnStartOffset, mnSpanLen);
+
+ aRangeList.Join(ScRange(aRefPos.Col(), aRefPos.Row() + mnStartOffset, aRefPos.Tab(),
+ aRefPos.Col(), aRefPos.Row() + mnStartOffset + nTrimLen - 1, aRefPos.Tab()));
+ }
+ else
+ {
+ if (isSelfReferenceAbsolute(aRefPos))
+ {
+ bHasSelfReferences = true;
+ continue;
+ }
+
+ aRangeList.Join(ScRange(aRefPos.Col(), aRefPos.Row(), aRefPos.Tab()));
+ }
+ }
+ break;
+ case svDoubleRef:
+ {
+ ScComplexRefData aRef = *p->GetDoubleRef();
+ if( aRef.IsDeleted())
+ return false;
+ ScRange aAbs = aRef.toAbs(mrDoc, mrPos);
+
+ // Multiple sheet
+ if (aRef.Ref1.Tab() != aRef.Ref2.Tab())
+ return false;
+
+ bool bIsRef1RowRel = aRef.Ref1.IsRowRel();
+ // Check for self reference.
+ if (bIsRef1RowRel)
+ {
+ if (isSelfReferenceRelative(aAbs.aStart, aRef.Ref1.Row()))
+ {
+ bHasSelfReferences = true;
+ continue;
+ }
+ }
+ else if (isSelfReferenceAbsolute(aAbs.aStart))
+ {
+ bHasSelfReferences = true;
+ continue;
+ }
+
+ bool bIsRef2RowRel = aRef.Ref2.IsRowRel();
+ if (bIsRef2RowRel)
+ {
+ if (isSelfReferenceRelative(aAbs.aEnd, aRef.Ref2.Row()))
+ {
+ bHasSelfReferences = true;
+ continue;
+ }
+ }
+ else if (isSelfReferenceAbsolute(aAbs.aEnd))
+ {
+ bHasSelfReferences = true;
+ continue;
+ }
+
+ if (isDoubleRefSpanGroupRange(aAbs, bIsRef1RowRel, bIsRef2RowRel))
+ {
+ bHasSelfReferences = true;
+ continue;
+ }
+
+ // The first row that will be referenced through the doubleref.
+ SCROW nFirstRefRow = bIsRef1RowRel ? aAbs.aStart.Row() + mnStartOffset : aAbs.aStart.Row();
+ // The last row that will be referenced through the doubleref.
+ SCROW nLastRefRow = bIsRef2RowRel ? aAbs.aEnd.Row() + mnEndOffset : aAbs.aEnd.Row();
+ // Number of rows to be evaluated from nFirstRefRow.
+ SCROW nArrayLength = nLastRefRow - nFirstRefRow + 1;
+ assert(nArrayLength > 0);
+
+ // Trim trailing empty rows.
+ nArrayLength = trimLength(aAbs.aStart.Tab(), aAbs.aStart.Col(), aAbs.aEnd.Col(), nFirstRefRow, nArrayLength);
+
+ aRangeList.Join(ScRange(aAbs.aStart.Col(), nFirstRefRow, aAbs.aStart.Tab(),
+ aAbs.aEnd.Col(), nFirstRefRow + nArrayLength - 1, aAbs.aEnd.Tab()));
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Compute dependencies irrespective of the presence of any self references.
+ // These dependencies would get computed via InterpretTail anyway when we disable group calc, so lets do it now.
+ // The advantage is that the FG's get marked for cycles early if present, and can avoid lots of complications.
+ for (size_t i = 0; i < aRangeList.size(); ++i)
+ {
+ const ScRange & rRange = aRangeList[i];
+ assert(rRange.aStart.Tab() == rRange.aEnd.Tab());
+ for (auto nCol = rRange.aStart.Col(); nCol <= rRange.aEnd.Col(); nCol++)
+ {
+ SCROW nStartRow = rRange.aStart.Row();
+ SCROW nLength = rRange.aEnd.Row() - rRange.aStart.Row() + 1;
+ if( mFromFirstRow )
+ { // include also all previous rows
+ nLength += nStartRow;
+ nStartRow = 0;
+ }
+ if (!mrDoc.HandleRefArrayForParallelism(ScAddress(nCol, nStartRow, rRange.aStart.Tab()),
+ nLength, mxGroup))
+ return false;
+ }
+ }
+
+ if (bHasSelfReferences)
+ mxGroup->mbPartOfCycle = true;
+
+ return !bHasSelfReferences;
+ }
+};
+
+} // anonymous namespace
+
+bool ScFormulaCell::InterpretFormulaGroup(SCROW nStartOffset, SCROW nEndOffset)
+{
+ if (!mxGroup || !pCode)
+ return false;
+
+ auto aScope = sc::FormulaLogger::get().enterGroup(rDocument, *this);
+ ScRecursionHelper& rRecursionHelper = rDocument.GetRecursionHelper();
+
+ if (mxGroup->mbPartOfCycle)
+ {
+ aScope.addMessage("This formula-group is part of a cycle");
+ return false;
+ }
+
+ if (mxGroup->meCalcState == sc::GroupCalcDisabled)
+ {
+ static constexpr OUStringLiteral MESSAGE = u"group calc disabled";
+ aScope.addMessage(MESSAGE);
+ return false;
+ }
+
+ // Use SC_FORCE_CALCULATION=opencl/threads to force calculation e.g. for unittests
+ static ForceCalculationType forceType = ScCalcConfig::getForceCalculationType();
+ if (forceType == ForceCalculationCore
+ || ( GetWeight() < ScInterpreter::GetGlobalConfig().mnOpenCLMinimumFormulaGroupSize
+ && forceType != ForceCalculationOpenCL
+ && forceType != ForceCalculationThreads))
+ {
+ mxGroup->meCalcState = sc::GroupCalcDisabled;
+ aScope.addGroupSizeThresholdMessage(*this);
+ return false;
+ }
+
+ if (cMatrixFlag != ScMatrixMode::NONE)
+ {
+ mxGroup->meCalcState = sc::GroupCalcDisabled;
+ aScope.addMessage("matrix skipped");
+ return false;
+ }
+
+ if( forceType != ForceCalculationNone )
+ {
+ // ScConditionEntry::Interpret() creates a temporary cell and interprets it
+ // without it actually being in the document at the specified position.
+ // That would confuse opencl/threading code, as they refer to the cell group
+ // also using the position. This is normally not triggered (single cells
+ // are normally not in a cell group), but if forced, check for this explicitly.
+ if( rDocument.GetFormulaCell( aPos ) != this )
+ {
+ mxGroup->meCalcState = sc::GroupCalcDisabled;
+ aScope.addMessage("cell not in document");
+ return false;
+ }
+ }
+
+ // Get rid of -1's in offsets (defaults) or any invalid offsets.
+ SCROW nMaxOffset = mxGroup->mnLength - 1;
+ nStartOffset = nStartOffset < 0 ? 0 : std::min(nStartOffset, nMaxOffset);
+ nEndOffset = nEndOffset < 0 ? nMaxOffset : std::min(nEndOffset, nMaxOffset);
+
+ if (nEndOffset < nStartOffset)
+ {
+ nStartOffset = 0;
+ nEndOffset = nMaxOffset;
+ }
+
+ if (nEndOffset == nStartOffset)
+ return false; // Do not use threads for a single row.
+
+ // Guard against endless recursion of Interpret() calls, for this to work
+ // ScFormulaCell::InterpretFormulaGroup() must never be called through
+ // anything else than ScFormulaCell::Interpret(), same as
+ // ScFormulaCell::InterpretTail()
+ RecursionCounter aRecursionCounter( rRecursionHelper, this);
+
+ bool bDependencyComputed = false;
+ bool bDependencyCheckFailed = false;
+
+ // Preference order: First try OpenCL, then threading.
+ // TODO: Do formula-group span computation for OCL too if nStartOffset/nEndOffset are non default.
+ if( InterpretFormulaGroupOpenCL(aScope, bDependencyComputed, bDependencyCheckFailed))
+ return true;
+
+ if( InterpretFormulaGroupThreading(aScope, bDependencyComputed, bDependencyCheckFailed, nStartOffset, nEndOffset))
+ return true;
+
+ return false;
+}
+
+bool ScFormulaCell::CheckComputeDependencies(sc::FormulaLogger::GroupScope& rScope, bool fromFirstRow,
+ SCROW nStartOffset, SCROW nEndOffset,
+ bool bCalcDependencyOnly)
+{
+ ScRecursionHelper& rRecursionHelper = rDocument.GetRecursionHelper();
+ // iterate over code in the formula ...
+ // ensure all input is pre-calculated -
+ // to avoid writing during the calculation
+ if (bCalcDependencyOnly)
+ {
+ // Lets not use "ScFormulaGroupDependencyComputeGuard" here as there is no corresponding
+ // "ScFormulaGroupCycleCheckGuard" for this formula-group.
+ // (We can only reach here from a multi-group dependency evaluation attempt).
+ // (These two have to be in pairs always for any given formula-group)
+ ScDependantsCalculator aCalculator(rDocument, *pCode, *this, mxGroup->mpTopCell->aPos, fromFirstRow, nStartOffset, nEndOffset);
+ return aCalculator.DoIt();
+ }
+
+ bool bOKToParallelize = false;
+ {
+ ScFormulaGroupCycleCheckGuard aCycleCheckGuard(rRecursionHelper, this);
+ if (mxGroup->mbPartOfCycle)
+ {
+ mxGroup->meCalcState = sc::GroupCalcDisabled;
+ rScope.addMessage("found circular formula-group dependencies");
+ return false;
+ }
+
+ ScFormulaGroupDependencyComputeGuard aDepComputeGuard(rRecursionHelper);
+ ScDependantsCalculator aCalculator(rDocument, *pCode, *this, mxGroup->mpTopCell->aPos, fromFirstRow, nStartOffset, nEndOffset);
+ bOKToParallelize = aCalculator.DoIt();
+
+ }
+
+ if (rRecursionHelper.IsInRecursionReturn())
+ {
+ mxGroup->meCalcState = sc::GroupCalcDisabled;
+ rScope.addMessage("Recursion limit reached, cannot thread this formula group now");
+ return false;
+ }
+
+ if (mxGroup->mbPartOfCycle)
+ {
+ mxGroup->meCalcState = sc::GroupCalcDisabled;
+ rScope.addMessage("found circular formula-group dependencies");
+ return false;
+ }
+
+ if (!rRecursionHelper.AreGroupsIndependent())
+ {
+ // This call resulted from a dependency calculation for a multigroup-threading attempt,
+ // but found dependency among the groups.
+ rScope.addMessage("multi-group-dependency failed");
+ return false;
+ }
+
+ if (!bOKToParallelize)
+ {
+ mxGroup->meCalcState = sc::GroupCalcDisabled;
+ rScope.addMessage("could not do new dependencies calculation thing");
+ return false;
+ }
+
+ return true;
+}
+
+static SCCOL lcl_probeLeftOrRightFGs(const ScFormulaCellGroupRef& xGroup, const ScDocument& rDoc,
+ o3tl::sorted_vector<ScFormulaCellGroup*>& rFGSet,
+ std::map<SCCOL, ScFormulaCell*>& rFGMap, bool bLeft)
+{
+ const SCROW nLen = xGroup->mnLength;
+ const sal_Int32 nWt = xGroup->mnWeight;
+ ScAddress aAddr(xGroup->mpTopCell->aPos);
+
+ SCCOL nColRet = aAddr.Col();
+
+ const SCCOL nMaxCol = rDoc.GetAllocatedColumnsCount(aAddr.Tab()) - 1;
+ if (bLeft)
+ --nColRet;
+ else
+ ++nColRet;
+
+ while (nColRet >= 0 && nColRet <= nMaxCol)
+ {
+ aAddr.SetCol(nColRet);
+ const ScFormulaCell* pCell = rDoc.GetFormulaCell(aAddr);
+ if (!pCell)
+ break;
+
+ if (!pCell->NeedsInterpret())
+ break;
+
+ const ScFormulaCellGroupRef& xNGroup = pCell->GetCellGroup();
+ if (!xNGroup)
+ break;
+
+ if (!pCell->GetCode()->IsEnabledForThreading())
+ break;
+
+ if (xNGroup->mpTopCell->aPos.Row() != aAddr.Row())
+ break;
+
+ const SCROW nNLen = xNGroup->mnLength;
+ const sal_Int32 nNWt = pCell->GetWeight();
+ if (nNLen != nLen || nNWt != nWt)
+ break;
+
+ rFGSet.insert(xNGroup.get());
+ rFGMap[nColRet] = xNGroup->mpTopCell;
+
+ if (bLeft)
+ --nColRet;
+ else
+ ++nColRet;
+ }
+
+ if (bLeft)
+ ++nColRet;
+ else
+ --nColRet;
+
+ return nColRet;
+}
+
+// To be called only from InterpretFormulaGroup().
+bool ScFormulaCell::InterpretFormulaGroupThreading(sc::FormulaLogger::GroupScope& aScope,
+ bool& bDependencyComputed,
+ bool& bDependencyCheckFailed,
+ SCROW nStartOffset,
+ SCROW nEndOffset)
+{
+ static const bool bThreadingProhibited = std::getenv("SC_NO_THREADED_CALCULATION");
+ if (!bDependencyCheckFailed && !bThreadingProhibited &&
+ pCode->IsEnabledForThreading() &&
+ ScCalcConfig::isThreadingEnabled())
+ {
+ if(!bDependencyComputed && !CheckComputeDependencies(aScope, false, nStartOffset, nEndOffset))
+ {
+ bDependencyComputed = true;
+ bDependencyCheckFailed = true;
+ return false;
+ }
+
+ bDependencyComputed = true;
+
+ // Then do the threaded calculation
+
+ class Executor : public comphelper::ThreadTask
+ {
+ private:
+ const unsigned mnThisThread;
+ const unsigned mnThreadsTotal;
+ ScDocument* mpDocument;
+ ScInterpreterContext* mpContext;
+ const ScAddress& mrTopPos;
+ SCCOL mnStartCol;
+ SCCOL mnEndCol;
+ SCROW mnStartOffset;
+ SCROW mnEndOffset;
+
+ public:
+ Executor(const std::shared_ptr<comphelper::ThreadTaskTag>& rTag,
+ unsigned nThisThread,
+ unsigned nThreadsTotal,
+ ScDocument* pDocument2,
+ ScInterpreterContext* pContext,
+ const ScAddress& rTopPos,
+ SCCOL nStartCol,
+ SCCOL nEndCol,
+ SCROW nStartOff,
+ SCROW nEndOff) :
+ comphelper::ThreadTask(rTag),
+ mnThisThread(nThisThread),
+ mnThreadsTotal(nThreadsTotal),
+ mpDocument(pDocument2),
+ mpContext(pContext),
+ mrTopPos(rTopPos),
+ mnStartCol(nStartCol),
+ mnEndCol(nEndCol),
+ mnStartOffset(nStartOff),
+ mnEndOffset(nEndOff)
+ {
+ }
+
+ virtual void doWork() override
+ {
+ ScRange aCalcRange(mnStartCol, mrTopPos.Row() + mnStartOffset, mrTopPos.Tab(),
+ mnEndCol, mrTopPos.Row() + mnEndOffset, mrTopPos.Tab());
+ mpDocument->CalculateInColumnInThread(*mpContext, aCalcRange, mnThisThread, mnThreadsTotal);
+ }
+
+ };
+
+ SvNumberFormatter* pNonThreadedFormatter = rDocument.GetNonThreadedContext().GetFormatTable();
+
+ comphelper::ThreadPool& rThreadPool(comphelper::ThreadPool::getSharedOptimalPool());
+ sal_Int32 nThreadCount = rThreadPool.getWorkerCount();
+
+ SAL_INFO("sc.threaded", "Running " << nThreadCount << " threads");
+
+ o3tl::sorted_vector<ScFormulaCellGroup*> aFGSet;
+ std::map<SCCOL, ScFormulaCell*> aFGMap;
+ aFGSet.insert(mxGroup.get());
+
+ ScRecursionHelper& rRecursionHelper = rDocument.GetRecursionHelper();
+ SCCOL nColStart = aPos.Col();
+ SCCOL nColEnd = nColStart;
+ if (!rRecursionHelper.HasFormulaGroupSet() && rDocument.IsInDocShellRecalc())
+ {
+ nColStart = lcl_probeLeftOrRightFGs(mxGroup, rDocument, aFGSet, aFGMap, true);
+ nColEnd = lcl_probeLeftOrRightFGs(mxGroup, rDocument, aFGSet, aFGMap, false);
+ }
+
+ if (nColStart != nColEnd)
+ {
+ ScCheckIndependentFGGuard aGuard(rRecursionHelper, &aFGSet);
+ for (SCCOL nCurrCol = nColStart; nCurrCol <= nColEnd; ++nCurrCol)
+ {
+ if (nCurrCol == aPos.Col())
+ continue;
+
+ bool bFGOK = aFGMap[nCurrCol]->CheckComputeDependencies(aScope, false, nStartOffset, nEndOffset, true);
+ if (!bFGOK || !aGuard.AreGroupsIndependent())
+ {
+ nColEnd = nColStart = aPos.Col();
+ break;
+ }
+ }
+ }
+
+ std::vector<std::unique_ptr<ScInterpreter>> aInterpreters(nThreadCount);
+ {
+ assert(!rDocument.IsThreadedGroupCalcInProgress());
+ rDocument.SetThreadedGroupCalcInProgress(true);
+
+ ScMutationDisable aGuard(rDocument, ScMutationGuardFlags::CORE);
+
+ // Start nThreadCount new threads
+ std::shared_ptr<comphelper::ThreadTaskTag> aTag = comphelper::ThreadPool::createThreadTaskTag();
+ ScThreadedInterpreterContextGetterGuard aContextGetterGuard(nThreadCount, rDocument, pNonThreadedFormatter);
+ ScInterpreterContext* context = nullptr;
+
+ for (int i = 0; i < nThreadCount; ++i)
+ {
+ context = aContextGetterGuard.GetInterpreterContextForThreadIdx(i);
+ assert(!context->pInterpreter);
+ aInterpreters[i].reset(new ScInterpreter(this, rDocument, *context, mxGroup->mpTopCell->aPos, *pCode, true));
+ context->pInterpreter = aInterpreters[i].get();
+ rDocument.SetupContextFromNonThreadedContext(*context, i);
+ rThreadPool.pushTask(std::make_unique<Executor>(aTag, i, nThreadCount, &rDocument, context, mxGroup->mpTopCell->aPos,
+ nColStart, nColEnd, nStartOffset, nEndOffset));
+ }
+
+ SAL_INFO("sc.threaded", "Waiting for threads to finish work");
+ // Do not join the threads here. They will get joined in ScDocument destructor
+ // if they don't get joined from elsewhere before (via ThreadPool::waitUntilDone).
+ rThreadPool.waitUntilDone(aTag, false);
+
+ rDocument.SetThreadedGroupCalcInProgress(false);
+
+ for (int i = 0; i < nThreadCount; ++i)
+ {
+ context = aContextGetterGuard.GetInterpreterContextForThreadIdx(i);
+ // This is intentionally done in this main thread in order to avoid locking.
+ rDocument.MergeContextBackIntoNonThreadedContext(*context, i);
+ context->pInterpreter = nullptr;
+ }
+
+ SAL_INFO("sc.threaded", "Done");
+ }
+
+ ScAddress aStartPos(mxGroup->mpTopCell->aPos);
+ SCROW nSpanLen = nEndOffset - nStartOffset + 1;
+ aStartPos.SetRow(aStartPos.Row() + nStartOffset);
+ // Reuse one of the previously allocated interpreter objects here.
+ rDocument.HandleStuffAfterParallelCalculation(nColStart, nColEnd, aStartPos.Row(), nSpanLen,
+ aStartPos.Tab(), aInterpreters[0].get());
+
+ return true;
+ }
+
+ return false;
+}
+
+// To be called only from InterpretFormulaGroup().
+bool ScFormulaCell::InterpretFormulaGroupOpenCL(sc::FormulaLogger::GroupScope& aScope,
+ bool& bDependencyComputed,
+ bool& bDependencyCheckFailed)
+{
+ bool bCanVectorize = pCode->IsEnabledForOpenCL();
+ switch (pCode->GetVectorState())
+ {
+ case FormulaVectorEnabled:
+ case FormulaVectorCheckReference:
+ break;
+
+ // Not good.
+ case FormulaVectorDisabledByOpCode:
+ aScope.addMessage("group calc disabled due to vector state (non-vector-supporting opcode)");
+ break;
+ case FormulaVectorDisabledByStackVariable:
+ aScope.addMessage("group calc disabled due to vector state (non-vector-supporting stack variable)");
+ break;
+ case FormulaVectorDisabledNotInSubSet:
+ aScope.addMessage("group calc disabled due to vector state (opcode not in subset)");
+ break;
+ case FormulaVectorDisabled:
+ case FormulaVectorUnknown:
+ default:
+ aScope.addMessage("group calc disabled due to vector state (unknown)");
+ return false;
+ }
+
+ if (!bCanVectorize)
+ return false;
+
+ if (!ScCalcConfig::isOpenCLEnabled())
+ {
+ aScope.addMessage("opencl not enabled");
+ return false;
+ }
+
+ // TableOp does tricks with using a cell with different values, just bail out.
+ if(rDocument.IsInInterpreterTableOp())
+ return false;
+
+ if (bDependencyCheckFailed)
+ return false;
+
+ if(!bDependencyComputed && !CheckComputeDependencies(aScope, true, 0, mxGroup->mnLength - 1))
+ {
+ bDependencyComputed = true;
+ bDependencyCheckFailed = true;
+ return false;
+ }
+
+ bDependencyComputed = true;
+
+ // TODO : Disable invariant formula group interpretation for now in order
+ // to get implicit intersection to work.
+ if (mxGroup->mbInvariant && false)
+ return InterpretInvariantFormulaGroup();
+
+ int nMaxGroupLength = INT_MAX;
+
+#ifdef _WIN32
+ // Heuristic: Certain old low-end OpenCL implementations don't
+ // work for us with too large group lengths. 1000 was determined
+ // empirically to be a good compromise.
+ if (openclwrapper::gpuEnv.mbNeedsTDRAvoidance)
+ nMaxGroupLength = 1000;
+#endif
+
+ if (std::getenv("SC_MAX_GROUP_LENGTH"))
+ nMaxGroupLength = std::atoi(std::getenv("SC_MAX_GROUP_LENGTH"));
+
+ int nNumOnePlus;
+ const int nNumParts = splitup(GetSharedLength(), nMaxGroupLength, nNumOnePlus);
+
+ int nOffset = 0;
+ int nCurChunkSize;
+ ScAddress aOrigPos = mxGroup->mpTopCell->aPos;
+ for (int i = 0; i < nNumParts; i++, nOffset += nCurChunkSize)
+ {
+ nCurChunkSize = GetSharedLength()/nNumParts + (i < nNumOnePlus ? 1 : 0);
+
+ ScFormulaCellGroupRef xGroup;
+
+ if (nNumParts == 1)
+ xGroup = mxGroup;
+ else
+ {
+ // Ugly hack
+ xGroup = new ScFormulaCellGroup();
+ xGroup->mpTopCell = mxGroup->mpTopCell;
+ xGroup->mpTopCell->aPos = aOrigPos;
+ xGroup->mpTopCell->aPos.IncRow(nOffset);
+ xGroup->mbInvariant = mxGroup->mbInvariant;
+ xGroup->mnLength = nCurChunkSize;
+ xGroup->mpCode = std::move(mxGroup->mpCode); // temporarily transfer
+ }
+
+ ScTokenArray aCode(rDocument);
+ ScGroupTokenConverter aConverter(aCode, rDocument, *this, xGroup->mpTopCell->aPos);
+ // TODO avoid this extra compilation
+ ScCompiler aComp( rDocument, xGroup->mpTopCell->aPos, *pCode, formula::FormulaGrammar::GRAM_UNSPECIFIED, true, cMatrixFlag != ScMatrixMode::NONE );
+ aComp.CompileTokenArray();
+ if (aComp.HasUnhandledPossibleImplicitIntersections() || !aConverter.convert(*pCode, aScope))
+ {
+ if(aComp.HasUnhandledPossibleImplicitIntersections())
+ {
+ SAL_INFO("sc.opencl", "group " << xGroup->mpTopCell->aPos << " has unhandled implicit intersections, disabling");
+#ifdef DBG_UTIL
+ for( const OpCode opcode : aComp.UnhandledPossibleImplicitIntersectionsOpCodes())
+ {
+ SAL_INFO("sc.opencl", "unhandled implicit intersection opcode "
+ << formula::FormulaCompiler().GetOpCodeMap(com::sun::star::sheet::FormulaLanguage::ENGLISH)->getSymbol(opcode)
+ << "(" << int(opcode) << ")");
+ }
+#endif
+ }
+ else
+ SAL_INFO("sc.opencl", "conversion of group " << xGroup->mpTopCell->aPos << " failed, disabling");
+
+ mxGroup->meCalcState = sc::GroupCalcDisabled;
+
+ // Undo the hack above
+ if (nNumParts > 1)
+ {
+ mxGroup->mpTopCell->aPos = aOrigPos;
+ xGroup->mpTopCell = nullptr;
+ mxGroup->mpCode = std::move(xGroup->mpCode);
+ }
+
+ aScope.addMessage("group token conversion failed");
+ return false;
+ }
+
+ // The converted code does not have RPN tokens yet. The interpreter will
+ // generate them.
+ xGroup->meCalcState = mxGroup->meCalcState = sc::GroupCalcRunning;
+ sc::FormulaGroupInterpreter *pInterpreter = sc::FormulaGroupInterpreter::getStatic();
+
+ if (pInterpreter == nullptr ||
+ !pInterpreter->interpret(rDocument, xGroup->mpTopCell->aPos, xGroup, aCode))
+ {
+ SAL_INFO("sc.opencl", "interpreting group " << mxGroup->mpTopCell->aPos
+ << " (state " << static_cast<int>(mxGroup->meCalcState) << ") failed, disabling");
+ mxGroup->meCalcState = sc::GroupCalcDisabled;
+
+ // Undo the hack above
+ if (nNumParts > 1)
+ {
+ mxGroup->mpTopCell->aPos = aOrigPos;
+ xGroup->mpTopCell = nullptr;
+ mxGroup->mpCode = std::move(xGroup->mpCode);
+ }
+
+ aScope.addMessage("group interpretation unsuccessful");
+ return false;
+ }
+
+ aScope.setCalcComplete();
+
+ if (nNumParts > 1)
+ {
+ xGroup->mpTopCell = nullptr;
+ mxGroup->mpCode = std::move(xGroup->mpCode);
+ }
+ }
+
+ if (nNumParts > 1)
+ mxGroup->mpTopCell->aPos = aOrigPos;
+ mxGroup->meCalcState = sc::GroupCalcEnabled;
+ return true;
+}
+
+bool ScFormulaCell::InterpretInvariantFormulaGroup()
+{
+ if (pCode->GetVectorState() == FormulaVectorCheckReference)
+ {
+ // An invariant group should only have absolute row references, and no
+ // external references are allowed.
+
+ ScTokenArray aCode(rDocument);
+ FormulaTokenArrayPlainIterator aIter(*pCode);
+ for (const formula::FormulaToken* p = aIter.First(); p; p = aIter.Next())
+ {
+ switch (p->GetType())
+ {
+ case svSingleRef:
+ {
+ ScSingleRefData aRef = *p->GetSingleRef();
+ ScAddress aRefPos = aRef.toAbs(rDocument, aPos);
+ formula::FormulaTokenRef pNewToken = rDocument.ResolveStaticReference(aRefPos);
+ if (!pNewToken)
+ return false;
+
+ aCode.AddToken(*pNewToken);
+ }
+ break;
+ case svDoubleRef:
+ {
+ ScComplexRefData aRef = *p->GetDoubleRef();
+ ScRange aRefRange = aRef.toAbs(rDocument, aPos);
+ formula::FormulaTokenRef pNewToken = rDocument.ResolveStaticReference(aRefRange);
+ if (!pNewToken)
+ return false;
+
+ aCode.AddToken(*pNewToken);
+ }
+ break;
+ default:
+ aCode.AddToken(*p);
+ }
+ }
+
+ ScCompiler aComp(rDocument, aPos, aCode, rDocument.GetGrammar(), true, cMatrixFlag != ScMatrixMode::NONE);
+ aComp.CompileTokenArray(); // Create RPN token array.
+ ScInterpreter aInterpreter(this, rDocument, rDocument.GetNonThreadedContext(), aPos, aCode);
+ aInterpreter.Interpret();
+ aResult.SetToken(aInterpreter.GetResultToken().get());
+ }
+ else
+ {
+ // Formula contains no references.
+ ScInterpreter aInterpreter(this, rDocument, rDocument.GetNonThreadedContext(), aPos, *pCode);
+ aInterpreter.Interpret();
+ aResult.SetToken(aInterpreter.GetResultToken().get());
+ }
+
+ for ( sal_Int32 i = 0; i < mxGroup->mnLength; i++ )
+ {
+ ScAddress aTmpPos = aPos;
+ aTmpPos.SetRow(mxGroup->mpTopCell->aPos.Row() + i);
+ ScFormulaCell* pCell = rDocument.GetFormulaCell(aTmpPos);
+ if (!pCell)
+ {
+ SAL_WARN("sc.core.formulacell", "GetFormulaCell not found");
+ continue;
+ }
+
+ // FIXME: this set of horrors is unclear to me ... certainly
+ // the above GetCell is profoundly nasty & slow ...
+ // Ensure the cell truly has a result:
+ pCell->aResult = aResult;
+ pCell->ResetDirty();
+ pCell->SetChanged(true);
+ }
+
+ return true;
+}
+
+namespace {
+
+void startListeningArea(
+ ScFormulaCell* pCell, ScDocument& rDoc, const ScAddress& rPos, const formula::FormulaToken& rToken)
+{
+ const ScSingleRefData& rRef1 = *rToken.GetSingleRef();
+ const ScSingleRefData& rRef2 = *rToken.GetSingleRef2();
+ ScAddress aCell1 = rRef1.toAbs(rDoc, rPos);
+ ScAddress aCell2 = rRef2.toAbs(rDoc, rPos);
+ if (!(aCell1.IsValid() && aCell2.IsValid()))
+ return;
+
+ if (rToken.GetOpCode() == ocColRowNameAuto)
+ { // automagically
+ if ( rRef1.IsColRel() )
+ { // ColName
+ aCell2.SetRow(rDoc.MaxRow());
+ }
+ else
+ { // RowName
+ aCell2.SetCol(rDoc.MaxCol());
+ }
+ }
+ rDoc.StartListeningArea(ScRange(aCell1, aCell2), false, pCell);
+}
+
+}
+
+void ScFormulaCell::StartListeningTo( ScDocument& rDoc )
+{
+ if (mxGroup)
+ mxGroup->endAllGroupListening(rDoc);
+
+ if (rDoc.IsClipOrUndo() || rDoc.GetNoListening() || IsInChangeTrack())
+ return;
+
+ rDoc.SetDetectiveDirty(true); // It has changed something
+
+ ScTokenArray* pArr = GetCode();
+ if( pArr->IsRecalcModeAlways() )
+ {
+ rDoc.StartListeningArea(BCA_LISTEN_ALWAYS, false, this);
+ SetNeedsListening( false);
+ return;
+ }
+
+ formula::FormulaTokenArrayPlainIterator aIter(*pArr);
+ formula::FormulaToken* t;
+ while ( ( t = aIter.GetNextReferenceRPN() ) != nullptr )
+ {
+ switch (t->GetType())
+ {
+ case svSingleRef:
+ {
+ ScAddress aCell = t->GetSingleRef()->toAbs(rDocument, aPos);
+ if (aCell.IsValid())
+ rDoc.StartListeningCell(aCell, this);
+ }
+ break;
+ case svDoubleRef:
+ startListeningArea(this, rDoc, aPos, *t);
+ break;
+ default:
+ ; // nothing
+ }
+ }
+ SetNeedsListening( false);
+}
+
+void ScFormulaCell::StartListeningTo( sc::StartListeningContext& rCxt )
+{
+ ScDocument& rDoc = rCxt.getDoc();
+
+ if (mxGroup)
+ mxGroup->endAllGroupListening(rDoc);
+
+ if (rDoc.IsClipOrUndo() || rDoc.GetNoListening() || IsInChangeTrack())
+ return;
+
+ rDoc.SetDetectiveDirty(true); // It has changed something
+
+ ScTokenArray* pArr = GetCode();
+ if( pArr->IsRecalcModeAlways() )
+ {
+ rDoc.StartListeningArea(BCA_LISTEN_ALWAYS, false, this);
+ SetNeedsListening( false);
+ return;
+ }
+
+ formula::FormulaTokenArrayPlainIterator aIter(*pArr);
+ formula::FormulaToken* t;
+ while ( ( t = aIter.GetNextReferenceRPN() ) != nullptr )
+ {
+ switch (t->GetType())
+ {
+ case svSingleRef:
+ {
+ ScAddress aCell = t->GetSingleRef()->toAbs(rDocument, aPos);
+ if (aCell.IsValid())
+ rDoc.StartListeningCell(rCxt, aCell, *this);
+ }
+ break;
+ case svDoubleRef:
+ startListeningArea(this, rDoc, aPos, *t);
+ break;
+ default:
+ ; // nothing
+ }
+ }
+ SetNeedsListening( false);
+}
+
+namespace {
+
+void endListeningArea(
+ ScFormulaCell* pCell, ScDocument& rDoc, const ScAddress& rPos, const formula::FormulaToken& rToken)
+{
+ const ScSingleRefData& rRef1 = *rToken.GetSingleRef();
+ const ScSingleRefData& rRef2 = *rToken.GetSingleRef2();
+ ScAddress aCell1 = rRef1.toAbs(rDoc, rPos);
+ ScAddress aCell2 = rRef2.toAbs(rDoc, rPos);
+ if (!(aCell1.IsValid() && aCell2.IsValid()))
+ return;
+
+ if (rToken.GetOpCode() == ocColRowNameAuto)
+ { // automagically
+ if ( rRef1.IsColRel() )
+ { // ColName
+ aCell2.SetRow(rDoc.MaxRow());
+ }
+ else
+ { // RowName
+ aCell2.SetCol(rDoc.MaxCol());
+ }
+ }
+
+ rDoc.EndListeningArea(ScRange(aCell1, aCell2), false, pCell);
+}
+
+}
+
+void ScFormulaCell::EndListeningTo( ScDocument& rDoc, ScTokenArray* pArr,
+ ScAddress aCellPos )
+{
+ if (mxGroup)
+ mxGroup->endAllGroupListening(rDoc);
+
+ if (rDoc.IsClipOrUndo() || IsInChangeTrack())
+ return;
+
+ if (!HasBroadcaster())
+ return;
+
+ rDoc.SetDetectiveDirty(true); // It has changed something
+
+ if ( GetCode()->IsRecalcModeAlways() )
+ {
+ rDoc.EndListeningArea(BCA_LISTEN_ALWAYS, false, this);
+ return;
+ }
+
+ if (!pArr)
+ {
+ pArr = GetCode();
+ aCellPos = aPos;
+ }
+ formula::FormulaTokenArrayPlainIterator aIter(*pArr);
+ formula::FormulaToken* t;
+ while ( ( t = aIter.GetNextReferenceRPN() ) != nullptr )
+ {
+ switch (t->GetType())
+ {
+ case svSingleRef:
+ {
+ ScAddress aCell = t->GetSingleRef()->toAbs(rDocument, aCellPos);
+ if (aCell.IsValid())
+ rDoc.EndListeningCell(aCell, this);
+ }
+ break;
+ case svDoubleRef:
+ endListeningArea(this, rDoc, aCellPos, *t);
+ break;
+ default:
+ ; // nothing
+ }
+ }
+}
+
+void ScFormulaCell::EndListeningTo( sc::EndListeningContext& rCxt )
+{
+ if (mxGroup)
+ mxGroup->endAllGroupListening(rCxt.getDoc());
+
+ if (rCxt.getDoc().IsClipOrUndo() || IsInChangeTrack())
+ return;
+
+ if (!HasBroadcaster())
+ return;
+
+ ScDocument& rDoc = rCxt.getDoc();
+ rDoc.SetDetectiveDirty(true); // It has changed something
+
+ ScTokenArray* pArr = rCxt.getOldCode();
+ ScAddress aCellPos = rCxt.getOldPosition(aPos);
+ if (!pArr)
+ pArr = pCode;
+
+ if (pArr->IsRecalcModeAlways())
+ {
+ rDoc.EndListeningArea(BCA_LISTEN_ALWAYS, false, this);
+ return;
+ }
+
+ formula::FormulaTokenArrayPlainIterator aIter(*pArr);
+ formula::FormulaToken* t;
+ while ( ( t = aIter.GetNextReferenceRPN() ) != nullptr )
+ {
+ switch (t->GetType())
+ {
+ case svSingleRef:
+ {
+ ScAddress aCell = t->GetSingleRef()->toAbs(rDocument, aCellPos);
+ if (aCell.IsValid())
+ rDoc.EndListeningCell(rCxt, aCell, *this);
+ }
+ break;
+ case svDoubleRef:
+ endListeningArea(this, rDoc, aCellPos, *t);
+ break;
+ default:
+ ; // nothing
+ }
+ }
+}
+
+bool ScFormulaCell::IsShared() const
+{
+ return bool(mxGroup);
+}
+
+bool ScFormulaCell::IsSharedTop() const
+{
+ if (!mxGroup)
+ return false;
+
+ return mxGroup->mpTopCell == this;
+}
+
+SCROW ScFormulaCell::GetSharedTopRow() const
+{
+ return mxGroup ? mxGroup->mpTopCell->aPos.Row() : -1;
+}
+
+SCROW ScFormulaCell::GetSharedLength() const
+{
+ return mxGroup ? mxGroup->mnLength : 0;
+}
+
+sal_Int32 ScFormulaCell::GetWeight() const
+{
+ if (!mxGroup)
+ return 1;
+
+ if (mxGroup->mnWeight > 0)
+ return mxGroup->mnWeight;
+
+ double nSharedCodeWeight = GetSharedCode()->GetWeight();
+ double nResult = nSharedCodeWeight * GetSharedLength();
+ if (nResult < SAL_MAX_INT32)
+ mxGroup->mnWeight = nResult;
+ else
+ mxGroup->mnWeight = SAL_MAX_INT32;
+
+ return mxGroup->mnWeight;
+}
+
+ScTokenArray* ScFormulaCell::GetSharedCode()
+{
+ return mxGroup ? &*mxGroup->mpCode : nullptr;
+}
+
+const ScTokenArray* ScFormulaCell::GetSharedCode() const
+{
+ return mxGroup ? &*mxGroup->mpCode : nullptr;
+}
+
+void ScFormulaCell::SyncSharedCode()
+{
+ if (!mxGroup)
+ // Not a shared formula cell.
+ return;
+
+ pCode = &*mxGroup->mpCode;
+}
+
+#if DUMP_COLUMN_STORAGE
+
+void ScFormulaCell::Dump() const
+{
+ cout << "-- formula cell (" << aPos.Format(ScRefFlags::VALID | ScRefFlags::TAB_3D, &rDocument) << ")" << endl;
+ cout << " * shared: " << (mxGroup ? "true" : "false") << endl;
+ if (mxGroup)
+ {
+ cout << " * shared length: " << mxGroup->mnLength << endl;
+ cout << " * shared calc state: " << mxGroup->meCalcState << endl;
+ }
+
+ sc::TokenStringContext aCxt(rDocument, rDocument.GetGrammar());
+ cout << " * code: " << pCode->CreateString(aCxt, aPos) << endl;
+
+ FormulaError nErrCode = pCode->GetCodeError();
+ cout << " * code error: ";
+ if (nErrCode == FormulaError::NONE)
+ cout << "(none)";
+ else
+ {
+ OUString aStr = ScGlobal::GetErrorString(nErrCode);
+ cout << " * code error: " << aStr << " (" << int(nErrCode) << ")";
+ }
+ cout << endl;
+
+ cout << " * result: ";
+ sc::FormulaResultValue aRV = aResult.GetResult();
+ switch (aRV.meType)
+ {
+ case sc::FormulaResultValue::Value:
+ cout << aRV.mfValue << " (value)";
+ break;
+ case sc::FormulaResultValue::String:
+ cout << aRV.maString.getString() << " (string)";
+ break;
+ case sc::FormulaResultValue::Error:
+ cout << ScGlobal::GetErrorString(aRV.mnError) << " (error: " << int(aRV.mnError) << ")";
+ break;
+ case sc::FormulaResultValue::Invalid:
+ cout << "(invalid)";
+ break;
+ default:
+ ;
+ }
+ cout << endl;
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/formulaiter.cxx b/sc/source/core/data/formulaiter.cxx
new file mode 100644
index 000000000..d7f183b2a
--- /dev/null
+++ b/sc/source/core/data/formulaiter.cxx
@@ -0,0 +1,77 @@
+/* -*- 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 <formulaiter.hxx>
+
+#include <formulacell.hxx>
+#include <tokenarray.hxx>
+#include <formula/token.hxx>
+#include <token.hxx>
+
+using namespace formula;
+
+ScDetectiveRefIter::ScDetectiveRefIter( const ScDocument& rDoc, ScFormulaCell* pCell ) :
+ mrDoc(rDoc),
+ maIter(*pCell->GetCode()),
+ aPos(pCell->aPos)
+{
+}
+
+static bool lcl_ScDetectiveRefIter_SkipRef( const ScDocument& rDoc, formula::FormulaToken* p, const ScAddress& rPos )
+{
+ ScSingleRefData& rRef1 = *p->GetSingleRef();
+ ScAddress aAbs1 = rRef1.toAbs(rDoc, rPos);
+ if (!rDoc.ValidAddress(aAbs1))
+ return true;
+ if ( p->GetType() == svDoubleRef || p->GetType() == svExternalDoubleRef )
+ {
+ ScSingleRefData& rRef2 = p->GetDoubleRef()->Ref2;
+ ScAddress aAbs2 = rRef2.toAbs(rDoc, rPos);
+ if (!rDoc.ValidAddress(aAbs2))
+ return true;
+ }
+ return false;
+}
+
+bool ScDetectiveRefIter::GetNextRef( ScRange& rRange )
+{
+ bool bRet = false;
+ formula::FormulaToken* p = GetNextRefToken();
+ if( p )
+ {
+ SingleDoubleRefProvider aProv( *p );
+ rRange.aStart = aProv.Ref1.toAbs(mrDoc, aPos);
+ rRange.aEnd = aProv.Ref2.toAbs(mrDoc, aPos);
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+formula::FormulaToken* ScDetectiveRefIter::GetNextRefToken()
+{
+ formula::FormulaToken* p = maIter.GetNextReferenceRPN();
+ while (p && lcl_ScDetectiveRefIter_SkipRef(mrDoc, p, aPos))
+ {
+ p = maIter.GetNextReferenceRPN();
+ }
+ return p;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/funcdesc.cxx b/sc/source/core/data/funcdesc.cxx
new file mode 100644
index 000000000..060cda7a5
--- /dev/null
+++ b/sc/source/core/data/funcdesc.cxx
@@ -0,0 +1,1267 @@
+/* -*- 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 <funcdesc.hxx>
+
+#include <addincol.hxx>
+#include <appoptio.hxx>
+#include <callform.hxx>
+#include <compiler.hxx>
+#include <compiler.hrc>
+#include <global.hxx>
+#include <scfuncs.hrc>
+#include <scmod.hxx>
+#include <scresid.hxx>
+#include <helpids.h>
+
+#include <rtl/ustring.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <unotools/collatorwrapper.hxx>
+#include <formula/funcvarargs.h>
+#include <osl/diagnose.h>
+
+#include <memory>
+
+namespace {
+
+struct ScFuncDescCore
+{
+ /*
+ * An opcode from include/formula/compiler.hxx
+ */
+ sal_uInt16 nOpCode;
+ /*
+ * Pointer to list of strings
+ */
+ const TranslateId* pResource;
+ /*
+ * Count of list of strings
+ */
+ size_t nResourceLen;
+ /*
+ * 16-bit value:
+ *
+ * Bit 1: boolean flag whether function is suppressed. Usually 0. This
+ * may be used to add UI string resources before UI freeze if
+ * implementation isn't ready yet without displaying them in the
+ * function wizard, most recent used list and other UI elements. Also
+ * not available via API then.
+ *
+ * Bit 2: boolean flag whether function is hidden in the Function
+ * Wizard unless used in an expression.
+ */
+ sal_uInt16 nFunctionFlags;
+ /*
+ * Function group (text, math, ...), one of ID_FUNCTION_GRP_...
+ */
+ sal_uInt16 nCategory;
+ /*
+ * Help ID, HID_FUNC_...
+ */
+ const char* pHelpId;
+ /*
+ * Number of parameters. VAR_ARGS if variable number, or
+ * VAR_ARGS+number if number of fixed parameters and variable
+ * arguments following. Or PAIRED_VAR_ARGS if variable number of
+ * paired parameters, or PAIRED_VAR_ARGS+number if number of fixed
+ * parameters and variable paired arguments following.
+ */
+ sal_uInt16 nArgs;
+ /*
+ * For every parameter:
+ * Boolean flag whether the parameter is optional.
+ */
+ sal_uInt8 aOptionalArgs[7];
+ /*
+ * Limited number of maximum (variable) parameters, or 0 if no specific
+ * limit other than the general VAR_ARGS-1 value.
+ */
+ sal_uInt16 nVarArgsLimit;
+};
+
+}
+
+static void ScFuncRes(const ScFuncDescCore &rEntry, ScFuncDesc*, bool& rbSuppressed);
+
+ScFuncDesc::ScFuncDesc() :
+ pDefArgFlags (nullptr),
+ nFIndex (0),
+ nCategory (0),
+ nArgCount (0),
+ nVarArgsStart (0),
+ nVarArgsLimit (0),
+ bIncomplete (false),
+ mbHidden (false)
+{}
+
+ScFuncDesc::~ScFuncDesc()
+{
+ Clear();
+}
+
+void ScFuncDesc::Clear()
+{
+ sal_uInt16 nArgs = nArgCount;
+ if (nArgs >= PAIRED_VAR_ARGS)
+ nArgs -= PAIRED_VAR_ARGS - 2;
+ else if (nArgs >= VAR_ARGS)
+ nArgs -= VAR_ARGS - 1;
+ if (nArgs)
+ {
+ delete [] pDefArgFlags;
+ }
+ nArgCount = 0;
+ nVarArgsStart = 0;
+ nVarArgsLimit = 0;
+ maDefArgNames.clear();
+ maDefArgDescs.clear();
+ pDefArgFlags = nullptr;
+
+ mxFuncName.reset();
+ mxFuncDesc.reset();
+
+ nFIndex = 0;
+ nCategory = 0;
+ sHelpId.clear();
+ bIncomplete = false;
+ mbHidden = false;
+}
+
+OUString ScFuncDesc::GetParamList() const
+{
+ OUString sep(ScCompiler::GetNativeSymbol(ocSep));
+
+ OUStringBuffer aSig;
+
+ if ( nArgCount > 0 )
+ {
+ if ( nArgCount < VAR_ARGS )
+ {
+ sal_uInt16 nLastSuppressed = nArgCount;
+ sal_uInt16 nLastAdded = nArgCount;
+ for ( sal_uInt16 i=0; i<nArgCount; i++ )
+ {
+ nLastAdded = i;
+ aSig.append(maDefArgNames[i]);
+ if ( i != nArgCount-1 )
+ {
+ aSig.append(sep);
+ aSig.append( " " );
+ }
+ }
+ // If only suppressed parameters follow the last added parameter,
+ // remove one "; "
+ if (nLastSuppressed < nArgCount && nLastAdded < nLastSuppressed &&
+ aSig.getLength() >= 2)
+ aSig.setLength(aSig.getLength() - 2);
+ }
+ else if ( nArgCount < PAIRED_VAR_ARGS)
+ {
+ for ( sal_uInt16 nArg = 0; nArg < nVarArgsStart; nArg++ )
+ {
+ aSig.append(maDefArgNames[nArg]);
+ aSig.append(sep);
+ aSig.append( " " );
+ }
+ /* NOTE: Currently there are no suppressed var args parameters. If
+ * there were, we'd have to cope with it here and above for the fix
+ * parameters. For now parameters are always added, so no special
+ * treatment of a trailing "; " necessary. */
+ aSig.append(maDefArgNames[nVarArgsStart]);
+ aSig.append('1');
+ aSig.append(sep);
+ aSig.append(' ');
+ aSig.append(maDefArgNames[nVarArgsStart]);
+ aSig.append('2');
+ aSig.append(sep);
+ aSig.append(" ... ");
+ }
+ else
+ {
+ for ( sal_uInt16 nArg = 0; nArg < nVarArgsStart; nArg++ )
+ {
+ aSig.append(maDefArgNames[nArg]);
+ aSig.append(sep);
+ aSig.append( " " );
+ }
+
+ aSig.append(maDefArgNames[nVarArgsStart]);
+ aSig.append('1');
+ aSig.append(sep);
+ aSig.append(maDefArgNames[nVarArgsStart+1]);
+ aSig.append('1');
+ aSig.append(sep);
+ aSig.append( " " );
+ aSig.append(maDefArgNames[nVarArgsStart]);
+ aSig.append('2');
+ aSig.append(sep);
+ aSig.append(maDefArgNames[nVarArgsStart+1]);
+ aSig.append('2');
+ aSig.append(sep);
+ aSig.append( " ... " );
+ }
+ }
+
+ return aSig.makeStringAndClear();
+}
+
+OUString ScFuncDesc::getSignature() const
+{
+ OUStringBuffer aSig;
+
+ if(mxFuncName)
+ {
+ aSig.append(*mxFuncName);
+
+ OUString aParamList = GetParamList();
+ if( !aParamList.isEmpty() )
+ {
+ aSig.append( "( " );
+ aSig.append(aParamList);
+ // U+00A0 (NBSP) prevents automatic line break
+ aSig.append( u'\x00A0' );
+ aSig.append( ")" );
+ }
+ else
+ aSig.append( "()" );
+ }
+ return aSig.makeStringAndClear();
+}
+
+OUString ScFuncDesc::getFormula( const ::std::vector< OUString >& _aArguments ) const
+{
+ OUString sep = ScCompiler::GetNativeSymbol(ocSep);
+
+ OUStringBuffer aFormula;
+
+ if(mxFuncName)
+ {
+ aFormula.append( *mxFuncName );
+
+ aFormula.append( "(" );
+ if ( nArgCount > 0 && !_aArguments.empty() && !_aArguments[0].isEmpty())
+ {
+ ::std::vector< OUString >::const_iterator aIter = _aArguments.begin();
+ ::std::vector< OUString >::const_iterator aEnd = _aArguments.end();
+
+ aFormula.append( *aIter );
+ ++aIter;
+ while( aIter != aEnd && !aIter->isEmpty() )
+ {
+ aFormula.append( sep );
+ aFormula.append( *aIter );
+ ++aIter;
+ }
+ }
+
+ aFormula.append( ")" );
+ }
+ return aFormula.makeStringAndClear();
+}
+
+sal_uInt16 ScFuncDesc::GetSuppressedArgCount() const
+{
+ return nArgCount;
+}
+
+OUString ScFuncDesc::getFunctionName() const
+{
+ OUString sRet;
+ if ( mxFuncName )
+ sRet = *mxFuncName;
+ return sRet;
+}
+
+const formula::IFunctionCategory* ScFuncDesc::getCategory() const
+{
+ return ScGlobal::GetStarCalcFunctionMgr()->getCategory(nCategory - 1);
+}
+
+OUString ScFuncDesc::getDescription() const
+{
+ OUString sRet;
+ if ( mxFuncDesc )
+ sRet = *mxFuncDesc;
+ return sRet;
+}
+
+sal_Int32 ScFuncDesc::getSuppressedArgumentCount() const
+{
+ return GetSuppressedArgCount();
+}
+
+void ScFuncDesc::fillVisibleArgumentMapping(::std::vector<sal_uInt16>& _rArguments) const
+{
+ _rArguments.resize( nArgCount);
+ sal_uInt16 value = 0;
+ for (auto & argument : _rArguments)
+ argument = value++;
+
+ sal_uInt16 nArgs = nArgCount;
+ if (nArgs >= PAIRED_VAR_ARGS)
+ nArgs -= PAIRED_VAR_ARGS - 2;
+ else if (nArgs >= VAR_ARGS)
+ nArgs -= VAR_ARGS - 1;
+ for (sal_uInt16 i=0; i < nArgs; ++i)
+ {
+ _rArguments.push_back(i);
+ }
+}
+
+void ScFuncDesc::initArgumentInfo() const
+{
+ // get the full argument description
+ // (add-in has to be instantiated to get the type information)
+
+ if ( !(bIncomplete && mxFuncName) )
+ return;
+
+ ScUnoAddInCollection& rAddIns = *ScGlobal::GetAddInCollection();
+ OUString aIntName(rAddIns.FindFunction( *mxFuncName, true )); // pFuncName is upper-case
+
+ if ( !aIntName.isEmpty() )
+ {
+ // GetFuncData with bComplete=true loads the component and updates
+ // the global function list if needed.
+
+ rAddIns.GetFuncData( aIntName, true );
+ }
+
+ if ( bIncomplete )
+ {
+ OSL_FAIL( "couldn't initialize add-in function" );
+ const_cast<ScFuncDesc*>(this)->bIncomplete = false; // even if there was an error, don't try again
+ }
+}
+
+OString ScFuncDesc::getHelpId() const
+{
+ return sHelpId;
+}
+
+bool ScFuncDesc::isHidden() const
+{
+ return mbHidden;
+}
+
+sal_uInt32 ScFuncDesc::getParameterCount() const
+{
+ return nArgCount;
+}
+
+sal_uInt32 ScFuncDesc::getVarArgsStart() const
+{
+ return nVarArgsStart;
+}
+
+sal_uInt32 ScFuncDesc::getVarArgsLimit() const
+{
+ return nVarArgsLimit;
+}
+
+OUString ScFuncDesc::getParameterName(sal_uInt32 _nPos) const
+{
+ return maDefArgNames[_nPos];
+}
+
+OUString ScFuncDesc::getParameterDescription(sal_uInt32 _nPos) const
+{
+ return maDefArgDescs[_nPos];
+}
+
+bool ScFuncDesc::isParameterOptional(sal_uInt32 _nPos) const
+{
+ return pDefArgFlags[_nPos].bOptional;
+}
+
+bool ScFuncDesc::compareByName(const ScFuncDesc* a, const ScFuncDesc* b)
+{
+ return (ScGlobal::GetCaseCollator().compareString(*a->mxFuncName, *b->mxFuncName ) < 0);
+}
+
+#define ENTRY(CODE) CODE, SAL_N_ELEMENTS(CODE)
+
+ScFunctionList::ScFunctionList( bool bEnglishFunctionNames )
+ : mbEnglishFunctionNames( bEnglishFunctionNames )
+{
+ sal_Int32 nMaxFuncNameLen = 0; // Length of longest function name
+
+ // See ScFuncDescCore definition for format details.
+ // This list must be sorted in order of the opcode, dbgutil builds enable _GLIBCXX_DEBUG
+ // which will concept check that the list is sorted on first use to ensure this holds
+ static const ScFuncDescCore aDescs[] =
+ {
+ { SC_OPCODE_IF, ENTRY(SC_OPCODE_IF_ARY), 0, ID_FUNCTION_GRP_LOGIC, HID_FUNC_WENN, 3, { 0, 1, 1 }, 0 },
+ { SC_OPCODE_IF_ERROR, ENTRY(SC_OPCODE_IF_ERROR_ARY), 0, ID_FUNCTION_GRP_LOGIC, HID_FUNC_IFERROR, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_IF_NA, ENTRY(SC_OPCODE_IF_NA_ARY), 0, ID_FUNCTION_GRP_LOGIC, HID_FUNC_IFNA, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_CHOOSE, ENTRY(SC_OPCODE_CHOOSE_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_WAHL, VAR_ARGS+1, { 0, 0 }, 31 },
+ { SC_OPCODE_AND, ENTRY(SC_OPCODE_AND_ARY), 0, ID_FUNCTION_GRP_LOGIC, HID_FUNC_UND, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_OR, ENTRY(SC_OPCODE_OR_ARY), 0, ID_FUNCTION_GRP_LOGIC, HID_FUNC_ODER, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_PI, ENTRY(SC_OPCODE_PI_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_PI, 0, { }, 0 },
+ { SC_OPCODE_RANDOM, ENTRY(SC_OPCODE_RANDOM_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_ZUFALLSZAHL, 0, { }, 0 },
+ { SC_OPCODE_TRUE, ENTRY(SC_OPCODE_TRUE_ARY), 0, ID_FUNCTION_GRP_LOGIC, HID_FUNC_WAHR, 0, { }, 0 },
+ { SC_OPCODE_FALSE, ENTRY(SC_OPCODE_FALSE_ARY), 0, ID_FUNCTION_GRP_LOGIC, HID_FUNC_FALSCH, 0, { }, 0 },
+ { SC_OPCODE_GET_ACT_DATE, ENTRY(SC_OPCODE_GET_ACT_DATE_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_HEUTE, 0, { }, 0 },
+ { SC_OPCODE_GET_ACT_TIME, ENTRY(SC_OPCODE_GET_ACT_TIME_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_JETZT, 0, { }, 0 },
+ { SC_OPCODE_NO_VALUE, ENTRY(SC_OPCODE_NO_VALUE_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_NV, 0, { }, 0 },
+ { SC_OPCODE_CURRENT, ENTRY(SC_OPCODE_CURRENT_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_AKTUELL, 0, { }, 0 },
+ { SC_OPCODE_RANDOM_NV, ENTRY(SC_OPCODE_RANDOM_NV_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_RAND_NV, 0, { }, 0 },
+ { SC_OPCODE_DEG, ENTRY(SC_OPCODE_DEG_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_DEG, 1, { 0 }, 0 },
+ { SC_OPCODE_RAD, ENTRY(SC_OPCODE_RAD_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_RAD, 1, { 0 }, 0 },
+ { SC_OPCODE_SIN, ENTRY(SC_OPCODE_SIN_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_SIN, 1, { 0 }, 0 },
+ { SC_OPCODE_COS, ENTRY(SC_OPCODE_COS_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_COS, 1, { 0 }, 0 },
+ { SC_OPCODE_TAN, ENTRY(SC_OPCODE_TAN_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_TAN, 1, { 0 }, 0 },
+ { SC_OPCODE_COT, ENTRY(SC_OPCODE_COT_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_COT, 1, { 0 }, 0 },
+ { SC_OPCODE_ARC_SIN, ENTRY(SC_OPCODE_ARC_SIN_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_ARCSIN, 1, { 0 }, 0 },
+ { SC_OPCODE_ARC_COS, ENTRY(SC_OPCODE_ARC_COS_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_ARCCOS, 1, { 0 }, 0 },
+ { SC_OPCODE_ARC_TAN, ENTRY(SC_OPCODE_ARC_TAN_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_ARCTAN, 1, { 0 }, 0 },
+ { SC_OPCODE_ARC_COT, ENTRY(SC_OPCODE_ARC_COT_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_ARCCOT, 1, { 0 }, 0 },
+ { SC_OPCODE_SIN_HYP, ENTRY(SC_OPCODE_SIN_HYP_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_SINHYP, 1, { 0 }, 0 },
+ { SC_OPCODE_COS_HYP, ENTRY(SC_OPCODE_COS_HYP_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_COSHYP, 1, { 0 }, 0 },
+ { SC_OPCODE_TAN_HYP, ENTRY(SC_OPCODE_TAN_HYP_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_TANHYP, 1, { 0 }, 0 },
+ { SC_OPCODE_COT_HYP, ENTRY(SC_OPCODE_COT_HYP_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_COTHYP, 1, { 0 }, 0 },
+ { SC_OPCODE_ARC_SIN_HYP, ENTRY(SC_OPCODE_ARC_SIN_HYP_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_ARSINHYP, 1, { 0 }, 0 },
+ { SC_OPCODE_ARC_COS_HYP, ENTRY(SC_OPCODE_ARC_COS_HYP_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_ARCOSHYP, 1, { 0 }, 0 },
+ { SC_OPCODE_ARC_TAN_HYP, ENTRY(SC_OPCODE_ARC_TAN_HYP_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_ARTANHYP, 1, { 0 }, 0 },
+ { SC_OPCODE_ARC_COT_HYP, ENTRY(SC_OPCODE_ARC_COT_HYP_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_ARCOTHYP, 1, { 0 }, 0 },
+ { SC_OPCODE_COSECANT, ENTRY(SC_OPCODE_COSECANT_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_COSECANT, 1, { 0 }, 0 },
+ { SC_OPCODE_SECANT, ENTRY(SC_OPCODE_SECANT_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_SECANT, 1, { 0 }, 0 },
+ { SC_OPCODE_COSECANT_HYP, ENTRY(SC_OPCODE_COSECANT_HYP_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_COSECANTHYP, 1, { 0 }, 0 },
+ { SC_OPCODE_SECANT_HYP, ENTRY(SC_OPCODE_SECANT_HYP_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_SECANTHYP, 1, { 0 }, 0 },
+ { SC_OPCODE_EXP, ENTRY(SC_OPCODE_EXP_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_EXP, 1, { 0 }, 0 },
+ { SC_OPCODE_LN, ENTRY(SC_OPCODE_LN_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_LN, 1, { 0 }, 0 },
+ { SC_OPCODE_SQRT, ENTRY(SC_OPCODE_SQRT_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_WURZEL, 1, { 0 }, 0 },
+ { SC_OPCODE_FACT, ENTRY(SC_OPCODE_FACT_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_FAKULTAET, 1, { 0 }, 0 },
+ { SC_OPCODE_GET_YEAR, ENTRY(SC_OPCODE_GET_YEAR_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_JAHR, 1, { 0 }, 0 },
+ { SC_OPCODE_GET_MONTH, ENTRY(SC_OPCODE_GET_MONTH_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_MONAT, 1, { 0 }, 0 },
+ { SC_OPCODE_GET_DAY, ENTRY(SC_OPCODE_GET_DAY_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_TAG, 1, { 0 }, 0 },
+ { SC_OPCODE_GET_HOUR, ENTRY(SC_OPCODE_GET_HOUR_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_STUNDE, 1, { 0 }, 0 },
+ { SC_OPCODE_GET_MIN, ENTRY(SC_OPCODE_GET_MIN_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_MINUTE, 1, { 0 }, 0 },
+ { SC_OPCODE_GET_SEC, ENTRY(SC_OPCODE_GET_SEC_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_SEKUNDE, 1, { 0 }, 0 },
+ { SC_OPCODE_PLUS_MINUS, ENTRY(SC_OPCODE_PLUS_MINUS_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_VORZEICHEN, 1, { 0 }, 0 },
+ { SC_OPCODE_ABS, ENTRY(SC_OPCODE_ABS_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_ABS, 1, { 0 }, 0 },
+ { SC_OPCODE_INT, ENTRY(SC_OPCODE_INT_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_GANZZAHL, 1, { 0 }, 0 },
+ { SC_OPCODE_PHI, ENTRY(SC_OPCODE_PHI_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_PHI, 1, { 0 }, 0 },
+ { SC_OPCODE_GAUSS, ENTRY(SC_OPCODE_GAUSS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_GAUSS, 1, { 0 }, 0 },
+ { SC_OPCODE_IS_EMPTY, ENTRY(SC_OPCODE_IS_EMPTY_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_ISTLEER, 1, { 0 }, 0 },
+ { SC_OPCODE_IS_STRING, ENTRY(SC_OPCODE_IS_STRING_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_ISTTEXT, 1, { 0 }, 0 },
+ { SC_OPCODE_IS_NON_STRING, ENTRY(SC_OPCODE_IS_NON_STRING_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_ISTKTEXT, 1, { 0 }, 0 },
+ { SC_OPCODE_IS_LOGICAL, ENTRY(SC_OPCODE_IS_LOGICAL_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_ISTLOG, 1, { 0 }, 0 },
+ { SC_OPCODE_TYPE, ENTRY(SC_OPCODE_TYPE_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_TYP, 1, { 0 }, 0 },
+ { SC_OPCODE_IS_REF, ENTRY(SC_OPCODE_IS_REF_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_ISTBEZUG, 1, { 0 }, 0 },
+ { SC_OPCODE_IS_VALUE, ENTRY(SC_OPCODE_IS_VALUE_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_ISTZAHL, 1, { 0 }, 0 },
+ { SC_OPCODE_IS_FORMULA, ENTRY(SC_OPCODE_IS_FORMULA_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_ISTFORMEL, 1, { 0 }, 0 },
+ { SC_OPCODE_IS_NV, ENTRY(SC_OPCODE_IS_NV_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_ISTNV, 1, { 0 }, 0 },
+ { SC_OPCODE_IS_ERR, ENTRY(SC_OPCODE_IS_ERR_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_ISTFEHL, 1, { 0 }, 0 },
+ { SC_OPCODE_IS_ERROR, ENTRY(SC_OPCODE_IS_ERROR_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_ISTFEHLER, 1, { 0 }, 0 },
+ { SC_OPCODE_IS_EVEN, ENTRY(SC_OPCODE_IS_EVEN_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_ISTGERADE, 1, { 0 }, 0 },
+ { SC_OPCODE_IS_ODD, ENTRY(SC_OPCODE_IS_ODD_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_ISTUNGERADE, 1, { 0 }, 0 },
+ { SC_OPCODE_N, ENTRY(SC_OPCODE_N_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_N, 1, { 0 }, 0 },
+ { SC_OPCODE_GET_DATE_VALUE, ENTRY(SC_OPCODE_GET_DATE_VALUE_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_DATWERT, 1, { 0 }, 0 },
+ { SC_OPCODE_GET_TIME_VALUE, ENTRY(SC_OPCODE_GET_TIME_VALUE_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_ZEITWERT, 1, { 0 }, 0 },
+ { SC_OPCODE_CODE, ENTRY(SC_OPCODE_CODE_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_CODE, 1, { 0 }, 0 },
+ { SC_OPCODE_TRIM, ENTRY(SC_OPCODE_TRIM_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_GLAETTEN, 1, { 0 }, 0 },
+ { SC_OPCODE_UPPER, ENTRY(SC_OPCODE_UPPER_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_GROSS, 1, { 0 }, 0 },
+ { SC_OPCODE_PROPER, ENTRY(SC_OPCODE_PROPER_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_GROSS2, 1, { 0 }, 0 },
+ { SC_OPCODE_LOWER, ENTRY(SC_OPCODE_LOWER_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_KLEIN, 1, { 0 }, 0 },
+ { SC_OPCODE_LEN, ENTRY(SC_OPCODE_LEN_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_LAENGE, 1, { 0 }, 0 },
+ { SC_OPCODE_T, ENTRY(SC_OPCODE_T_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_T, 1, { 0 }, 0 },
+ { SC_OPCODE_VALUE, ENTRY(SC_OPCODE_VALUE_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_WERT, 1, { 0 }, 0 },
+ { SC_OPCODE_CLEAN, ENTRY(SC_OPCODE_CLEAN_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_SAEUBERN, 1, { 0 }, 0 },
+ { SC_OPCODE_CHAR, ENTRY(SC_OPCODE_CHAR_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_ZEICHEN, 1, { 0 }, 0 },
+ { SC_OPCODE_LOG10, ENTRY(SC_OPCODE_LOG10_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_LOG10, 1, { 0 }, 0 },
+ { SC_OPCODE_EVEN, ENTRY(SC_OPCODE_EVEN_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_GERADE, 1, { 0 }, 0 },
+ { SC_OPCODE_ODD, ENTRY(SC_OPCODE_ODD_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_UNGERADE, 1, { 0 }, 0 },
+ { SC_OPCODE_STD_NORM_DIST, ENTRY(SC_OPCODE_STD_NORM_DIST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_STANDNORMVERT, 1, { 0 }, 0 },
+ { SC_OPCODE_FISHER, ENTRY(SC_OPCODE_FISHER_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_FISHER, 1, { 0 }, 0 },
+ { SC_OPCODE_FISHER_INV, ENTRY(SC_OPCODE_FISHER_INV_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_FISHERINV, 1, { 0 }, 0 },
+ { SC_OPCODE_S_NORM_INV, ENTRY(SC_OPCODE_S_NORM_INV_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_STANDNORMINV, 1, { 0 }, 0 },
+ { SC_OPCODE_GAMMA_LN, ENTRY(SC_OPCODE_GAMMA_LN_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_GAMMALN, 1, { 0 }, 0 },
+ { SC_OPCODE_ERROR_TYPE, ENTRY(SC_OPCODE_ERROR_TYPE_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_FEHLERTYP, 1, { 0 }, 0 },
+ { SC_OPCODE_FORMULA, ENTRY(SC_OPCODE_FORMULA_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_FORMEL, 1, { 0 }, 0 },
+ { SC_OPCODE_ARABIC, ENTRY(SC_OPCODE_ARABIC_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_ARABISCH, 1, { 0 }, 0 },
+ { SC_OPCODE_INFO, ENTRY(SC_OPCODE_INFO_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_INFO, 1, { 0 }, 0 },
+ { SC_OPCODE_BAHTTEXT, ENTRY(SC_OPCODE_BAHTTEXT_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_BAHTTEXT, 1, { 0 }, 0 },
+ { SC_OPCODE_JIS, ENTRY(SC_OPCODE_JIS_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_JIS, 1, { 0 }, 0 },
+ { SC_OPCODE_ASC, ENTRY(SC_OPCODE_ASC_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_ASC, 1, { 0 }, 0 },
+ { SC_OPCODE_UNICODE, ENTRY(SC_OPCODE_UNICODE_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_UNICODE, 1, { 0 }, 0 },
+ { SC_OPCODE_UNICHAR, ENTRY(SC_OPCODE_UNICHAR_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_UNICHAR, 1, { 0 }, 0 },
+ { SC_OPCODE_GAMMA, ENTRY(SC_OPCODE_GAMMA_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_GAMMA, 1, { 0 }, 0 },
+ { SC_OPCODE_GAMMA_LN_MS, ENTRY(SC_OPCODE_GAMMA_LN_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_GAMMALN_MS, 1, { 0 }, 0 },
+ { SC_OPCODE_ERF_MS, ENTRY(SC_OPCODE_ERF_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_ERF_MS, 1, { 0 }, 0 },
+ { SC_OPCODE_ERFC_MS, ENTRY(SC_OPCODE_ERFC_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_ERFC_MS, 1, { 0 }, 0 },
+ { SC_OPCODE_ERROR_TYPE_ODF, ENTRY(SC_OPCODE_ERROR_TYPE_ODF_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_ERROR_TYPE_ODF, 1, { 0 }, 0 },
+ { SC_OPCODE_ENCODEURL, ENTRY(SC_OPCODE_ENCODEURL_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_ENCODEURL, 1, { 0 }, 0 },
+ { SC_OPCODE_ISOWEEKNUM, ENTRY(SC_OPCODE_ISOWEEKNUM_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_ISOWEEKNUM, 1, { 0 }, 0 },
+ { SC_OPCODE_NOT, ENTRY(SC_OPCODE_NOT_ARY), 0, ID_FUNCTION_GRP_LOGIC, HID_FUNC_NICHT, 1, { 0 }, 0 },
+ { SC_OPCODE_ARC_TAN_2, ENTRY(SC_OPCODE_ARC_TAN_2_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_ARCTAN2, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_CEIL, ENTRY(SC_OPCODE_CEIL_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_OBERGRENZE, 3, { 0, 1, 1 }, 0 },
+ { SC_OPCODE_FLOOR, ENTRY(SC_OPCODE_FLOOR_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_UNTERGRENZE, 3, { 0, 1, 1 }, 0 },
+ { SC_OPCODE_ROUND, ENTRY(SC_OPCODE_ROUND_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_RUNDEN, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_ROUND_UP, ENTRY(SC_OPCODE_ROUND_UP_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_AUFRUNDEN, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_ROUND_DOWN, ENTRY(SC_OPCODE_ROUND_DOWN_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_ABRUNDEN, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_TRUNC, ENTRY(SC_OPCODE_TRUNC_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_KUERZEN, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_LOG, ENTRY(SC_OPCODE_LOG_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_LOG, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_POWER, ENTRY(SC_OPCODE_POWER_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_POTENZ, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_GCD, ENTRY(SC_OPCODE_GCD_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_GGT, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_LCM, ENTRY(SC_OPCODE_LCM_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_KGV, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_MOD, ENTRY(SC_OPCODE_MOD_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_REST, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_SUM_PRODUCT, ENTRY(SC_OPCODE_SUM_PRODUCT_ARY), 0, ID_FUNCTION_GRP_MATRIX, HID_FUNC_SUMMENPRODUKT, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_SUM_SQ, ENTRY(SC_OPCODE_SUM_SQ_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_QUADRATESUMME, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_SUM_X2MY2, ENTRY(SC_OPCODE_SUM_X2MY2_ARY), 0, ID_FUNCTION_GRP_MATRIX, HID_FUNC_SUMMEX2MY2, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_SUM_X2DY2, ENTRY(SC_OPCODE_SUM_X2DY2_ARY), 0, ID_FUNCTION_GRP_MATRIX, HID_FUNC_SUMMEX2PY2, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_SUM_XMY2, ENTRY(SC_OPCODE_SUM_XMY2_ARY), 0, ID_FUNCTION_GRP_MATRIX, HID_FUNC_SUMMEXMY2, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_GET_DATE, ENTRY(SC_OPCODE_GET_DATE_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_DATUM, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_GET_TIME, ENTRY(SC_OPCODE_GET_TIME_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_ZEIT, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_GET_DIFF_DATE, ENTRY(SC_OPCODE_GET_DIFF_DATE_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_TAGE, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_GET_DIFF_DATE_360, ENTRY(SC_OPCODE_GET_DIFF_DATE_360_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_TAGE360, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_MIN, ENTRY(SC_OPCODE_MIN_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_MIN, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_MAX, ENTRY(SC_OPCODE_MAX_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_MAX, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_SUM, ENTRY(SC_OPCODE_SUM_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_SUMME, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_PRODUCT, ENTRY(SC_OPCODE_PRODUCT_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_PRODUKT, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_AVERAGE, ENTRY(SC_OPCODE_AVERAGE_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_MITTELWERT, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_COUNT, ENTRY(SC_OPCODE_COUNT_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_ANZAHL, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_COUNT_2, ENTRY(SC_OPCODE_COUNT_2_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_ANZAHL2, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_NPV, ENTRY(SC_OPCODE_NPV_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_NBW, VAR_ARGS+1, { 0, 0 }, 0 },
+ { SC_OPCODE_IRR, ENTRY(SC_OPCODE_IRR_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_IKV, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_VAR, ENTRY(SC_OPCODE_VAR_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_VARIANZ, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_VAR_P, ENTRY(SC_OPCODE_VAR_P_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_VARIANZEN, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_ST_DEV, ENTRY(SC_OPCODE_ST_DEV_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_STABW, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_ST_DEV_P, ENTRY(SC_OPCODE_ST_DEV_P_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_STABWN, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_B, ENTRY(SC_OPCODE_B_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_B, 4, { 0, 0, 0, 1 }, 0 },
+ { SC_OPCODE_NORM_DIST, ENTRY(SC_OPCODE_NORM_DIST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_NORMVERT, 4, { 0, 0, 0, 1 }, 0 },
+ { SC_OPCODE_EXP_DIST, ENTRY(SC_OPCODE_EXP_DIST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_EXPONVERT, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_BINOM_DIST, ENTRY(SC_OPCODE_BINOM_DIST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_BINOMVERT, 4, { 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_POISSON_DIST, ENTRY(SC_OPCODE_POISSON_DIST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_POISSON, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_COMBIN, ENTRY(SC_OPCODE_COMBIN_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_KOMBINATIONEN, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_COMBIN_A, ENTRY(SC_OPCODE_COMBIN_A_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_KOMBINATIONEN2, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_PERMUT, ENTRY(SC_OPCODE_PERMUT_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_VARIATIONEN, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_PERMUTATION_A, ENTRY(SC_OPCODE_PERMUTATION_A_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_VARIATIONEN2, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_PV, ENTRY(SC_OPCODE_PV_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_BW, 5, { 0, 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_SYD, ENTRY(SC_OPCODE_SYD_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_DIA, 4, { 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_DDB, ENTRY(SC_OPCODE_DDB_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_GDA, 5, { 0, 0, 0, 0, 1 }, 0 },
+ { SC_OPCODE_DB, ENTRY(SC_OPCODE_DB_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_GDA2, 5, { 0, 0, 0, 0, 1 }, 0 },
+ { SC_OPCODE_VBD , ENTRY(SC_OPCODE_VBD_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_VDB, 7, { 0, 0, 0, 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_PDURATION, ENTRY(SC_OPCODE_PDURATION_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_LAUFZEIT, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_SLN, ENTRY(SC_OPCODE_SLN_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_LIA, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_PMT, ENTRY(SC_OPCODE_PMT_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_RMZ, 5, { 0, 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_COLUMNS, ENTRY(SC_OPCODE_COLUMNS_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_SPALTEN, 1, { 0 }, 0 },
+ { SC_OPCODE_ROWS, ENTRY(SC_OPCODE_ROWS_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_ZEILEN, 1, { 0 }, 0 },
+ { SC_OPCODE_COLUMN, ENTRY(SC_OPCODE_COLUMN_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_SPALTE, 1, { 1 }, 0 },
+ { SC_OPCODE_ROW, ENTRY(SC_OPCODE_ROW_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_ZEILE, 1, { 1 }, 0 },
+ { SC_OPCODE_RRI, ENTRY(SC_OPCODE_RRI_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_ZGZ, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_FV, ENTRY(SC_OPCODE_FV_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_ZW, 5, { 0, 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_NPER, ENTRY(SC_OPCODE_NPER_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_ZZR, 5, { 0, 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_RATE, ENTRY(SC_OPCODE_RATE_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_ZINS, 6, { 0, 0, 0, 1, 1, 1 }, 0 },
+ { SC_OPCODE_IPMT, ENTRY(SC_OPCODE_IPMT_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_ZINSZ, 6, { 0, 0, 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_PPMT, ENTRY(SC_OPCODE_PPMT_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_KAPZ, 6, { 0, 0, 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_CUM_IPMT, ENTRY(SC_OPCODE_CUM_IPMT_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_KUMZINSZ, 6, { 0, 0, 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_CUM_PRINC, ENTRY(SC_OPCODE_CUM_PRINC_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_KUMKAPITAL, 6, { 0, 0, 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_EFFECT, ENTRY(SC_OPCODE_EFFECT_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_EFFEKTIV, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_NOMINAL, ENTRY(SC_OPCODE_NOMINAL_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_NOMINAL, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_SUB_TOTAL, ENTRY(SC_OPCODE_SUB_TOTAL_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_TEILERGEBNIS, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_DB_SUM, ENTRY(SC_OPCODE_DB_SUM_ARY), 0, ID_FUNCTION_GRP_DATABASE, HID_FUNC_DBSUMME, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_DB_COUNT, ENTRY(SC_OPCODE_DB_COUNT_ARY), 0, ID_FUNCTION_GRP_DATABASE, HID_FUNC_DBANZAHL, 3, { 0, 1, 0 }, 0 },
+ { SC_OPCODE_DB_COUNT_2, ENTRY(SC_OPCODE_DB_COUNT_2_ARY), 0, ID_FUNCTION_GRP_DATABASE, HID_FUNC_DBANZAHL2, 3, { 0, 1, 0 }, 0 },
+ { SC_OPCODE_DB_AVERAGE, ENTRY(SC_OPCODE_DB_AVERAGE_ARY), 0, ID_FUNCTION_GRP_DATABASE, HID_FUNC_DBMITTELWERT, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_DB_GET, ENTRY(SC_OPCODE_DB_GET_ARY), 0, ID_FUNCTION_GRP_DATABASE, HID_FUNC_DBAUSZUG, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_DB_MAX, ENTRY(SC_OPCODE_DB_MAX_ARY), 0, ID_FUNCTION_GRP_DATABASE, HID_FUNC_DBMAX, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_DB_MIN, ENTRY(SC_OPCODE_DB_MIN_ARY), 0, ID_FUNCTION_GRP_DATABASE, HID_FUNC_DBMIN, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_DB_PRODUCT, ENTRY(SC_OPCODE_DB_PRODUCT_ARY), 0, ID_FUNCTION_GRP_DATABASE, HID_FUNC_DBPRODUKT, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_DB_STD_DEV, ENTRY(SC_OPCODE_DB_STD_DEV_ARY), 0, ID_FUNCTION_GRP_DATABASE, HID_FUNC_DBSTDABW, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_DB_STD_DEV_P, ENTRY(SC_OPCODE_DB_STD_DEV_P_ARY), 0, ID_FUNCTION_GRP_DATABASE, HID_FUNC_DBSTDABWN, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_DB_VAR, ENTRY(SC_OPCODE_DB_VAR_ARY), 0, ID_FUNCTION_GRP_DATABASE, HID_FUNC_DBVARIANZ, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_DB_VAR_P, ENTRY(SC_OPCODE_DB_VAR_P_ARY), 0, ID_FUNCTION_GRP_DATABASE, HID_FUNC_DBVARIANZEN, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_INDIRECT, ENTRY(SC_OPCODE_INDIRECT_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_INDIREKT, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_ADDRESS, ENTRY(SC_OPCODE_ADDRESS_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_ADRESSE, 5, { 0, 0, 1, 1, 1 }, 0 },
+ { SC_OPCODE_MATCH, ENTRY(SC_OPCODE_MATCH_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_VERGLEICH, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_COUNT_EMPTY_CELLS, ENTRY(SC_OPCODE_COUNT_EMPTY_CELLS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_ANZAHLLEEREZELLEN, 1, { 0 }, 0 },
+ { SC_OPCODE_COUNT_IF, ENTRY(SC_OPCODE_COUNT_IF_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_ZAEHLENWENN, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_SUM_IF, ENTRY(SC_OPCODE_SUM_IF_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_SUMMEWENN, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_LOOKUP, ENTRY(SC_OPCODE_LOOKUP_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_VERWEIS, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_V_LOOKUP, ENTRY(SC_OPCODE_V_LOOKUP_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_SVERWEIS, 4, { 0, 0, 0, 1 }, 0 },
+ { SC_OPCODE_H_LOOKUP, ENTRY(SC_OPCODE_H_LOOKUP_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_WVERWEIS, 4, { 0, 0, 0, 1 }, 0 },
+ { SC_OPCODE_OFFSET, ENTRY(SC_OPCODE_OFFSET_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_VERSCHIEBUNG, 5, { 0, 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_INDEX, ENTRY(SC_OPCODE_INDEX_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_INDEX, 4, { 0, 1, 1, 1 }, 0 },
+ { SC_OPCODE_AREAS, ENTRY(SC_OPCODE_AREAS_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_BEREICHE, 1, { 0 }, 0 },
+ { SC_OPCODE_CURRENCY, ENTRY(SC_OPCODE_CURRENCY_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_DM, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_REPLACE, ENTRY(SC_OPCODE_REPLACE_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_ERSETZEN, 4, { 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_FIXED, ENTRY(SC_OPCODE_FIXED_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_FEST, 3, { 0, 1, 1 }, 0 },
+ { SC_OPCODE_FIND, ENTRY(SC_OPCODE_FIND_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_FINDEN, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_EXACT, ENTRY(SC_OPCODE_EXACT_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_IDENTISCH, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_LEFT, ENTRY(SC_OPCODE_LEFT_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_LINKS, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_RIGHT, ENTRY(SC_OPCODE_RIGHT_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_RECHTS, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_SEARCH, ENTRY(SC_OPCODE_SEARCH_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_SUCHEN, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_MID, ENTRY(SC_OPCODE_MID_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_TEIL, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_TEXT, ENTRY(SC_OPCODE_TEXT_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_TEXT, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_SUBSTITUTE, ENTRY(SC_OPCODE_SUBSTITUTE_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_WECHSELN, 4, { 0, 0, 0, 1 }, 0 },
+ { SC_OPCODE_REPT, ENTRY(SC_OPCODE_REPT_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_WIEDERHOLEN, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_CONCAT, ENTRY(SC_OPCODE_CONCAT_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_VERKETTEN, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_MAT_DET, ENTRY(SC_OPCODE_MAT_DET_ARY), 0, ID_FUNCTION_GRP_MATRIX, HID_FUNC_MDET, 1, { 0 }, 0 },
+ { SC_OPCODE_MAT_INV, ENTRY(SC_OPCODE_MAT_INV_ARY), 0, ID_FUNCTION_GRP_MATRIX, HID_FUNC_MINV, 1, { 0 }, 0 },
+ { SC_OPCODE_MAT_MULT, ENTRY(SC_OPCODE_MAT_MULT_ARY), 0, ID_FUNCTION_GRP_MATRIX, HID_FUNC_MMULT, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_MAT_TRANS, ENTRY(SC_OPCODE_MAT_TRANS_ARY), 0, ID_FUNCTION_GRP_MATRIX, HID_FUNC_MTRANS, 1, { 0 }, 0 },
+ { SC_OPCODE_MATRIX_UNIT, ENTRY(SC_OPCODE_MATRIX_UNIT_ARY), 0, ID_FUNCTION_GRP_MATRIX, HID_FUNC_EINHEITSMATRIX, 1, { 0 }, 0 },
+ { SC_OPCODE_HYP_GEOM_DIST, ENTRY(SC_OPCODE_HYP_GEOM_DIST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_HYPGEOMVERT, 5, { 0, 0, 0, 0, 1 }, 0 },
+ { SC_OPCODE_LOG_NORM_DIST, ENTRY(SC_OPCODE_LOG_NORM_DIST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_LOGNORMVERT, 4, { 0, 1, 1, 1 }, 0 },
+ { SC_OPCODE_T_DIST, ENTRY(SC_OPCODE_T_DIST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_TVERT, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_F_DIST, ENTRY(SC_OPCODE_F_DIST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_FVERT, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_CHI_DIST, ENTRY(SC_OPCODE_CHI_DIST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_CHIVERT, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_WEIBULL, ENTRY(SC_OPCODE_WEIBULL_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_WEIBULL, 4, { 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_NEG_BINOM_VERT, ENTRY(SC_OPCODE_NEG_BINOM_VERT_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_NEGBINOMVERT, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_CRIT_BINOM, ENTRY(SC_OPCODE_CRIT_BINOM_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_KRITBINOM, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_KURT, ENTRY(SC_OPCODE_KURT_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_KURT, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_HAR_MEAN, ENTRY(SC_OPCODE_HAR_MEAN_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_HARMITTEL, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_GEO_MEAN, ENTRY(SC_OPCODE_GEO_MEAN_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_GEOMITTEL, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_STANDARD, ENTRY(SC_OPCODE_STANDARD_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_STANDARDISIERUNG, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_AVE_DEV, ENTRY(SC_OPCODE_AVE_DEV_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_MITTELABW, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_SKEW, ENTRY(SC_OPCODE_SKEW_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_SCHIEFE, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_DEV_SQ, ENTRY(SC_OPCODE_DEV_SQ_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_SUMQUADABW, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_MEDIAN, ENTRY(SC_OPCODE_MEDIAN_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_MEDIAN, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_MODAL_VALUE, ENTRY(SC_OPCODE_MODAL_VALUE_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_MODALWERT, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_Z_TEST, ENTRY(SC_OPCODE_Z_TEST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_GTEST, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_T_TEST, ENTRY(SC_OPCODE_T_TEST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_TTEST, 4, { 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_RANK, ENTRY(SC_OPCODE_RANK_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_RANG, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_PERCENTILE, ENTRY(SC_OPCODE_PERCENTILE_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_QUANTIL, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_PERCENT_RANK, ENTRY(SC_OPCODE_PERCENT_RANK_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_QUANTILSRANG, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_LARGE, ENTRY(SC_OPCODE_LARGE_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_KGROESSTE, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_SMALL, ENTRY(SC_OPCODE_SMALL_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_KKLEINSTE, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_FREQUENCY, ENTRY(SC_OPCODE_FREQUENCY_ARY), 0, ID_FUNCTION_GRP_MATRIX, HID_FUNC_HAEUFIGKEIT, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_QUARTILE, ENTRY(SC_OPCODE_QUARTILE_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_QUARTILE, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_NORM_INV, ENTRY(SC_OPCODE_NORM_INV_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_NORMINV, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_CONFIDENCE, ENTRY(SC_OPCODE_CONFIDENCE_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_KONFIDENZ, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_F_TEST, ENTRY(SC_OPCODE_F_TEST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_FTEST, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_TRIM_MEAN, ENTRY(SC_OPCODE_TRIM_MEAN_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_GESTUTZTMITTEL, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_PROB, ENTRY(SC_OPCODE_PROB_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_WAHRSCHBEREICH, 4, { 0, 0, 0, 1 }, 0 },
+ { SC_OPCODE_CORREL, ENTRY(SC_OPCODE_CORREL_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_KORREL, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_COVAR, ENTRY(SC_OPCODE_COVAR_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_KOVAR, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_PEARSON, ENTRY(SC_OPCODE_PEARSON_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_PEARSON, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_RSQ, ENTRY(SC_OPCODE_RSQ_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_BESTIMMTHEITSMASS, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_STEYX, ENTRY(SC_OPCODE_STEYX_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_STFEHLERYX, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_SLOPE, ENTRY(SC_OPCODE_SLOPE_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_STEIGUNG, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_INTERCEPT, ENTRY(SC_OPCODE_INTERCEPT_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_ACHSENABSCHNITT, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_TREND, ENTRY(SC_OPCODE_TREND_ARY), 0, ID_FUNCTION_GRP_MATRIX, HID_FUNC_TREND, 4, { 0, 1, 1, 1 }, 0 },
+ { SC_OPCODE_GROWTH, ENTRY(SC_OPCODE_GROWTH_ARY), 0, ID_FUNCTION_GRP_MATRIX, HID_FUNC_VARIATION, 4, { 0, 1, 1, 1 }, 0 },
+ { SC_OPCODE_LINEST, ENTRY(SC_OPCODE_LINEST_ARY), 0, ID_FUNCTION_GRP_MATRIX, HID_FUNC_RGP, 4, { 0, 1, 1, 1 }, 0 },
+ { SC_OPCODE_LOGEST, ENTRY(SC_OPCODE_LOGEST_ARY), 0, ID_FUNCTION_GRP_MATRIX, HID_FUNC_RKP, 4, { 0, 1, 1, 1 }, 0 },
+ { SC_OPCODE_FORECAST, ENTRY(SC_OPCODE_FORECAST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_SCHAETZER, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_CHI_INV, ENTRY(SC_OPCODE_CHI_INV_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_CHIINV, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_GAMMA_DIST, ENTRY(SC_OPCODE_GAMMA_DIST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_GAMMAVERT, 4, { 0, 0, 0, 1 }, 0 },
+ { SC_OPCODE_GAMMA_INV, ENTRY(SC_OPCODE_GAMMA_INV_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_GAMMAINV, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_T_INV, ENTRY(SC_OPCODE_T_INV_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_TINV, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_F_INV, ENTRY(SC_OPCODE_F_INV_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_FINV, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_CHI_TEST, ENTRY(SC_OPCODE_CHI_TEST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_CHITEST, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_LOG_INV, ENTRY(SC_OPCODE_LOG_INV_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_LOGINV, 3, { 0, 1, 1 }, 0 },
+ { SC_OPCODE_BETA_DIST, ENTRY(SC_OPCODE_BETA_DIST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_BETAVERT, 6, { 0, 0, 0, 1, 1, 1 }, 0 },
+ { SC_OPCODE_BETA_INV, ENTRY(SC_OPCODE_BETA_INV_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_BETAINV, 5, { 0, 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_WEEK, ENTRY(SC_OPCODE_WEEK_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_KALENDERWOCHE, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_GET_DAY_OF_WEEK, ENTRY(SC_OPCODE_GET_DAY_OF_WEEK_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_WOCHENTAG, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_STYLE, ENTRY(SC_OPCODE_STYLE_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_VORLAGE, 3, { 0, 1, 1 }, 0 },
+ { SC_OPCODE_DDE, ENTRY(SC_OPCODE_DDE_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_DDE, 4, { 0, 0, 0, 1 }, 0 },
+ { SC_OPCODE_BASE, ENTRY(SC_OPCODE_BASE_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_BASIS, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_SHEET, ENTRY(SC_OPCODE_SHEET_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_TABELLE, 1, { 1 }, 0 },
+ { SC_OPCODE_SHEETS, ENTRY(SC_OPCODE_SHEETS_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_TABELLEN, 1, { 1 }, 0 },
+ { SC_OPCODE_MIN_A, ENTRY(SC_OPCODE_MIN_A_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_MINA, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_MAX_A, ENTRY(SC_OPCODE_MAX_A_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_MAXA, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_AVERAGE_A, ENTRY(SC_OPCODE_AVERAGE_A_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_MITTELWERTA, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_ST_DEV_A, ENTRY(SC_OPCODE_ST_DEV_A_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_STABWA, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_ST_DEV_P_A, ENTRY(SC_OPCODE_ST_DEV_P_A_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_STABWNA, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_VAR_A, ENTRY(SC_OPCODE_VAR_A_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_VARIANZA, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_VAR_P_A, ENTRY(SC_OPCODE_VAR_P_A_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_VARIANZENA, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_EASTERSUNDAY, ENTRY(SC_OPCODE_EASTERSUNDAY_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_OSTERSONNTAG, 1, { 0 }, 0 },
+ { SC_OPCODE_DECIMAL, ENTRY(SC_OPCODE_DECIMAL_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_DEZIMAL, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_CONVERT_OOO, ENTRY(SC_OPCODE_CONVERT_OOO_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_UMRECHNEN, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_ROMAN, ENTRY(SC_OPCODE_ROMAN_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_ROEMISCH, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_MIRR, ENTRY(SC_OPCODE_MIRR_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_QIKV, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_CELL, ENTRY(SC_OPCODE_CELL_ARY), 0, ID_FUNCTION_GRP_INFO, HID_FUNC_ZELLE, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_ISPMT, ENTRY(SC_OPCODE_ISPMT_ARY), 0, ID_FUNCTION_GRP_FINANCIAL, HID_FUNC_ISPMT, 4, { 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_HYPERLINK, ENTRY(SC_OPCODE_HYPERLINK_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_HYPERLINK, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_GET_PIVOT_DATA, ENTRY(SC_OPCODE_GET_PIVOT_DATA_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_GETPIVOTDATA, VAR_ARGS+2, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_EUROCONVERT, ENTRY(SC_OPCODE_EUROCONVERT_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_EUROCONVERT, 5, { 0, 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_NUMBERVALUE, ENTRY(SC_OPCODE_NUMBERVALUE_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_NUMBERVALUE, 3, { 0, 1, 1 }, 0 },
+ { SC_OPCODE_CHISQ_DIST, ENTRY(SC_OPCODE_CHISQ_DIST_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_CHISQDIST, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_CHISQ_INV, ENTRY(SC_OPCODE_CHISQ_INV_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_CHISQINV, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_BITAND, ENTRY(SC_OPCODE_BITAND_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_BITAND, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_BITOR, ENTRY(SC_OPCODE_BITOR_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_BITOR, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_BITXOR, ENTRY(SC_OPCODE_BITXOR_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_BITXOR, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_BITRSHIFT, ENTRY(SC_OPCODE_BITRSHIFT_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_BITRSHIFT, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_BITLSHIFT, ENTRY(SC_OPCODE_BITLSHIFT_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_BITLSHIFT, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_GET_DATEDIF, ENTRY(SC_OPCODE_GET_DATEDIF_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_DATEDIF, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_XOR, ENTRY(SC_OPCODE_XOR_ARY), 0, ID_FUNCTION_GRP_LOGIC, HID_FUNC_XOR, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_AVERAGE_IF, ENTRY(SC_OPCODE_AVERAGE_IF_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_AVERAGEIF, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_SUM_IFS, ENTRY(SC_OPCODE_SUM_IFS_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_SUMIFS, PAIRED_VAR_ARGS+1, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_AVERAGE_IFS, ENTRY(SC_OPCODE_AVERAGE_IFS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_AVERAGEIFS, PAIRED_VAR_ARGS+1, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_COUNT_IFS, ENTRY(SC_OPCODE_COUNT_IFS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_COUNTIFS, PAIRED_VAR_ARGS, { 0, 0 }, 0 },
+ { SC_OPCODE_SKEWP, ENTRY(SC_OPCODE_SKEWP_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_SKEWP, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_LENB, ENTRY(SC_OPCODE_LENB_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_LENB, 1, { 0 }, 0 },
+ { SC_OPCODE_RIGHTB, ENTRY(SC_OPCODE_RIGHTB_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_RIGHTB, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_LEFTB, ENTRY(SC_OPCODE_LEFTB_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_LEFTB, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_MIDB, ENTRY(SC_OPCODE_MIDB_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_MIDB, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_FILTERXML, ENTRY(SC_OPCODE_FILTERXML_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_FILTERXML, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_WEBSERVICE, ENTRY(SC_OPCODE_WEBSERVICE_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_WEBSERVICE, 1, { 0, 0 }, 0 },
+ { SC_OPCODE_COVARIANCE_S, ENTRY(SC_OPCODE_COVARIANCE_S_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_COVARIANCE_S, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_COVARIANCE_P, ENTRY(SC_OPCODE_COVARIANCE_P_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_COVARIANCE_P, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_ST_DEV_P_MS, ENTRY(SC_OPCODE_ST_DEV_P_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_ST_DEV_P_MS, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_ST_DEV_S, ENTRY(SC_OPCODE_ST_DEV_S_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_ST_DEV_S, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_VAR_P_MS, ENTRY(SC_OPCODE_VAR_P_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_VAR_P_MS, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_VAR_S, ENTRY(SC_OPCODE_VAR_S_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_VAR_S, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_BETA_DIST_MS, ENTRY(SC_OPCODE_BETA_DIST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_BETADIST_MS, 6, { 0, 0, 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_BETA_INV_MS, ENTRY(SC_OPCODE_BETA_INV_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_BETAINV_MS, 5, { 0, 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_BINOM_DIST_MS, ENTRY(SC_OPCODE_BINOM_DIST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_BINOM_DIST_MS, 4, { 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_BINOM_INV, ENTRY(SC_OPCODE_BINOM_INV_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_BINOM_INV_MS, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_CHI_DIST_MS, ENTRY(SC_OPCODE_CHI_DIST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_CHIVERT_MS, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_CHI_INV_MS, ENTRY(SC_OPCODE_CHI_INV_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_CHIINV_MS, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_CHI_TEST_MS, ENTRY(SC_OPCODE_CHI_TEST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_CHITEST_MS, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_CHISQ_DIST_MS, ENTRY(SC_OPCODE_CHISQ_DIST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_CHISQDIST_MS, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_CHISQ_INV_MS, ENTRY(SC_OPCODE_CHISQ_INV_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_CHISQINV_MS, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_CONFIDENCE_N, ENTRY(SC_OPCODE_CONFIDENCE_N_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_CONFIDENCE_N, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_CONFIDENCE_T, ENTRY(SC_OPCODE_CONFIDENCE_T_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_CONFIDENCE_T, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_F_DIST_LT, ENTRY(SC_OPCODE_F_DIST_LT_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_F_DIST_LT, 4, { 0, 0, 0, 1 }, 0 },
+ { SC_OPCODE_F_DIST_RT, ENTRY(SC_OPCODE_F_DIST_RT_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_F_DIST_RT, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_F_INV_LT, ENTRY(SC_OPCODE_F_INV_LT_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_F_INV_LT, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_F_INV_RT, ENTRY(SC_OPCODE_F_INV_RT_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_F_INV_RT, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_F_TEST_MS, ENTRY(SC_OPCODE_F_TEST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_F_TEST_MS, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_EXP_DIST_MS, ENTRY(SC_OPCODE_EXP_DIST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_EXP_DIST_MS, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_HYP_GEOM_DIST_MS, ENTRY(SC_OPCODE_HYP_GEOM_DIST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_HYP_GEOM_DIST_MS, 5, { 0, 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_POISSON_DIST_MS, ENTRY(SC_OPCODE_POISSON_DIST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_POISSON_DIST_MS, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_WEIBULL_MS, ENTRY(SC_OPCODE_WEIBULL_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_WEIBULL_DIST_MS, 4, { 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_GAMMA_DIST_MS, ENTRY(SC_OPCODE_GAMMA_DIST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_GAMMADIST_MS, 4, { 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_GAMMA_INV_MS, ENTRY(SC_OPCODE_GAMMA_INV_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_GAMMAINV_MS, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_LOG_NORM_DIST_MS, ENTRY(SC_OPCODE_LOG_NORM_DIST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_LOGNORMDIST_MS, 4, { 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_LOG_INV_MS, ENTRY(SC_OPCODE_LOG_INV_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_LOGINV, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_NORM_DIST_MS, ENTRY(SC_OPCODE_NORM_DIST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_NORMDIST_MS, 4, { 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_NORM_INV_MS, ENTRY(SC_OPCODE_NORM_INV_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_NORMINV_MS, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_STD_NORM_DIST_MS, ENTRY(SC_OPCODE_STD_NORM_DIST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_STD_NORMDIST_MS, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_S_NORM_INV_MS, ENTRY(SC_OPCODE_S_NORM_INV_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_STD_NORMINV_MS, 1, { 0 }, 0 },
+ { SC_OPCODE_T_DIST_MS, ENTRY(SC_OPCODE_T_DIST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_TDIST_MS, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_T_DIST_RT, ENTRY(SC_OPCODE_T_DIST_RT_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_TDIST_RT, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_T_DIST_2T, ENTRY(SC_OPCODE_T_DIST_2T_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_TDIST_2T, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_T_INV_2T, ENTRY(SC_OPCODE_T_INV_2T_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_TINV_2T, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_T_INV_MS, ENTRY(SC_OPCODE_T_INV_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_TINV_MS, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_T_TEST_MS, ENTRY(SC_OPCODE_T_TEST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_TTEST_MS, 4, { 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_PERCENTILE_INC, ENTRY(SC_OPCODE_PERCENTILE_INC_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_PERCENTILE_INC, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_PERCENT_RANK_INC, ENTRY(SC_OPCODE_PERCENT_RANK_INC_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_PERCENTRANK_INC, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_QUARTILE_INC, ENTRY(SC_OPCODE_QUARTILE_INC_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_QUARTILE_INC, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_RANK_EQ, ENTRY(SC_OPCODE_RANK_EQ_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_RANK_EQ, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_PERCENTILE_EXC, ENTRY(SC_OPCODE_PERCENTILE_EXC_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_PERCENTILE_EXC, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_PERCENT_RANK_EXC, ENTRY(SC_OPCODE_PERCENT_RANK_EXC_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_PERCENTRANK_EXC, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_QUARTILE_EXC, ENTRY(SC_OPCODE_QUARTILE_EXC_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_QUARTILE_EXC, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_RANK_AVG, ENTRY(SC_OPCODE_RANK_AVG_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_RANK_AVG, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_MODAL_VALUE_MS, ENTRY(SC_OPCODE_MODAL_VALUE_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_MODAL_VALUE_MS, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_MODAL_VALUE_MULTI, ENTRY(SC_OPCODE_MODAL_VALUE_MULTI_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_MODAL_VALUE_MULTI, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_NEG_BINOM_DIST_MS, ENTRY(SC_OPCODE_NEG_BINOM_DIST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_NEGBINOMDIST_MS, 4, { 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_Z_TEST_MS, ENTRY(SC_OPCODE_Z_TEST_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_Z_TEST_MS, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_CEIL_MS, ENTRY(SC_OPCODE_CEIL_MS_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_CEIL_MS, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_CEIL_ISO, ENTRY(SC_OPCODE_CEIL_ISO_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_CEIL_ISO, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_FLOOR_MS, ENTRY(SC_OPCODE_FLOOR_MS_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_FLOOR_MS, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_NETWORKDAYS_MS, ENTRY(SC_OPCODE_NETWORKDAYS_MS_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_NETWORKDAYS_MS, 4, { 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_WORKDAY_MS, ENTRY(SC_OPCODE_WORKDAY_MS_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_WORKDAY_MS, 4, { 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_AGGREGATE, ENTRY(SC_OPCODE_AGGREGATE_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_AGGREGATE, VAR_ARGS+3, { 0, 0, 0, 1 }, 0 },
+ { SC_OPCODE_COLOR, ENTRY(SC_OPCODE_COLOR_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_COLOR, 4, { 0, 0, 0, 1 }, 0 },
+ { SC_OPCODE_CEIL_MATH, ENTRY(SC_OPCODE_CEIL_MATH_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_CEIL_MATH, 3, { 0, 1, 1 }, 0 },
+ { SC_OPCODE_CEIL_PRECISE, ENTRY(SC_OPCODE_CEIL_PRECISE_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_CEIL_PRECISE, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_NETWORKDAYS, ENTRY(SC_OPCODE_NETWORKDAYS_ARY), 0, ID_FUNCTION_GRP_DATETIME, HID_FUNC_NETWORKDAYS, 4, { 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_FLOOR_MATH, ENTRY(SC_OPCODE_FLOOR_MATH_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_FLOOR_MATH, 3, { 0, 1, 1 }, 0 },
+ { SC_OPCODE_FLOOR_PRECISE, ENTRY(SC_OPCODE_FLOOR_PRECISE_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_FLOOR_PRECISE, 2, { 0, 1 }, 0 },
+ { SC_OPCODE_RAWSUBTRACT, ENTRY(SC_OPCODE_RAWSUBTRACT_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_RAWSUBTRACT, VAR_ARGS+2, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_WEEKNUM_OOO, ENTRY(SC_OPCODE_WEEKNUM_OOO_ARY), 2, ID_FUNCTION_GRP_DATETIME, HID_FUNC_WEEKNUM_OOO, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_FORECAST_ETS_ADD, ENTRY(SC_OPCODE_FORECAST_ETS_ADD_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_FORECAST_ETS_ADD, 6, { 0, 0, 0, 1, 1, 1 }, 0 },
+ { SC_OPCODE_FORECAST_ETS_SEA, ENTRY(SC_OPCODE_FORECAST_ETS_SEA_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_FORECAST_ETS_SEA, 4, { 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_FORECAST_ETS_MUL, ENTRY(SC_OPCODE_FORECAST_ETS_MUL_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_FORECAST_ETS_MUL, 6, { 0, 0, 0, 1, 1, 1 }, 0 },
+ { SC_OPCODE_FORECAST_ETS_PIA, ENTRY(SC_OPCODE_FORECAST_ETS_PIA_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_FORECAST_ETS_PIA, 7, { 0, 0, 0, 1, 1, 1, 1 }, 0 },
+ { SC_OPCODE_FORECAST_ETS_PIM, ENTRY(SC_OPCODE_FORECAST_ETS_PIM_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_FORECAST_ETS_PIM, 7, { 0, 0, 0, 1, 1, 1, 1 }, 0 },
+ { SC_OPCODE_FORECAST_ETS_STA, ENTRY(SC_OPCODE_FORECAST_ETS_STA_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_FORECAST_ETS_STA, 6, { 0, 0, 0, 1, 1, 1 }, 0 },
+ { SC_OPCODE_FORECAST_ETS_STM, ENTRY(SC_OPCODE_FORECAST_ETS_STM_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_FORECAST_ETS_STM, 6, { 0, 0, 0, 1, 1, 1 }, 0 },
+ { SC_OPCODE_FORECAST_LIN, ENTRY(SC_OPCODE_FORECAST_LIN_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_FORECAST_LIN, 3, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_CONCAT_MS, ENTRY(SC_OPCODE_CONCAT_MS_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_CONCAT_MS, VAR_ARGS, { 0 }, 0 },
+ { SC_OPCODE_TEXTJOIN_MS, ENTRY(SC_OPCODE_TEXTJOIN_MS_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_TEXTJOIN_MS, VAR_ARGS + 2, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_IFS_MS, ENTRY(SC_OPCODE_IFS_MS_ARY), 0, ID_FUNCTION_GRP_LOGIC, HID_FUNC_IFS_MS, PAIRED_VAR_ARGS, { 0, 0 }, 0 },
+ { SC_OPCODE_SWITCH_MS, ENTRY(SC_OPCODE_SWITCH_MS_ARY), 0, ID_FUNCTION_GRP_LOGIC, HID_FUNC_SWITCH_MS, PAIRED_VAR_ARGS + 1, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_MINIFS_MS, ENTRY(SC_OPCODE_MINIFS_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_MINIFS_MS, PAIRED_VAR_ARGS + 1, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_MAXIFS_MS, ENTRY(SC_OPCODE_MAXIFS_MS_ARY), 0, ID_FUNCTION_GRP_STATISTIC, HID_FUNC_MAXIFS_MS, PAIRED_VAR_ARGS + 1, { 0, 0, 0 }, 0 },
+ { SC_OPCODE_ROUNDSIG, ENTRY(SC_OPCODE_ROUNDSIG_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_ROUNDSIG, 2, { 0, 0 }, 0 },
+ { SC_OPCODE_REPLACEB, ENTRY(SC_OPCODE_REPLACEB_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_REPLACEB, 4, { 0, 0, 0, 0 }, 0 },
+ { SC_OPCODE_FINDB, ENTRY(SC_OPCODE_FINDB_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_FINDB, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_SEARCHB, ENTRY(SC_OPCODE_SEARCHB_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_SEARCHB, 3, { 0, 0, 1 }, 0 },
+ { SC_OPCODE_REGEX, ENTRY(SC_OPCODE_REGEX_ARY), 0, ID_FUNCTION_GRP_TEXT, HID_FUNC_REGEX, 4, { 0, 0, 1, 1 }, 0 },
+ { SC_OPCODE_FOURIER, ENTRY(SC_OPCODE_FOURIER_ARY), 0, ID_FUNCTION_GRP_MATRIX, HID_FUNC_FOURIER, 5, { 0, 0, 1, 1, 1 }, 0 },
+ { SC_OPCODE_RANDBETWEEN_NV, ENTRY(SC_OPCODE_RANDBETWEEN_NV_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_RANDBETWEEN_NV, 2, { 0, 0 }, 0 }
+ };
+
+ ScFuncDesc* pDesc = nullptr;
+ sal_Int32 nStrLen = 0;
+ ::std::vector<const ScFuncDesc*> tmpFuncVector;
+
+ // Browse for all possible OpCodes. This is not the fastest method, but
+ // otherwise the sub resources within the resource blocks and the
+ // resource blocks themselves would had to be ordered according to
+ // OpCodes, which is utopian...
+ ScFuncDescCore const * pDescsEnd = aDescs + SAL_N_ELEMENTS(aDescs);
+ for (sal_uInt16 i = 0; i <= SC_OPCODE_LAST_OPCODE_ID; ++i)
+ {
+ const ScFuncDescCore *pEntry = std::lower_bound(aDescs, pDescsEnd, i,
+ [](const ScFuncDescCore &rItem, sal_uInt16 key)
+ {
+ return rItem.nOpCode < key;
+ }
+ );
+
+ // Opcode Resource available?
+ if (pEntry != pDescsEnd && pEntry->nOpCode == i && pEntry->pResource)
+ {
+ pDesc = new ScFuncDesc;
+ bool bSuppressed = false;
+
+ ScFuncRes(*pEntry, pDesc, bSuppressed);
+ // Instead of dealing with this exceptional case at 1001 places
+ // we simply don't add an entirely suppressed function to the
+ // list and delete it.
+ if (bSuppressed)
+ delete pDesc;
+ else
+ {
+ pDesc->nFIndex = i;
+ tmpFuncVector.push_back(pDesc);
+
+ nStrLen = pDesc->mxFuncName->getLength();
+ if (nStrLen > nMaxFuncNameLen)
+ nMaxFuncNameLen = nStrLen;
+ }
+ }
+ }
+
+ // legacy binary AddIn functions
+
+ sal_uInt16 nNextId = SC_OPCODE_LAST_OPCODE_ID + 1; // FuncID for AddIn functions
+
+ // Interpretation of AddIn list
+ OUString aDefArgNameValue = "value";
+ OUString aDefArgNameString = "string";
+ OUString aDefArgNameValues = "values";
+ OUString aDefArgNameStrings = "strings";
+ OUString aDefArgNameCells = "cells";
+ OUString aDefArgNameNone = "none";
+ OUString aDefArgDescValue = "a value";
+ OUString aDefArgDescString = "a string";
+ OUString aDefArgDescValues = "array of values";
+ OUString aDefArgDescStrings = "array of strings";
+ OUString aDefArgDescCells = "range of cells";
+ OUString aDefArgDescNone = "none";
+
+ OUString aArgName, aArgDesc;
+ const LegacyFuncCollection& rLegacyFuncColl = *ScGlobal::GetLegacyFuncCollection();
+ for (auto const& legacyFunc : rLegacyFuncColl)
+ {
+ const LegacyFuncData *const pLegacyFuncData = legacyFunc.second.get();
+ pDesc = new ScFuncDesc;
+ sal_uInt16 nArgs = pLegacyFuncData->GetParamCount() - 1;
+ pLegacyFuncData->getParamDesc( aArgName, aArgDesc, 0 );
+ pDesc->nFIndex = nNextId++; // ??? OpCode vergeben
+ pDesc->nCategory = ID_FUNCTION_GRP_ADDINS;
+ pDesc->mxFuncName = pLegacyFuncData->GetInternalName().toAsciiUpperCase();
+ pDesc->mxFuncDesc = aArgDesc + "\n"
+ "( AddIn: " + pLegacyFuncData->GetModuleName() + " )";
+ pDesc->nArgCount = nArgs;
+ if (nArgs)
+ {
+ pDesc->maDefArgNames.clear();
+ pDesc->maDefArgNames.resize(nArgs);
+ pDesc->maDefArgDescs.clear();
+ pDesc->maDefArgDescs.resize(nArgs);
+ pDesc->pDefArgFlags = new ScFuncDesc::ParameterFlags[nArgs];
+ for (sal_uInt16 j = 0; j < nArgs; ++j)
+ {
+ pDesc->pDefArgFlags[j].bOptional = false;
+ pLegacyFuncData->getParamDesc( aArgName, aArgDesc, j+1 );
+ if ( !aArgName.isEmpty() )
+ pDesc->maDefArgNames[j] = aArgName;
+ else
+ {
+ switch (pLegacyFuncData->GetParamType(j+1))
+ {
+ case ParamType::PTR_DOUBLE:
+ pDesc->maDefArgNames[j] = aDefArgNameValue;
+ break;
+ case ParamType::PTR_STRING:
+ pDesc->maDefArgNames[j] = aDefArgNameString;
+ break;
+ case ParamType::PTR_DOUBLE_ARR:
+ pDesc->maDefArgNames[j] = aDefArgNameValues;
+ break;
+ case ParamType::PTR_STRING_ARR:
+ pDesc->maDefArgNames[j] = aDefArgNameStrings;
+ break;
+ case ParamType::PTR_CELL_ARR:
+ pDesc->maDefArgNames[j] = aDefArgNameCells;
+ break;
+ default:
+ pDesc->maDefArgNames[j] = aDefArgNameNone;
+ break;
+ }
+ }
+ if ( !aArgDesc.isEmpty() )
+ pDesc->maDefArgDescs[j] = aArgDesc;
+ else
+ {
+ switch (pLegacyFuncData->GetParamType(j+1))
+ {
+ case ParamType::PTR_DOUBLE:
+ pDesc->maDefArgDescs[j] = aDefArgDescValue;
+ break;
+ case ParamType::PTR_STRING:
+ pDesc->maDefArgDescs[j] = aDefArgDescString;
+ break;
+ case ParamType::PTR_DOUBLE_ARR:
+ pDesc->maDefArgDescs[j] = aDefArgDescValues;
+ break;
+ case ParamType::PTR_STRING_ARR:
+ pDesc->maDefArgDescs[j] = aDefArgDescStrings;
+ break;
+ case ParamType::PTR_CELL_ARR:
+ pDesc->maDefArgDescs[j] = aDefArgDescCells;
+ break;
+ default:
+ pDesc->maDefArgDescs[j] = aDefArgDescNone;
+ break;
+ }
+ }
+ }
+ }
+
+ tmpFuncVector.push_back(pDesc);
+ nStrLen = pDesc->mxFuncName->getLength();
+ if ( nStrLen > nMaxFuncNameLen)
+ nMaxFuncNameLen = nStrLen;
+ }
+
+ // StarOne AddIns
+
+ ScUnoAddInCollection* pUnoAddIns = ScGlobal::GetAddInCollection();
+ tools::Long nUnoCount = pUnoAddIns->GetFuncCount();
+ for (tools::Long nFunc=0; nFunc<nUnoCount; nFunc++)
+ {
+ pDesc = new ScFuncDesc;
+ pDesc->nFIndex = nNextId++;
+
+ if ( pUnoAddIns->FillFunctionDesc( nFunc, *pDesc, mbEnglishFunctionNames ) )
+ {
+ tmpFuncVector.push_back(pDesc);
+ nStrLen = pDesc->mxFuncName->getLength();
+ if (nStrLen > nMaxFuncNameLen)
+ nMaxFuncNameLen = nStrLen;
+ }
+ else
+ delete pDesc;
+ }
+
+ aFunctionList.swap(tmpFuncVector);
+
+ //Initialize iterator
+ aFunctionListIter = aFunctionList.end();
+}
+
+ScFunctionList::~ScFunctionList()
+{
+ const ScFuncDesc* pDesc = First();
+ while (pDesc)
+ {
+ delete pDesc;
+ pDesc = Next();
+ }
+}
+
+const ScFuncDesc* ScFunctionList::First()
+{
+ const ScFuncDesc* pDesc = nullptr;
+ aFunctionListIter = aFunctionList.begin();
+ if(aFunctionListIter != aFunctionList.end())
+ pDesc = *aFunctionListIter;
+
+ return pDesc;
+}
+
+const ScFuncDesc* ScFunctionList::Next()
+{
+ const ScFuncDesc* pDesc = nullptr;
+ if(aFunctionListIter != aFunctionList.end())
+ {
+ if((++aFunctionListIter) != aFunctionList.end())
+ pDesc = *aFunctionListIter;
+ }
+ return pDesc;
+}
+
+const ScFuncDesc* ScFunctionList::GetFunction( sal_uInt32 nIndex ) const
+{
+ const ScFuncDesc* pDesc = nullptr;
+ if(nIndex < aFunctionList.size())
+ pDesc = aFunctionList.at(nIndex);
+
+ return pDesc;
+}
+
+
+sal_uInt32 ScFunctionCategory::getCount() const
+{
+ return m_rCategory.size();
+}
+
+OUString ScFunctionCategory::getName() const
+{
+ if ( m_sName.isEmpty() )
+ m_sName = ScFunctionMgr::GetCategoryName(m_nCategory);
+ return m_sName;
+}
+
+const formula::IFunctionDescription* ScFunctionCategory::getFunction(sal_uInt32 _nPos) const
+{
+ const ScFuncDesc* pDesc = nullptr;
+ if(_nPos < m_rCategory.size())
+ pDesc = m_rCategory.at(_nPos);
+ return pDesc;
+}
+
+sal_uInt32 ScFunctionCategory::getNumber() const
+{
+ return m_nCategory;
+}
+
+
+ScFunctionMgr::ScFunctionMgr()
+{
+ ScFunctionList* pFuncList /**< list of all calc functions */
+ = ScGlobal::GetStarCalcFunctionList();
+
+ OSL_ENSURE( pFuncList, "Functionlist not found." );
+ sal_uInt32 catCount[MAX_FUNCCAT] = {0};
+
+ aCatLists[0].reserve(pFuncList->GetCount());
+
+ // Retrieve all functions, store in cumulative ("All") category, and count
+ // number of functions in each category
+ for(const ScFuncDesc* pDesc = pFuncList->First(); pDesc; pDesc = pFuncList->Next())
+ {
+ OSL_ENSURE((pDesc->nCategory) < MAX_FUNCCAT, "Unknown category");
+ if ((pDesc->nCategory) < MAX_FUNCCAT)
+ ++catCount[pDesc->nCategory];
+ aCatLists[0].push_back(pDesc);
+ }
+
+ // Sort functions in cumulative category by name
+ ::std::sort(aCatLists[0].begin(), aCatLists[0].end(), ScFuncDesc::compareByName);
+
+ // Allocate correct amount of space for categories
+ for (sal_uInt16 i = 1; i < MAX_FUNCCAT; ++i)
+ {
+ aCatLists[i].reserve(catCount[i]);
+ }
+
+ // Fill categories with the corresponding functions (still sorted by name)
+ for (auto const& elemList : aCatLists[0])
+ {
+ if ((elemList->nCategory) < MAX_FUNCCAT)
+ aCatLists[elemList->nCategory].push_back(elemList);
+ }
+
+ // Initialize iterators
+ pCurCatListIter = aCatLists[0].end();
+ pCurCatListEnd = aCatLists[0].end();
+}
+
+ScFunctionMgr::~ScFunctionMgr()
+{
+}
+
+
+const ScFuncDesc* ScFunctionMgr::Get( sal_uInt16 nFIndex ) const
+{
+ const ScFuncDesc* pDesc;
+ for (pDesc = First(); pDesc; pDesc = Next())
+ if (pDesc->nFIndex == nFIndex)
+ break;
+ return pDesc;
+}
+
+const ScFuncDesc* ScFunctionMgr::First( sal_uInt16 nCategory ) const
+{
+ OSL_ENSURE( nCategory < MAX_FUNCCAT, "Unknown category" );
+ const ScFuncDesc* pDesc = nullptr;
+ if ( nCategory < MAX_FUNCCAT )
+ {
+ pCurCatListIter = aCatLists[nCategory].begin();
+ pCurCatListEnd = aCatLists[nCategory].end();
+ pDesc = *pCurCatListIter;
+ }
+ else
+ {
+ pCurCatListIter = aCatLists[0].end();
+ pCurCatListEnd = aCatLists[0].end();
+ }
+ return pDesc;
+}
+
+const ScFuncDesc* ScFunctionMgr::Next() const
+{
+ const ScFuncDesc* pDesc = nullptr;
+ if ( pCurCatListIter != pCurCatListEnd )
+ {
+ if ( (++pCurCatListIter) != pCurCatListEnd )
+ {
+ pDesc = *pCurCatListIter;
+ }
+ }
+ return pDesc;
+}
+
+sal_uInt32 ScFunctionMgr::getCount() const
+{
+ return MAX_FUNCCAT - 1;
+}
+
+const formula::IFunctionCategory* ScFunctionMgr::getCategory(sal_uInt32 nCategory) const
+{
+ if ( nCategory < (MAX_FUNCCAT-1) )
+ {
+ if (m_aCategories.find(nCategory) == m_aCategories.end())
+ m_aCategories[nCategory] = std::make_shared<ScFunctionCategory>(aCatLists[nCategory+1],nCategory); // aCatLists[0] is "all"
+ return m_aCategories[nCategory].get();
+ }
+ return nullptr;
+}
+
+void ScFunctionMgr::fillLastRecentlyUsedFunctions(::std::vector< const formula::IFunctionDescription*>& _rLastRUFunctions) const
+{
+ const ScAppOptions& rAppOpt = SC_MOD()->GetAppOptions();
+ sal_uInt16 nLRUFuncCount = std::min( rAppOpt.GetLRUFuncListCount(), sal_uInt16(LRU_MAX) );
+ sal_uInt16* pLRUListIds = rAppOpt.GetLRUFuncList();
+ _rLastRUFunctions.clear();
+
+ if ( pLRUListIds )
+ {
+ for (sal_uInt16 i = 0; i < nLRUFuncCount; ++i)
+ {
+ _rLastRUFunctions.push_back( Get( pLRUListIds[i] ) );
+ }
+ }
+}
+
+OUString ScFunctionMgr::GetCategoryName(sal_uInt32 _nCategoryNumber )
+{
+ if (_nCategoryNumber >= SC_FUNCGROUP_COUNT)
+ {
+ OSL_FAIL("Invalid category number!");
+ return OUString();
+ }
+
+ return ScResId(RID_FUNCTION_CATEGORIES[_nCategoryNumber]);
+}
+
+sal_Unicode ScFunctionMgr::getSingleToken(const formula::IFunctionManager::EToken _eToken) const
+{
+ switch(_eToken)
+ {
+ case eOk:
+ return ScCompiler::GetNativeSymbolChar(ocOpen);
+ case eClose:
+ return ScCompiler::GetNativeSymbolChar(ocClose);
+ case eSep:
+ return ScCompiler::GetNativeSymbolChar(ocSep);
+ case eArrayOpen:
+ return ScCompiler::GetNativeSymbolChar(ocArrayOpen);
+ case eArrayClose:
+ return ScCompiler::GetNativeSymbolChar(ocArrayClose);
+ }
+ return 0;
+}
+
+static void ScFuncRes(const ScFuncDescCore &rEntry, ScFuncDesc* pDesc, bool& rbSuppressed)
+{
+ const sal_uInt16 nOpCode = rEntry.nOpCode;
+ sal_uInt16 nFunctionFlags = rEntry.nFunctionFlags;
+ // Bit 1: entirely suppressed
+ // Bit 2: hidden unless used
+ rbSuppressed = ((nFunctionFlags & 1) != 0);
+ pDesc->mbHidden = ((nFunctionFlags & 2) != 0);
+ pDesc->nCategory = rEntry.nCategory;
+ pDesc->sHelpId = rEntry.pHelpId;
+ pDesc->nArgCount = rEntry.nArgs;
+ sal_uInt16 nArgs = pDesc->nArgCount;
+ sal_uInt16 nVarArgsSet = 0;
+ if (nArgs >= PAIRED_VAR_ARGS)
+ {
+ nVarArgsSet = 2;
+ nArgs -= PAIRED_VAR_ARGS - nVarArgsSet;
+ }
+ else if (nArgs >= VAR_ARGS)
+ {
+ nVarArgsSet = 1;
+ nArgs -= VAR_ARGS - nVarArgsSet;
+ }
+ assert(nArgs <= SAL_N_ELEMENTS(rEntry.aOptionalArgs));
+ if (nArgs)
+ {
+ pDesc->nVarArgsStart = nArgs - nVarArgsSet;
+ pDesc->nVarArgsLimit = rEntry.nVarArgsLimit;
+ pDesc->pDefArgFlags = new ScFuncDesc::ParameterFlags[nArgs];
+ for (sal_uInt16 i = 0; i < nArgs; ++i)
+ {
+ pDesc->pDefArgFlags[i].bOptional = static_cast<bool>(rEntry.aOptionalArgs[i]);
+ }
+ }
+
+ pDesc->mxFuncName = ScCompiler::GetNativeSymbol(static_cast<OpCode>(nOpCode));
+ pDesc->mxFuncDesc = ScResId(rEntry.pResource[0]);
+
+ if (!nArgs)
+ return;
+
+ pDesc->maDefArgNames.clear();
+ pDesc->maDefArgNames.resize(nArgs);
+ pDesc->maDefArgDescs.clear();
+ pDesc->maDefArgDescs.resize(nArgs);
+ for (sal_uInt16 i = 0; i < nArgs; ++i)
+ {
+ size_t nIndex = (i * 2) + 1;
+ if (nIndex < rEntry.nResourceLen)
+ pDesc->maDefArgNames[i] = ScResId(rEntry.pResource[nIndex]);
+ if (nIndex + 1 < rEntry.nResourceLen)
+ pDesc->maDefArgDescs[i] = ScResId(rEntry.pResource[nIndex + 1]);
+ // If empty and variable number of arguments and last parameter and
+ // parameter is optional and the previous is not optional, repeat
+ // previous parameter name and description.
+ if ((pDesc->maDefArgNames[i].isEmpty() || pDesc->maDefArgDescs[i].isEmpty()) &&
+ nVarArgsSet > 0 && i > nVarArgsSet && (i == nArgs-1 || i == nArgs-2) &&
+ pDesc->pDefArgFlags[i].bOptional)
+ {
+ sal_uInt16 nPrev = i - nVarArgsSet;
+ if (!pDesc->pDefArgFlags[nPrev].bOptional)
+ {
+ if (pDesc->maDefArgNames[i].isEmpty())
+ pDesc->maDefArgNames[i] = pDesc->maDefArgNames[nPrev];
+ if (pDesc->maDefArgDescs[i].isEmpty())
+ pDesc->maDefArgDescs[i] = pDesc->maDefArgDescs[nPrev];
+ // This also means that variable arguments start one
+ // parameter set earlier.
+ pDesc->nVarArgsStart -= nVarArgsSet;
+ }
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/global.cxx b/sc/source/core/data/global.cxx
new file mode 100644
index 000000000..a7b63ce1e
--- /dev/null
+++ b/sc/source/core/data/global.cxx
@@ -0,0 +1,1160 @@
+/* -*- 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 <svx/algitem.hxx>
+#include <editeng/brushitem.hxx>
+#include <editeng/editobj.hxx>
+#include <svl/itempool.hxx>
+#include <svl/srchitem.hxx>
+#include <editeng/langitem.hxx>
+#include <o3tl/string_view.hxx>
+#include <o3tl/unit_conversion.hxx>
+#include <sfx2/docfile.hxx>
+#include <sfx2/dispatch.hxx>
+#include <sfx2/objsh.hxx>
+#include <sfx2/sfxsids.hrc>
+#include <sfx2/viewfrm.hxx>
+#include <sfx2/viewsh.hxx>
+#include <svl/intitem.hxx>
+#include <svl/numformat.hxx>
+#include <svl/stritem.hxx>
+#include <svl/zforlist.hxx>
+#include <svl/zformat.hxx>
+#include <vcl/keycodes.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <unotools/charclass.hxx>
+#include <unotools/securityoptions.hxx>
+#include <osl/diagnose.h>
+
+#include <i18nlangtag/mslangid.hxx>
+#include <comphelper/doublecheckedinit.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/string.hxx>
+#include <unotools/calendarwrapper.hxx>
+#include <unotools/collatorwrapper.hxx>
+#include <unotools/syslocale.hxx>
+#include <unotools/transliterationwrapper.hxx>
+
+#include <comphelper/lok.hxx>
+
+#include <global.hxx>
+#include <scresid.hxx>
+#include <autoform.hxx>
+#include <patattr.hxx>
+#include <addincol.hxx>
+#include <adiasync.hxx>
+#include <userlist.hxx>
+#include <interpre.hxx>
+#include <unitconv.hxx>
+#include <compiler.hxx>
+#include <parclass.hxx>
+#include <funcdesc.hxx>
+#include <globstr.hrc>
+#include <strings.hrc>
+#include <scmod.hxx>
+#include <editutil.hxx>
+#include <docsh.hxx>
+#include <sharedstringpoolpurge.hxx>
+#include <formulaopt.hxx>
+
+tools::SvRef<ScDocShell> ScGlobal::xDrawClipDocShellRef;
+std::unique_ptr<SvxSearchItem> ScGlobal::xSearchItem;
+std::unique_ptr<ScAutoFormat> ScGlobal::xAutoFormat;
+std::atomic<LegacyFuncCollection*> ScGlobal::pLegacyFuncCollection(nullptr);
+std::atomic<ScUnoAddInCollection*> ScGlobal::pAddInCollection(nullptr);
+std::unique_ptr<ScUserList> ScGlobal::xUserList;
+LanguageType ScGlobal::eLnge = LANGUAGE_SYSTEM;
+std::atomic<css::lang::Locale*> ScGlobal::pLocale(nullptr);
+std::optional<SvtSysLocale> ScGlobal::oSysLocale;
+std::optional<CalendarWrapper> ScGlobal::oCalendar;
+std::atomic<CollatorWrapper*> ScGlobal::pCollator(nullptr);
+std::atomic<CollatorWrapper*> ScGlobal::pCaseCollator(nullptr);
+std::atomic<::utl::TransliterationWrapper*> ScGlobal::pTransliteration(nullptr);
+std::atomic<::utl::TransliterationWrapper*> ScGlobal::pCaseTransliteration(nullptr);
+css::uno::Reference< css::i18n::XOrdinalSuffix> ScGlobal::xOrdinalSuffix;
+OUString ScGlobal::aStrClipDocName;
+
+std::unique_ptr<SvxBrushItem> ScGlobal::xEmptyBrushItem;
+std::unique_ptr<SvxBrushItem> ScGlobal::xButtonBrushItem;
+
+std::unique_ptr<ScFunctionList> ScGlobal::xStarCalcFunctionList;
+std::unique_ptr<ScFunctionMgr> ScGlobal::xStarCalcFunctionMgr;
+
+std::atomic<ScUnitConverter*> ScGlobal::pUnitConverter(nullptr);
+std::unique_ptr<SvNumberFormatter> ScGlobal::xEnglishFormatter;
+std::unique_ptr<ScFieldEditEngine> ScGlobal::xFieldEditEngine;
+std::atomic<sc::SharedStringPoolPurge*> ScGlobal::pSharedStringPoolPurge;
+
+double ScGlobal::nScreenPPTX = 96.0;
+double ScGlobal::nScreenPPTY = 96.0;
+
+sal_uInt16 ScGlobal::nDefFontHeight = 225;
+sal_uInt16 ScGlobal::nStdRowHeight = 256;
+
+tools::Long ScGlobal::nLastRowHeightExtra = 0;
+tools::Long ScGlobal::nLastColWidthExtra = STD_EXTRA_WIDTH;
+
+SfxViewShell* pScActiveViewShell = nullptr; //FIXME: Make this a member
+sal_uInt16 nScClickMouseModifier = 0; //FIXME: This too
+sal_uInt16 nScFillModeMouseModifier = 0; //FIXME: And this
+
+bool ScGlobal::bThreadedGroupCalcInProgress = false;
+
+InputHandlerFunctionNames ScGlobal::maInputHandlerFunctionNames;
+
+
+// Static functions
+
+bool ScGlobal::HasAttrChanged( const SfxItemSet& rNewAttrs,
+ const SfxItemSet& rOldAttrs,
+ const sal_uInt16 nWhich )
+{
+ bool bInvalidate = false;
+ const SfxPoolItem* pNewItem = nullptr;
+ const SfxItemState eNewState = rNewAttrs.GetItemState( nWhich, true, &pNewItem );
+ const SfxPoolItem* pOldItem = nullptr;
+ const SfxItemState eOldState = rOldAttrs.GetItemState( nWhich, true, &pOldItem );
+
+ if ( eNewState == eOldState )
+ {
+ // Both Items set
+ // PoolItems, meaning comparing pointers is valid
+ if ( SfxItemState::SET == eOldState )
+ bInvalidate = (pNewItem != pOldItem);
+ }
+ else
+ {
+ // Contains a Default Item
+ // PoolItems, meaning Item comparison necessary
+ if (!pOldItem)
+ pOldItem = &rOldAttrs.GetPool()->GetDefaultItem( nWhich );
+
+ if (!pNewItem)
+ pNewItem = &rNewAttrs.GetPool()->GetDefaultItem( nWhich );
+
+ bInvalidate = (*pNewItem != *pOldItem);
+ }
+
+ return bInvalidate;
+}
+
+sal_uInt32 ScGlobal::GetStandardFormat( SvNumberFormatter& rFormatter,
+ sal_uInt32 nFormat, SvNumFormatType nType )
+{
+ const SvNumberformat* pFormat = rFormatter.GetEntry( nFormat );
+ if ( pFormat )
+ return rFormatter.GetStandardFormat( nFormat, nType, pFormat->GetLanguage() );
+ return rFormatter.GetStandardFormat( nType, eLnge );
+}
+
+sal_uInt16 ScGlobal::GetStandardRowHeight()
+{
+ return nStdRowHeight;
+}
+
+SvNumberFormatter* ScGlobal::GetEnglishFormatter()
+{
+ assert(!bThreadedGroupCalcInProgress);
+ if ( !xEnglishFormatter )
+ {
+ xEnglishFormatter.reset( new SvNumberFormatter(
+ ::comphelper::getProcessComponentContext(), LANGUAGE_ENGLISH_US ) );
+ xEnglishFormatter->SetEvalDateFormat( NF_EVALDATEFORMAT_INTL_FORMAT );
+ }
+ return xEnglishFormatter.get();
+}
+
+bool ScGlobal::CheckWidthInvalidate( bool& bNumFormatChanged,
+ const SfxItemSet& rNewAttrs,
+ const SfxItemSet& rOldAttrs )
+{
+ std::optional<bool> equal = ScPatternAttr::FastEqualPatternSets( rNewAttrs, rOldAttrs );
+ if( equal.has_value() && equal )
+ {
+ bNumFormatChanged = false;
+ return false;
+ }
+ // Check whether attribute changes in rNewAttrs compared to rOldAttrs render
+ // the text width at a cell invalid
+ bNumFormatChanged =
+ HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_VALUE_FORMAT );
+ return ( bNumFormatChanged
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_LANGUAGE_FORMAT )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_CJK_FONT )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_CTL_FONT )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT_HEIGHT )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_CJK_FONT_HEIGHT )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_CTL_FONT_HEIGHT )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT_WEIGHT )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_CJK_FONT_WEIGHT )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_CTL_FONT_WEIGHT )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT_POSTURE )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_CJK_FONT_POSTURE )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_CTL_FONT_POSTURE )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT_UNDERLINE )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT_OVERLINE )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT_CROSSEDOUT )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT_CONTOUR )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_FONT_SHADOWED )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_STACKED )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_ROTATE_VALUE )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_ROTATE_MODE )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_LINEBREAK )
+ || HasAttrChanged( rNewAttrs, rOldAttrs, ATTR_MARGIN )
+ );
+}
+
+const SvxSearchItem& ScGlobal::GetSearchItem()
+{
+ assert(!bThreadedGroupCalcInProgress);
+ if (!xSearchItem)
+ {
+ xSearchItem.reset(new SvxSearchItem( SID_SEARCH_ITEM ));
+ xSearchItem->SetAppFlag( SvxSearchApp::CALC );
+ }
+ return *xSearchItem;
+}
+
+void ScGlobal::SetSearchItem( const SvxSearchItem& rNew )
+{
+ assert(!bThreadedGroupCalcInProgress);
+ // FIXME: An assignment operator would be nice here
+ xSearchItem.reset(rNew.Clone());
+
+ xSearchItem->SetWhich( SID_SEARCH_ITEM );
+ xSearchItem->SetAppFlag( SvxSearchApp::CALC );
+}
+
+void ScGlobal::ClearAutoFormat()
+{
+ assert(!bThreadedGroupCalcInProgress);
+ if (xAutoFormat)
+ {
+ // When modified via StarOne then only the SaveLater flag is set and no saving is done.
+ // If the flag is set then save now.
+ if (xAutoFormat->IsSaveLater())
+ xAutoFormat->Save();
+ xAutoFormat.reset();
+ }
+}
+
+ScAutoFormat* ScGlobal::GetAutoFormat()
+{
+ return xAutoFormat.get();
+}
+
+ScAutoFormat* ScGlobal::GetOrCreateAutoFormat()
+{
+ assert(!bThreadedGroupCalcInProgress);
+ if ( !xAutoFormat )
+ {
+ xAutoFormat.reset(new ScAutoFormat);
+ xAutoFormat->Load();
+ }
+
+ return xAutoFormat.get();
+}
+
+LegacyFuncCollection* ScGlobal::GetLegacyFuncCollection()
+{
+ return comphelper::doubleCheckedInit( pLegacyFuncCollection, []() { return new LegacyFuncCollection(); });
+}
+
+ScUnoAddInCollection* ScGlobal::GetAddInCollection()
+{
+ return comphelper::doubleCheckedInit( pAddInCollection, []() { return new ScUnoAddInCollection(); });
+}
+
+ScUserList* ScGlobal::GetUserList()
+{
+ assert(!bThreadedGroupCalcInProgress);
+ // Hack: Load Cfg item at the App
+ global_InitAppOptions();
+
+ if (!xUserList)
+ xUserList.reset(new ScUserList());
+ return xUserList.get();
+}
+
+void ScGlobal::SetUserList( const ScUserList* pNewList )
+{
+ assert(!bThreadedGroupCalcInProgress);
+ if ( pNewList )
+ {
+ if ( !xUserList )
+ xUserList.reset( new ScUserList( *pNewList ) );
+ else
+ *xUserList = *pNewList;
+ }
+ else
+ {
+ xUserList.reset();
+ }
+}
+
+OUString ScGlobal::GetErrorString(FormulaError nErr)
+{
+ TranslateId pErrNumber;
+ switch (nErr)
+ {
+ case FormulaError::NoRef:
+ pErrNumber = STR_NO_REF_TABLE;
+ break;
+ case FormulaError::NoAddin:
+ pErrNumber = STR_NO_ADDIN;
+ break;
+ case FormulaError::NoMacro:
+ pErrNumber = STR_NO_MACRO;
+ break;
+ case FormulaError::NotAvailable:
+ return ScCompiler::GetNativeSymbol(ocErrNA);
+ case FormulaError::NoName:
+ return ScCompiler::GetNativeSymbol(ocErrName);
+ case FormulaError::NoValue:
+ return ScCompiler::GetNativeSymbol(ocErrValue);
+ case FormulaError::NoCode:
+ return ScCompiler::GetNativeSymbol(ocErrNull);
+ case FormulaError::DivisionByZero:
+ return ScCompiler::GetNativeSymbol(ocErrDivZero);
+ case FormulaError::IllegalFPOperation:
+ return ScCompiler::GetNativeSymbol(ocErrNum);
+ default:
+ return ScResId(STR_ERROR_STR) + OUString::number( static_cast<int>(nErr) );
+ }
+ return ScResId(pErrNumber);
+}
+
+OUString ScGlobal::GetLongErrorString(FormulaError nErr)
+{
+ TranslateId pErrNumber;
+ switch (nErr)
+ {
+ case FormulaError::NONE:
+ return OUString();
+ case FormulaError::IllegalArgument:
+ pErrNumber = STR_LONG_ERR_ILL_ARG;
+ break;
+ case FormulaError::IllegalFPOperation:
+ pErrNumber = STR_LONG_ERR_ILL_FPO;
+ break;
+ case FormulaError::IllegalChar:
+ pErrNumber = STR_LONG_ERR_ILL_CHAR;
+ break;
+ case FormulaError::IllegalParameter:
+ pErrNumber = STR_LONG_ERR_ILL_PAR;
+ break;
+ case FormulaError::Pair:
+ case FormulaError::PairExpected:
+ pErrNumber = STR_LONG_ERR_PAIR;
+ break;
+ case FormulaError::OperatorExpected:
+ pErrNumber = STR_LONG_ERR_OP_EXP;
+ break;
+ case FormulaError::VariableExpected:
+ case FormulaError::ParameterExpected:
+ pErrNumber = STR_LONG_ERR_VAR_EXP;
+ break;
+ case FormulaError::CodeOverflow:
+ pErrNumber = STR_LONG_ERR_CODE_OVF;
+ break;
+ case FormulaError::StringOverflow:
+ pErrNumber = STR_LONG_ERR_STR_OVF;
+ break;
+ case FormulaError::StackOverflow:
+ pErrNumber = STR_LONG_ERR_STACK_OVF;
+ break;
+ case FormulaError::MatrixSize:
+ pErrNumber = STR_LONG_ERR_MATRIX_SIZE;
+ break;
+ case FormulaError::UnknownState:
+ case FormulaError::UnknownVariable:
+ case FormulaError::UnknownOpCode:
+ case FormulaError::UnknownStackVariable:
+ case FormulaError::UnknownToken:
+ case FormulaError::NoCode:
+ pErrNumber = STR_LONG_ERR_SYNTAX;
+ break;
+ case FormulaError::CircularReference:
+ pErrNumber = STR_LONG_ERR_CIRC_REF;
+ break;
+ case FormulaError::NoConvergence:
+ pErrNumber = STR_LONG_ERR_NO_CONV;
+ break;
+ case FormulaError::NoRef:
+ pErrNumber = STR_LONG_ERR_NO_REF;
+ break;
+ case FormulaError::NoName:
+ pErrNumber = STR_LONG_ERR_NO_NAME;
+ break;
+ case FormulaError::NoAddin:
+ pErrNumber = STR_LONG_ERR_NO_ADDIN;
+ break;
+ case FormulaError::NoMacro:
+ pErrNumber = STR_LONG_ERR_NO_MACRO;
+ break;
+ case FormulaError::DivisionByZero:
+ pErrNumber = STR_LONG_ERR_DIV_ZERO;
+ break;
+ case FormulaError::NestedArray:
+ pErrNumber = STR_ERR_LONG_NESTED_ARRAY;
+ break;
+ case FormulaError::BadArrayContent:
+ pErrNumber = STR_ERR_LONG_BAD_ARRAY_CONTENT;
+ break;
+ case FormulaError::LinkFormulaNeedingCheck:
+ pErrNumber = STR_ERR_LONG_LINK_FORMULA_NEEDING_CHECK;
+ break;
+ case FormulaError::NoValue:
+ pErrNumber = STR_LONG_ERR_NO_VALUE;
+ break;
+ case FormulaError::NotAvailable:
+ pErrNumber = STR_LONG_ERR_NV;
+ break;
+ default:
+ return ScResId(STR_ERROR_STR) + OUString::number( static_cast<int>(nErr) );
+ }
+ return ScResId(pErrNumber);
+}
+
+SvxBrushItem* ScGlobal::GetButtonBrushItem()
+{
+ assert(!bThreadedGroupCalcInProgress);
+ xButtonBrushItem->SetColor( Application::GetSettings().GetStyleSettings().GetFaceColor() );
+ return xButtonBrushItem.get();
+}
+
+void ScGlobal::Init()
+{
+ // The default language for number formats (ScGlobal::eLnge) must
+ // always be LANGUAGE_SYSTEM
+ // FIXME: So remove this variable?
+ eLnge = LANGUAGE_SYSTEM;
+
+ oSysLocale.emplace();
+
+ xEmptyBrushItem = std::make_unique<SvxBrushItem>( COL_TRANSPARENT, ATTR_BACKGROUND );
+ xButtonBrushItem = std::make_unique<SvxBrushItem>( Color(), ATTR_BACKGROUND );
+
+ InitPPT();
+ //ScCompiler::InitSymbolsNative();
+ // ScParameterClassification _after_ Compiler, needs function resources if
+ // arguments are to be merged in, which in turn need strings of function
+ // names from the compiler.
+ ScParameterClassification::Init();
+
+ InitAddIns();
+
+ aStrClipDocName = ScResId( SCSTR_NONAME ) + "1";
+
+ // ScDocumentPool::InitVersionMaps() has been called earlier already
+}
+
+void ScGlobal::InitPPT()
+{
+ OutputDevice* pDev = Application::GetDefaultDevice();
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ // LOK: the below limited precision is not enough for RowColumnHeader.
+ nScreenPPTX = o3tl::convert<double>(pDev->GetDPIX(), o3tl::Length::twip, o3tl::Length::in);
+ nScreenPPTY = o3tl::convert<double>(pDev->GetDPIY(), o3tl::Length::twip, o3tl::Length::in);
+ }
+ else
+ {
+ // Avoid cumulative placement errors by intentionally limiting
+ // precision.
+ Point aPix1000 = pDev->LogicToPixel(Point(1000, 1000), MapMode(MapUnit::MapTwip));
+ nScreenPPTX = aPix1000.X() / 1000.0;
+ nScreenPPTY = aPix1000.Y() / 1000.0;
+ }
+}
+
+const OUString& ScGlobal::GetClipDocName()
+{
+ return aStrClipDocName;
+}
+
+void ScGlobal::SetClipDocName( const OUString& rNew )
+{
+ assert(!bThreadedGroupCalcInProgress);
+ aStrClipDocName = rNew;
+}
+
+void ScGlobal::InitTextHeight(const SfxItemPool* pPool)
+{
+ if (!pPool)
+ {
+ OSL_FAIL("ScGlobal::InitTextHeight: No Pool");
+ return;
+ }
+
+ const ScPatternAttr& rPattern = pPool->GetDefaultItem(ATTR_PATTERN);
+
+ OutputDevice* pDefaultDev = Application::GetDefaultDevice();
+ ScopedVclPtrInstance< VirtualDevice > pVirtWindow( *pDefaultDev );
+ pVirtWindow->SetMapMode(MapMode(MapUnit::MapPixel));
+ vcl::Font aDefFont;
+ rPattern.GetFont(aDefFont, SC_AUTOCOL_BLACK, pVirtWindow); // Font color doesn't matter here
+ pVirtWindow->SetFont(aDefFont);
+ sal_uInt16 nTest = static_cast<sal_uInt16>(
+ pVirtWindow->PixelToLogic(Size(0, pVirtWindow->GetTextHeight()), MapMode(MapUnit::MapTwip)).Height());
+
+ if (nTest > nDefFontHeight)
+ nDefFontHeight = nTest;
+
+ const SvxMarginItem& rMargin = rPattern.GetItem(ATTR_MARGIN);
+
+ nTest = static_cast<sal_uInt16>(nDefFontHeight + rMargin.GetTopMargin()
+ + rMargin.GetBottomMargin() - STD_ROWHEIGHT_DIFF);
+
+ if (nTest > nStdRowHeight)
+ nStdRowHeight = nTest;
+}
+
+void ScGlobal::Clear()
+{
+ // Destroy asyncs _before_ ExitExternalFunc!
+ theAddInAsyncTbl.clear();
+ ExitExternalFunc();
+ ClearAutoFormat();
+ xSearchItem.reset();
+ delete pLegacyFuncCollection.exchange(nullptr);
+ delete pAddInCollection.exchange(nullptr);
+ xUserList.reset();
+ xStarCalcFunctionList.reset(); // Destroy before ResMgr!
+ xStarCalcFunctionMgr.reset();
+ ScParameterClassification::Exit();
+ ScCompiler::DeInit();
+ ScInterpreter::GlobalExit(); // Delete static Stack
+
+ xEmptyBrushItem.reset();
+ xButtonBrushItem.reset();
+ xEnglishFormatter.reset();
+ delete pCaseTransliteration.exchange(nullptr);
+ delete pTransliteration.exchange(nullptr);
+ delete pCaseCollator.exchange(nullptr);
+ delete pCollator.exchange(nullptr);
+ oCalendar.reset();
+ oSysLocale.reset();
+ delete pLocale.exchange(nullptr);
+
+ delete pUnitConverter.exchange(nullptr);
+ xFieldEditEngine.reset();
+ delete pSharedStringPoolPurge.exchange(nullptr);
+
+ xDrawClipDocShellRef.clear();
+}
+
+rtl_TextEncoding ScGlobal::GetCharsetValue( std::u16string_view rCharSet )
+{
+ // new TextEncoding values
+ if ( CharClass::isAsciiNumeric( rCharSet ) )
+ {
+ sal_Int32 nVal = o3tl::toInt32(rCharSet);
+ if ( nVal == RTL_TEXTENCODING_DONTKNOW )
+ return osl_getThreadTextEncoding();
+ return static_cast<rtl_TextEncoding>(nVal);
+ }
+ // old CharSet values for compatibility
+ else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"ANSI") ) return RTL_TEXTENCODING_MS_1252;
+ else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"MAC") ) return RTL_TEXTENCODING_APPLE_ROMAN;
+ else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"IBMPC") ) return RTL_TEXTENCODING_IBM_850;
+ else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"IBMPC_437")) return RTL_TEXTENCODING_IBM_437;
+ else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"IBMPC_850")) return RTL_TEXTENCODING_IBM_850;
+ else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"IBMPC_860")) return RTL_TEXTENCODING_IBM_860;
+ else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"IBMPC_861")) return RTL_TEXTENCODING_IBM_861;
+ else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"IBMPC_863")) return RTL_TEXTENCODING_IBM_863;
+ else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"IBMPC_865")) return RTL_TEXTENCODING_IBM_865;
+ // Some wrong "help" on the net mentions UTF8 and even unoconv uses it,
+ // which worked accidentally if the system encoding is UTF-8 anyway, so
+ // support it ;) but only when reading.
+ else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"UTF8")) return RTL_TEXTENCODING_UTF8;
+ else if (o3tl::equalsIgnoreAsciiCase(rCharSet, u"UTF-8")) return RTL_TEXTENCODING_UTF8;
+ else return osl_getThreadTextEncoding();
+}
+
+OUString ScGlobal::GetCharsetString( rtl_TextEncoding eVal )
+{
+ const char* pChar;
+ switch ( eVal )
+ {
+ // old CharSet strings for compatibility
+ case RTL_TEXTENCODING_MS_1252: pChar = "ANSI"; break;
+ case RTL_TEXTENCODING_APPLE_ROMAN: pChar = "MAC"; break;
+ // IBMPC == IBMPC_850
+ case RTL_TEXTENCODING_IBM_437: pChar = "IBMPC_437"; break;
+ case RTL_TEXTENCODING_IBM_850: pChar = "IBMPC_850"; break;
+ case RTL_TEXTENCODING_IBM_860: pChar = "IBMPC_860"; break;
+ case RTL_TEXTENCODING_IBM_861: pChar = "IBMPC_861"; break;
+ case RTL_TEXTENCODING_IBM_863: pChar = "IBMPC_863"; break;
+ case RTL_TEXTENCODING_IBM_865: pChar = "IBMPC_865"; break;
+ case RTL_TEXTENCODING_DONTKNOW: pChar = "SYSTEM"; break;
+ // new string of TextEncoding value
+ default:
+ return OUString::number( eVal );
+ }
+ return OUString::createFromAscii(pChar);
+}
+
+bool ScGlobal::HasStarCalcFunctionList()
+{
+ return bool(xStarCalcFunctionList);
+}
+
+ScFunctionList* ScGlobal::GetStarCalcFunctionList()
+{
+ assert(!bThreadedGroupCalcInProgress);
+ if ( !xStarCalcFunctionList )
+ xStarCalcFunctionList.reset( new ScFunctionList( SC_MOD()->GetFormulaOptions().GetUseEnglishFuncName()));
+
+ return xStarCalcFunctionList.get();
+}
+
+ScFunctionMgr* ScGlobal::GetStarCalcFunctionMgr()
+{
+ assert(!bThreadedGroupCalcInProgress);
+ if ( !xStarCalcFunctionMgr )
+ xStarCalcFunctionMgr.reset(new ScFunctionMgr);
+
+ return xStarCalcFunctionMgr.get();
+}
+
+void ScGlobal::ResetFunctionList()
+{
+ // FunctionMgr has pointers into FunctionList, must also be updated
+ xStarCalcFunctionMgr.reset();
+ xStarCalcFunctionList.reset();
+ // Building new names also needs InputHandler data to be refreshed.
+ maInputHandlerFunctionNames = InputHandlerFunctionNames();
+}
+
+const InputHandlerFunctionNames& ScGlobal::GetInputHandlerFunctionNames()
+{
+ if (maInputHandlerFunctionNames.maFunctionData.empty())
+ {
+ const OUString aParenthesesReplacement( cParenthesesReplacement);
+ const ScFunctionList* pFuncList = GetStarCalcFunctionList();
+ const sal_uInt32 nListCount = pFuncList->GetCount();
+ const CharClass* pCharClass = (pFuncList->IsEnglishFunctionNames()
+ ? ScCompiler::GetCharClassEnglish()
+ : ScCompiler::GetCharClassLocalized());
+ for (sal_uInt32 i=0; i < nListCount; ++i)
+ {
+ const ScFuncDesc* pDesc = pFuncList->GetFunction( i );
+ if ( pDesc->mxFuncName )
+ {
+ OUString aFuncName(pCharClass->uppercase(*(pDesc->mxFuncName)));
+ // fdo#75264 fill maFormulaChar with all characters used in formula names
+ for (sal_Int32 j = 0; j < aFuncName.getLength(); j++)
+ maInputHandlerFunctionNames.maFunctionChar.insert(aFuncName[j]);
+ maInputHandlerFunctionNames.maFunctionData.insert(
+ ScTypedStrData(*(pDesc->mxFuncName) + aParenthesesReplacement, 0.0, 0.0,
+ ScTypedStrData::Standard));
+ pDesc->initArgumentInfo();
+ OUString aEntry = pDesc->getSignature();
+ maInputHandlerFunctionNames.maFunctionDataPara.insert(
+ ScTypedStrData(aEntry, 0.0, 0.0, ScTypedStrData::Standard));
+ }
+ }
+ }
+ return maInputHandlerFunctionNames;
+}
+
+ScUnitConverter* ScGlobal::GetUnitConverter()
+{
+ return comphelper::doubleCheckedInit( pUnitConverter,
+ []() { return new ScUnitConverter; });
+}
+
+const sal_Unicode* ScGlobal::UnicodeStrChr( const sal_Unicode* pStr,
+ sal_Unicode c )
+{
+ if ( !pStr )
+ return nullptr;
+ while ( *pStr )
+ {
+ if ( *pStr == c )
+ return pStr;
+ pStr++;
+ }
+ return nullptr;
+}
+
+OUString ScGlobal::addToken(std::u16string_view rTokenList, std::u16string_view rToken,
+ sal_Unicode cSep, sal_Int32 nSepCount, bool bForceSep)
+{
+ OUStringBuffer aBuf(rTokenList);
+ if( bForceSep || (!rToken.empty() && !rTokenList.empty()) )
+ comphelper::string::padToLength(aBuf, aBuf.getLength() + nSepCount, cSep);
+ aBuf.append(rToken);
+ return aBuf.makeStringAndClear();
+}
+
+bool ScGlobal::IsQuoted( const OUString& rString, sal_Unicode cQuote )
+{
+ return (rString.getLength() >= 2) && (rString[0] == cQuote) && (rString[ rString.getLength() - 1 ] == cQuote);
+}
+
+void ScGlobal::AddQuotes( OUString& rString, sal_Unicode cQuote, bool bEscapeEmbedded )
+{
+ if (bEscapeEmbedded)
+ {
+ sal_Unicode pQ[3];
+ pQ[0] = pQ[1] = cQuote;
+ pQ[2] = 0;
+ OUString aQuotes( pQ );
+ rString = rString.replaceAll( OUStringChar(cQuote), aQuotes);
+ }
+ rString = OUStringChar( cQuote ) + rString + OUStringChar( cQuote );
+}
+
+void ScGlobal::EraseQuotes( OUString& rString, sal_Unicode cQuote, bool bUnescapeEmbedded )
+{
+ if ( IsQuoted( rString, cQuote ) )
+ {
+ rString = rString.copy( 1, rString.getLength() - 2 );
+ if (bUnescapeEmbedded)
+ {
+ sal_Unicode pQ[3];
+ pQ[0] = pQ[1] = cQuote;
+ pQ[2] = 0;
+ OUString aQuotes( pQ );
+ rString = rString.replaceAll( aQuotes, OUStringChar(cQuote));
+ }
+ }
+}
+
+sal_Int32 ScGlobal::FindUnquoted( const OUString& rString, sal_Unicode cChar, sal_Int32 nStart )
+{
+ assert(nStart >= 0);
+ const sal_Unicode cQuote = '\'';
+ const sal_Unicode* const pStart = rString.getStr();
+ const sal_Unicode* const pStop = pStart + rString.getLength();
+ const sal_Unicode* p = pStart + nStart;
+ bool bQuoted = false;
+ while (p < pStop)
+ {
+ if (*p == cChar && !bQuoted)
+ return sal::static_int_cast< sal_Int32 >( p - pStart );
+ else if (*p == cQuote)
+ {
+ if (!bQuoted)
+ bQuoted = true;
+ else if (p < pStop-1 && *(p+1) == cQuote)
+ ++p;
+ else
+ bQuoted = false;
+ }
+ ++p;
+ }
+ return -1;
+}
+
+const sal_Unicode* ScGlobal::FindUnquoted( const sal_Unicode* pString, sal_Unicode cChar )
+{
+ sal_Unicode cQuote = '\'';
+ const sal_Unicode* p = pString;
+ bool bQuoted = false;
+ while (*p)
+ {
+ if (*p == cChar && !bQuoted)
+ return p;
+ else if (*p == cQuote)
+ {
+ if (!bQuoted)
+ bQuoted = true;
+ else if (*(p+1) == cQuote)
+ ++p;
+ else
+ bQuoted = false;
+ }
+ ++p;
+ }
+ return nullptr;
+}
+
+bool ScGlobal::EETextObjEqual( const EditTextObject* pObj1,
+ const EditTextObject* pObj2 )
+{
+ if ( pObj1 == pObj2 ) // Both empty or the same object
+ return true;
+
+ if ( pObj1 && pObj2 )
+ return pObj1->Equals( *pObj2);
+
+ return false;
+}
+
+void ScGlobal::OpenURL(const OUString& rURL, const OUString& rTarget, bool bIgnoreSettings)
+{
+ // OpenURL is always called in the GridWindow by mouse clicks in some way or another.
+ // That's why pScActiveViewShell and nScClickMouseModifier are correct.
+
+ // Fragments pointing into the current document should be always opened.
+ if (!bIgnoreSettings && !(ShouldOpenURL() || rURL.startsWith("#")))
+ return;
+
+ SfxViewFrame* pViewFrm = SfxViewFrame::Current();
+ if (!pViewFrm)
+ return;
+
+ OUString aUrlName( rURL );
+ SfxViewFrame* pFrame = nullptr;
+ const SfxObjectShell* pObjShell = nullptr;
+ OUString aReferName;
+ if ( pScActiveViewShell )
+ {
+ pFrame = pScActiveViewShell->GetViewFrame();
+ pObjShell = pFrame->GetObjectShell();
+ const SfxMedium* pMed = pObjShell->GetMedium();
+ if (pMed)
+ aReferName = pMed->GetName();
+ }
+
+ // Don't fiddle with fragments pointing into current document.
+ // Also don't mess around with a vnd.sun.star.script or service or other
+ // internal "URI".
+ if (!aUrlName.startsWith("#")
+ && !aUrlName.startsWithIgnoreAsciiCase("vnd.sun.star.script:")
+ && !aUrlName.startsWithIgnoreAsciiCase("macro:")
+ && !aUrlName.startsWithIgnoreAsciiCase("slot:")
+ && !aUrlName.startsWithIgnoreAsciiCase("service:")
+ && !aUrlName.startsWithIgnoreAsciiCase(".uno:"))
+ {
+ // Any relative reference would fail with "not an absolute URL"
+ // error, try to construct an absolute URI with the path relative
+ // to the current document's path or work path, as usual for all
+ // external references.
+ // This then also, as ScGlobal::GetAbsDocName() uses
+ // INetURLObject::smartRel2Abs(), supports "\\" UNC path names as
+ // smb:// Samba shares and DOS path separators converted to proper
+ // file:// URI.
+ const OUString aNewUrlName( ScGlobal::GetAbsDocName( aUrlName, pObjShell));
+ if (!aNewUrlName.isEmpty())
+ aUrlName = aNewUrlName;
+ }
+
+ SfxStringItem aUrl( SID_FILE_NAME, aUrlName );
+ SfxStringItem aTarget( SID_TARGETNAME, rTarget );
+ if ( nScClickMouseModifier & KEY_SHIFT ) // control-click -> into new window
+ aTarget.SetValue("_blank");
+
+ SfxFrameItem aFrm( SID_DOCFRAME, pFrame );
+ SfxStringItem aReferer( SID_REFERER, aReferName );
+
+ SfxBoolItem aNewView( SID_OPEN_NEW_VIEW, false );
+ SfxBoolItem aBrowsing( SID_BROWSE, true );
+
+ // No SID_SILENT anymore
+ pViewFrm->GetDispatcher()->ExecuteList(SID_OPENDOC,
+ SfxCallMode::ASYNCHRON | SfxCallMode::RECORD,
+ { &aUrl, &aTarget, &aFrm, &aReferer, &aNewView, &aBrowsing });
+}
+
+bool ScGlobal::ShouldOpenURL()
+{
+ bool bCtrlClickHappened = (nScClickMouseModifier & KEY_MOD1);
+ bool bCtrlClickSecOption = SvtSecurityOptions::IsOptionSet( SvtSecurityOptions::EOption::CtrlClickHyperlink );
+ if( bCtrlClickHappened && ! bCtrlClickSecOption )
+ {
+ // return since ctrl+click happened when the
+ // ctrl+click security option was disabled, link should not open
+ return false;
+ }
+ else if( ! bCtrlClickHappened && bCtrlClickSecOption )
+ {
+ // ctrl+click did not happen; only click happened maybe with some
+ // other key combo. and security option is set, so return
+ return false;
+ }
+ return true;
+}
+
+bool ScGlobal::IsSystemRTL()
+{
+ return MsLangId::isRightToLeft( Application::GetSettings().GetLanguageTag().getLanguageType() );
+}
+
+SvtScriptType ScGlobal::GetDefaultScriptType()
+{
+ // Used when text contains only WEAK characters.
+ // Script type of office language is used then (same as GetEditDefaultLanguage,
+ // to get consistent behavior of text in simple cells and EditEngine,
+ // also same as GetAppLanguage() in Writer)
+ return SvtLanguageOptions::GetScriptTypeOfLanguage( Application::GetSettings().GetLanguageTag().getLanguageType() );
+}
+
+LanguageType ScGlobal::GetEditDefaultLanguage()
+{
+ // Used for EditEngine::SetDefaultLanguage
+ return Application::GetSettings().GetLanguageTag().getLanguageType();
+}
+
+sal_uInt16 ScGlobal::GetScriptedWhichID( SvtScriptType nScriptType, sal_uInt16 nWhich )
+{
+ switch ( nScriptType )
+ {
+ case SvtScriptType::LATIN:
+ case SvtScriptType::ASIAN:
+ case SvtScriptType::COMPLEX:
+ break; // take exact matches
+ default: // prefer one, first COMPLEX, then ASIAN
+ if ( nScriptType & SvtScriptType::COMPLEX )
+ nScriptType = SvtScriptType::COMPLEX;
+ else if ( nScriptType & SvtScriptType::ASIAN )
+ nScriptType = SvtScriptType::ASIAN;
+ }
+ switch ( nScriptType )
+ {
+ case SvtScriptType::COMPLEX:
+ {
+ switch ( nWhich )
+ {
+ case ATTR_FONT:
+ case ATTR_CJK_FONT:
+ nWhich = ATTR_CTL_FONT;
+ break;
+ case ATTR_FONT_HEIGHT:
+ case ATTR_CJK_FONT_HEIGHT:
+ nWhich = ATTR_CTL_FONT_HEIGHT;
+ break;
+ case ATTR_FONT_WEIGHT:
+ case ATTR_CJK_FONT_WEIGHT:
+ nWhich = ATTR_CTL_FONT_WEIGHT;
+ break;
+ case ATTR_FONT_POSTURE:
+ case ATTR_CJK_FONT_POSTURE:
+ nWhich = ATTR_CTL_FONT_POSTURE;
+ break;
+ }
+ }
+ break;
+ case SvtScriptType::ASIAN:
+ {
+ switch ( nWhich )
+ {
+ case ATTR_FONT:
+ case ATTR_CTL_FONT:
+ nWhich = ATTR_CJK_FONT;
+ break;
+ case ATTR_FONT_HEIGHT:
+ case ATTR_CTL_FONT_HEIGHT:
+ nWhich = ATTR_CJK_FONT_HEIGHT;
+ break;
+ case ATTR_FONT_WEIGHT:
+ case ATTR_CTL_FONT_WEIGHT:
+ nWhich = ATTR_CJK_FONT_WEIGHT;
+ break;
+ case ATTR_FONT_POSTURE:
+ case ATTR_CTL_FONT_POSTURE:
+ nWhich = ATTR_CJK_FONT_POSTURE;
+ break;
+ }
+ }
+ break;
+ default:
+ {
+ switch ( nWhich )
+ {
+ case ATTR_CTL_FONT:
+ case ATTR_CJK_FONT:
+ nWhich = ATTR_FONT;
+ break;
+ case ATTR_CTL_FONT_HEIGHT:
+ case ATTR_CJK_FONT_HEIGHT:
+ nWhich = ATTR_FONT_HEIGHT;
+ break;
+ case ATTR_CTL_FONT_WEIGHT:
+ case ATTR_CJK_FONT_WEIGHT:
+ nWhich = ATTR_FONT_WEIGHT;
+ break;
+ case ATTR_CTL_FONT_POSTURE:
+ case ATTR_CJK_FONT_POSTURE:
+ nWhich = ATTR_FONT_POSTURE;
+ break;
+ }
+ }
+ }
+ return nWhich;
+}
+
+void ScGlobal::AddLanguage( SfxItemSet& rSet, const SvNumberFormatter& rFormatter )
+{
+ OSL_ENSURE( rSet.GetItemState( ATTR_LANGUAGE_FORMAT, false ) == SfxItemState::DEFAULT,
+ "ScGlobal::AddLanguage - language already added");
+
+ const SfxUInt32Item* pHardItem = rSet.GetItemIfSet( ATTR_VALUE_FORMAT, false );
+ if ( !pHardItem )
+ return;
+
+ const SvNumberformat* pHardFormat = rFormatter.GetEntry(
+ pHardItem->GetValue() );
+
+ sal_uInt32 nParentFmt = 0; // Pool default
+ const SfxItemSet* pParent = rSet.GetParent();
+ if ( pParent )
+ nParentFmt = pParent->Get( ATTR_VALUE_FORMAT ).GetValue();
+ const SvNumberformat* pParFormat = rFormatter.GetEntry( nParentFmt );
+
+ if ( pHardFormat && pParFormat &&
+ (pHardFormat->GetLanguage() != pParFormat->GetLanguage()) )
+ rSet.Put( SvxLanguageItem( pHardFormat->GetLanguage(), ATTR_LANGUAGE_FORMAT ) );
+}
+
+utl::TransliterationWrapper& ScGlobal::GetTransliteration()
+{
+ return *comphelper::doubleCheckedInit( pTransliteration,
+ []()
+ {
+ const LanguageType eOfficeLanguage = Application::GetSettings().GetLanguageTag().getLanguageType();
+ ::utl::TransliterationWrapper* p = new ::utl::TransliterationWrapper(
+ ::comphelper::getProcessComponentContext(), TransliterationFlags::IGNORE_CASE );
+ p->loadModuleIfNeeded( eOfficeLanguage );
+ return p;
+ });
+}
+::utl::TransliterationWrapper& ScGlobal::GetCaseTransliteration()
+{
+ return *comphelper::doubleCheckedInit( pCaseTransliteration,
+ []()
+ {
+ const LanguageType eOfficeLanguage = Application::GetSettings().GetLanguageTag().getLanguageType();
+ ::utl::TransliterationWrapper* p = new ::utl::TransliterationWrapper(
+ ::comphelper::getProcessComponentContext(), TransliterationFlags::NONE );
+ p->loadModuleIfNeeded( eOfficeLanguage );
+ return p;
+ });
+}
+utl::TransliterationWrapper& ScGlobal::GetTransliteration(bool bCaseSensitive)
+{
+ return bCaseSensitive ? GetCaseTransliteration() : GetTransliteration();
+}
+
+const LocaleDataWrapper& ScGlobal::getLocaleData()
+{
+ OSL_ENSURE(
+ oSysLocale,
+ "ScGlobal::getLocaleDataPtr() called before ScGlobal::Init()");
+
+ return oSysLocale->GetLocaleData();
+}
+
+const CharClass& ScGlobal::getCharClass()
+{
+ OSL_ENSURE(
+ oSysLocale,
+ "ScGlobal::getCharClassPtr() called before ScGlobal::Init()");
+
+ return oSysLocale->GetCharClass();
+}
+
+CalendarWrapper& ScGlobal::GetCalendar()
+{
+ assert(!bThreadedGroupCalcInProgress);
+ if ( !oCalendar )
+ {
+ oCalendar.emplace( ::comphelper::getProcessComponentContext() );
+ oCalendar->loadDefaultCalendar( GetLocale() );
+ }
+ return *oCalendar;
+}
+
+namespace {
+
+struct GetMutex {
+ osl::Mutex * operator ()() {
+ static osl::Mutex m;
+ return &m;
+ }
+};
+
+}
+
+CollatorWrapper& ScGlobal::GetCollator()
+{
+ return *comphelper::doubleCheckedInit( pCollator,
+ []()
+ {
+ CollatorWrapper* p = new CollatorWrapper( ::comphelper::getProcessComponentContext() );
+ p->loadDefaultCollator( GetLocale(), SC_COLLATOR_IGNORES );
+ return p;
+ },
+ GetMutex());
+}
+CollatorWrapper& ScGlobal::GetCaseCollator()
+{
+ return *comphelper::doubleCheckedInit( pCaseCollator,
+ []()
+ {
+ CollatorWrapper* p = new CollatorWrapper( ::comphelper::getProcessComponentContext() );
+ p->loadDefaultCollator( GetLocale(), 0 );
+ return p;
+ },
+ GetMutex());
+}
+CollatorWrapper& ScGlobal::GetCollator(bool bCaseSensitive)
+{
+ return bCaseSensitive ? GetCaseCollator() : GetCollator();
+}
+css::lang::Locale& ScGlobal::GetLocale()
+{
+ return *comphelper::doubleCheckedInit( pLocale,
+ []() { return new css::lang::Locale( Application::GetSettings().GetLanguageTag().getLocale()); });
+}
+
+ScFieldEditEngine& ScGlobal::GetStaticFieldEditEngine()
+{
+ assert(!bThreadedGroupCalcInProgress);
+ if (!xFieldEditEngine)
+ {
+ // Creating a ScFieldEditEngine with pDocument=NULL leads to document
+ // specific fields not being resolvable! See
+ // ScFieldEditEngine::CalcFieldValue(). pEnginePool=NULL lets
+ // EditEngine internally create and delete a default pool.
+ xFieldEditEngine.reset(new ScFieldEditEngine( nullptr, nullptr));
+ }
+ return *xFieldEditEngine;
+}
+
+sc::SharedStringPoolPurge& ScGlobal::GetSharedStringPoolPurge()
+{
+ return *comphelper::doubleCheckedInit( pSharedStringPoolPurge,
+ []() { return new sc::SharedStringPoolPurge; });
+}
+
+OUString ScGlobal::ReplaceOrAppend( const OUString& rString,
+ std::u16string_view rPlaceholder, const OUString& rReplacement )
+{
+ if (rString.isEmpty())
+ return rReplacement;
+ sal_Int32 nFound = rString.indexOf( rPlaceholder);
+ if (nFound < 0)
+ {
+ if (rString[rString.getLength()-1] == ' ')
+ return rString + rReplacement;
+ return rString + " " + rReplacement;
+ }
+ return rString.replaceFirst( rPlaceholder, rReplacement, &nFound);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/global2.cxx b/sc/source/core/data/global2.cxx
new file mode 100644
index 000000000..ab731b2a9
--- /dev/null
+++ b/sc/source/core/data/global2.cxx
@@ -0,0 +1,598 @@
+/* -*- 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/docfile.hxx>
+#include <sfx2/objsh.hxx>
+#include <unotools/configmgr.hxx>
+#include <unotools/pathoptions.hxx>
+#include <tools/urlobj.hxx>
+#include <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+#include <formula/errorcodes.hxx>
+#include <sal/log.hxx>
+#include <rtl/character.hxx>
+#include <rtl/math.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <global.hxx>
+#include <rangeutl.hxx>
+#include <compiler.hxx>
+#include <paramisc.hxx>
+#include <calcconfig.hxx>
+
+// struct ScImportParam:
+
+ScImportParam::ScImportParam() :
+ nCol1(0),
+ nRow1(0),
+ nCol2(0),
+ nRow2(0),
+ bImport(false),
+ bNative(false),
+ bSql(true),
+ nType(ScDbTable)
+{
+}
+
+ScImportParam::ScImportParam( const ScImportParam& r ) :
+ nCol1 (r.nCol1),
+ nRow1 (r.nRow1),
+ nCol2 (r.nCol2),
+ nRow2 (r.nRow2),
+ bImport (r.bImport),
+ aDBName (r.aDBName),
+ aStatement (r.aStatement),
+ bNative (r.bNative),
+ bSql (r.bSql),
+ nType (r.nType)
+{
+}
+
+ScImportParam::~ScImportParam()
+{
+}
+
+ScImportParam& ScImportParam::operator=( const ScImportParam& r )
+{
+ nCol1 = r.nCol1;
+ nRow1 = r.nRow1;
+ nCol2 = r.nCol2;
+ nRow2 = r.nRow2;
+ bImport = r.bImport;
+ aDBName = r.aDBName;
+ aStatement = r.aStatement;
+ bNative = r.bNative;
+ bSql = r.bSql;
+ nType = r.nType;
+
+ return *this;
+}
+
+bool ScImportParam::operator==( const ScImportParam& rOther ) const
+{
+ return( nCol1 == rOther.nCol1 &&
+ nRow1 == rOther.nRow1 &&
+ nCol2 == rOther.nCol2 &&
+ nRow2 == rOther.nRow2 &&
+ bImport == rOther.bImport &&
+ aDBName == rOther.aDBName &&
+ aStatement == rOther.aStatement &&
+ bNative == rOther.bNative &&
+ bSql == rOther.bSql &&
+ nType == rOther.nType );
+
+ //TODO: are nQuerySh and pConnection equal ?
+}
+
+// struct ScConsolidateParam:
+
+ScConsolidateParam::ScConsolidateParam()
+{
+ Clear();
+}
+
+ScConsolidateParam::ScConsolidateParam( const ScConsolidateParam& r )
+{
+ operator=(r);
+}
+
+ScConsolidateParam::~ScConsolidateParam()
+{
+}
+
+void ScConsolidateParam::ClearDataAreas()
+{
+ pDataAreas.reset();
+ nDataAreaCount = 0;
+}
+
+void ScConsolidateParam::Clear()
+{
+ ClearDataAreas();
+
+ nCol = 0;
+ nRow = 0;
+ nTab = 0;
+ bByCol = bByRow = bReferenceData = false;
+ eFunction = SUBTOTAL_FUNC_SUM;
+}
+
+ScConsolidateParam& ScConsolidateParam::operator=( const ScConsolidateParam& r )
+{
+ if (this != &r)
+ {
+ nCol = r.nCol;
+ nRow = r.nRow;
+ nTab = r.nTab;
+ bByCol = r.bByCol;
+ bByRow = r.bByRow;
+ bReferenceData = r.bReferenceData;
+ eFunction = r.eFunction;
+ nDataAreaCount = r.nDataAreaCount;
+ if ( r.nDataAreaCount > 0 )
+ {
+ nDataAreaCount = r.nDataAreaCount;
+ pDataAreas.reset( new ScArea[nDataAreaCount] );
+ for ( sal_uInt16 i=0; i<nDataAreaCount; i++ )
+ pDataAreas[i] = r.pDataAreas[i];
+ }
+ else
+ pDataAreas.reset();
+ }
+ return *this;
+}
+
+bool ScConsolidateParam::operator==( const ScConsolidateParam& r ) const
+{
+ bool bEqual = (nCol == r.nCol)
+ && (nRow == r.nRow)
+ && (nTab == r.nTab)
+ && (bByCol == r.bByCol)
+ && (bByRow == r.bByRow)
+ && (bReferenceData == r.bReferenceData)
+ && (nDataAreaCount == r.nDataAreaCount)
+ && (eFunction == r.eFunction);
+
+ if ( nDataAreaCount == 0 )
+ bEqual = bEqual && (pDataAreas == nullptr) && (r.pDataAreas == nullptr);
+ else
+ bEqual = bEqual && (pDataAreas != nullptr) && (r.pDataAreas != nullptr);
+
+ if ( bEqual && (nDataAreaCount > 0) )
+ for ( sal_uInt16 i=0; i<nDataAreaCount && bEqual; i++ )
+ bEqual = pDataAreas[i] == r.pDataAreas[i];
+
+ return bEqual;
+}
+
+void ScConsolidateParam::SetAreas( std::unique_ptr<ScArea[]> pAreas, sal_uInt16 nCount )
+{
+ pDataAreas = std::move(pAreas);
+ nDataAreaCount = nCount;
+}
+
+// struct ScSolveParam
+
+ScSolveParam::ScSolveParam()
+{
+}
+
+ScSolveParam::ScSolveParam( const ScSolveParam& r )
+ : aRefFormulaCell ( r.aRefFormulaCell ),
+ aRefVariableCell( r.aRefVariableCell ),
+ pStrTargetVal ( r.pStrTargetVal )
+{
+}
+
+ScSolveParam::ScSolveParam( const ScAddress& rFormulaCell,
+ const ScAddress& rVariableCell,
+ const OUString& rTargetValStr )
+ : aRefFormulaCell ( rFormulaCell ),
+ aRefVariableCell( rVariableCell ),
+ pStrTargetVal ( rTargetValStr )
+{
+}
+
+ScSolveParam::~ScSolveParam()
+{
+}
+
+ScSolveParam& ScSolveParam::operator=( const ScSolveParam& r )
+{
+ aRefFormulaCell = r.aRefFormulaCell;
+ aRefVariableCell = r.aRefVariableCell;
+ pStrTargetVal = r.pStrTargetVal;
+ return *this;
+}
+
+bool ScSolveParam::operator==( const ScSolveParam& r ) const
+{
+ bool bEqual = (aRefFormulaCell == r.aRefFormulaCell)
+ && (aRefVariableCell == r.aRefVariableCell);
+
+ if ( bEqual )
+ {
+ if ( !pStrTargetVal && !r.pStrTargetVal )
+ bEqual = true;
+ else if ( !pStrTargetVal || !r.pStrTargetVal )
+ bEqual = false;
+ else
+ bEqual = ( *pStrTargetVal == *(r.pStrTargetVal) );
+ }
+
+ return bEqual;
+}
+
+// struct ScTabOpParam
+
+ScTabOpParam::ScTabOpParam() : meMode(Column) {}
+
+ScTabOpParam::ScTabOpParam( const ScTabOpParam& r )
+ : aRefFormulaCell ( r.aRefFormulaCell ),
+ aRefFormulaEnd ( r.aRefFormulaEnd ),
+ aRefRowCell ( r.aRefRowCell ),
+ aRefColCell ( r.aRefColCell ),
+ meMode(r.meMode)
+{
+}
+
+ScTabOpParam::ScTabOpParam( const ScRefAddress& rFormulaCell,
+ const ScRefAddress& rFormulaEnd,
+ const ScRefAddress& rRowCell,
+ const ScRefAddress& rColCell,
+ Mode eMode )
+ : aRefFormulaCell ( rFormulaCell ),
+ aRefFormulaEnd ( rFormulaEnd ),
+ aRefRowCell ( rRowCell ),
+ aRefColCell ( rColCell ),
+ meMode(eMode)
+{
+}
+
+ScTabOpParam& ScTabOpParam::operator=( const ScTabOpParam& r )
+{
+ aRefFormulaCell = r.aRefFormulaCell;
+ aRefFormulaEnd = r.aRefFormulaEnd;
+ aRefRowCell = r.aRefRowCell;
+ aRefColCell = r.aRefColCell;
+ meMode = r.meMode;
+ return *this;
+}
+
+bool ScTabOpParam::operator==( const ScTabOpParam& r ) const
+{
+ return ( (aRefFormulaCell == r.aRefFormulaCell)
+ && (aRefFormulaEnd == r.aRefFormulaEnd)
+ && (aRefRowCell == r.aRefRowCell)
+ && (aRefColCell == r.aRefColCell)
+ && (meMode == r.meMode) );
+}
+
+OUString ScGlobal::GetAbsDocName( const OUString& rFileName,
+ const SfxObjectShell* pShell )
+{
+ OUString aAbsName;
+ if (!pShell || !pShell->HasName())
+ { // maybe relative to document path working directory
+ INetURLObject aObj;
+ if (!utl::ConfigManager::IsFuzzing())
+ {
+ aObj.SetSmartURL(SvtPathOptions().GetWorkPath());
+ aObj.setFinalSlash(); // it IS a path
+ }
+ else
+ aObj.SetSmartURL(u"file:///tmp/document");
+ bool bWasAbs = true;
+ aAbsName = aObj.smartRel2Abs( rFileName, bWasAbs ).GetMainURL(INetURLObject::DecodeMechanism::NONE);
+ // returned string must be encoded because it's used directly to create SfxMedium
+ }
+ else
+ {
+ const SfxMedium* pMedium = pShell->GetMedium();
+ if ( pMedium )
+ {
+ bool bWasAbs = true;
+ aAbsName = pMedium->GetURLObject().smartRel2Abs( rFileName, bWasAbs ).GetMainURL(INetURLObject::DecodeMechanism::NONE);
+ }
+ else
+ { // This can't happen, but ...
+ // just to be sure to have the same encoding
+ INetURLObject aObj;
+ aObj.SetSmartURL( aAbsName );
+ aAbsName = aObj.GetMainURL(INetURLObject::DecodeMechanism::NONE);
+ }
+ }
+ return aAbsName;
+}
+
+OUString ScGlobal::GetDocTabName( std::u16string_view rFileName,
+ std::u16string_view rTabName )
+{
+ OUString aDocTab = OUString::Concat("'") + rFileName;
+ sal_Int32 nPos = 1;
+ while( (nPos = aDocTab.indexOf( '\'', nPos )) != -1 )
+ { // escape Quotes
+ aDocTab = aDocTab.replaceAt( nPos, 0, u"\\" );
+ nPos += 2;
+ }
+ aDocTab += "'" + OUStringChar(SC_COMPILER_FILE_TAB_SEP) + rTabName;
+ // "'Doc'#Tab"
+ return aDocTab;
+}
+
+namespace
+{
+bool isEmptyString( const OUString& rStr )
+{
+ if (rStr.isEmpty())
+ return true;
+ else if (rStr[0] == ' ')
+ {
+ const sal_Unicode* p = rStr.getStr() + 1;
+ const sal_Unicode* const pStop = p - 1 + rStr.getLength();
+ while (p < pStop && *p == ' ')
+ ++p;
+ if (p == pStop)
+ return true;
+ }
+ return false;
+}
+}
+
+double ScGlobal::ConvertStringToValue( const OUString& rStr, const ScCalcConfig& rConfig,
+ FormulaError & rError, FormulaError nStringNoValueError,
+ SvNumberFormatter* pFormatter, SvNumFormatType & rCurFmtType )
+{
+ // We keep ScCalcConfig::StringConversion::LOCALE default until
+ // we provide a friendly way to convert string numbers into numbers in the UI.
+
+ double fValue = 0.0;
+ if (nStringNoValueError == FormulaError::CellNoValue)
+ {
+ // Requested that all strings result in 0, error handled by caller.
+ rError = nStringNoValueError;
+ return fValue;
+ }
+
+ switch (rConfig.meStringConversion)
+ {
+ case ScCalcConfig::StringConversion::ILLEGAL:
+ rError = nStringNoValueError;
+ return fValue;
+ case ScCalcConfig::StringConversion::ZERO:
+ return fValue;
+ case ScCalcConfig::StringConversion::LOCALE:
+ {
+ if (rConfig.mbEmptyStringAsZero)
+ {
+ // The number scanner does not accept empty strings or strings
+ // containing only spaces, be on par in these cases with what was
+ // accepted in OOo and is in AOO (see also the
+ // StringConversion::UNAMBIGUOUS branch) and convert to 0 to prevent
+ // interoperability nightmares.
+
+ if (isEmptyString( rStr))
+ return fValue;
+ }
+
+ if (!pFormatter)
+ goto Label_fallback_to_unambiguous;
+
+ sal_uInt32 nFIndex = 0;
+ if (!pFormatter->IsNumberFormat(rStr, nFIndex, fValue))
+ {
+ rError = nStringNoValueError;
+ fValue = 0.0;
+ }
+ return fValue;
+ }
+ break;
+ case ScCalcConfig::StringConversion::UNAMBIGUOUS:
+Label_fallback_to_unambiguous:
+ {
+ if (!rConfig.mbEmptyStringAsZero)
+ {
+ if (isEmptyString( rStr))
+ {
+ rError = nStringNoValueError;
+ return fValue;
+ }
+ }
+ }
+ // continue below, pulled from switch case for better readability
+ break;
+ }
+
+ rtl_math_ConversionStatus eStatus;
+ sal_Int32 nParseEnd;
+ // Decimal and group separator 0 => only integer and possibly exponent,
+ // stops at first non-digit non-sign.
+ fValue = ::rtl::math::stringToDouble( rStr, 0, 0, &eStatus, &nParseEnd);
+ sal_Int32 nLen = rStr.getLength();
+ if (eStatus == rtl_math_ConversionStatus_Ok && nParseEnd < nLen)
+ {
+ // Not at string end, check for trailing blanks or switch to date or
+ // time parsing or bail out.
+ const sal_Unicode* const pStart = rStr.getStr();
+ const sal_Unicode* p = pStart + nParseEnd;
+ const sal_Unicode* const pStop = pStart + nLen;
+ switch (*p++)
+ {
+ case ' ':
+ while (p < pStop && *p == ' ')
+ ++p;
+ if (p < pStop)
+ rError = nStringNoValueError;
+ break;
+ case '-':
+ case ':':
+ {
+ bool bDate = (*(p-1) == '-');
+ enum State { year = 0, month, day, hour, minute, second, fraction, done, blank, stop };
+ sal_Int32 nUnit[done] = {0,0,0,0,0,0,0};
+ const sal_Int32 nLimit[done] = {0,12,31,0,59,59,0};
+ State eState = (bDate ? month : minute);
+ rCurFmtType = (bDate ? SvNumFormatType::DATE : SvNumFormatType::TIME);
+ nUnit[eState-1] = o3tl::toInt32(rStr.subView( 0, nParseEnd));
+ const sal_Unicode* pLastStart = p;
+ // Ensure there's no preceding sign. Negative dates
+ // currently aren't handled correctly. Also discard
+ // +CCYY-MM-DD
+ p = pStart;
+ while (p < pStop && *p == ' ')
+ ++p;
+ if (p < pStop && !rtl::isAsciiDigit(*p))
+ rError = nStringNoValueError;
+ p = pLastStart;
+ while (p < pStop && rError == FormulaError::NONE && eState < blank)
+ {
+ if (eState == minute)
+ rCurFmtType |= SvNumFormatType::TIME;
+ if (rtl::isAsciiDigit(*p))
+ {
+ // Maximum 2 digits per unit, except fractions.
+ if (p - pLastStart >= 2 && eState != fraction)
+ rError = nStringNoValueError;
+ }
+ else if (p > pLastStart)
+ {
+ // We had at least one digit.
+ if (eState < done)
+ {
+ nUnit[eState] = o3tl::toInt32(rStr.subView( pLastStart - pStart, p - pLastStart));
+ if (nLimit[eState] && nLimit[eState] < nUnit[eState])
+ rError = nStringNoValueError;
+ }
+ pLastStart = p + 1; // hypothetical next start
+ // Delimiters must match, a trailing delimiter
+ // yields an invalid date/time.
+ switch (eState)
+ {
+ case month:
+ // Month must be followed by separator and
+ // day, no trailing blanks.
+ if (*p != '-' || (p+1 == pStop))
+ rError = nStringNoValueError;
+ break;
+ case day:
+ if ((*p != 'T' || (p+1 == pStop)) && *p != ' ')
+ rError = nStringNoValueError;
+ // Take one blank as a valid delimiter
+ // between date and time.
+ break;
+ case hour:
+ // Hour must be followed by separator and
+ // minute, no trailing blanks.
+ if (*p != ':' || (p+1 == pStop))
+ rError = nStringNoValueError;
+ break;
+ case minute:
+ if ((*p != ':' || (p+1 == pStop)) && *p != ' ')
+ rError = nStringNoValueError;
+ if (*p == ' ')
+ eState = done;
+ break;
+ case second:
+ if (((*p != ',' && *p != '.') || (p+1 == pStop)) && *p != ' ')
+ rError = nStringNoValueError;
+ if (*p == ' ')
+ eState = done;
+ break;
+ case fraction:
+ eState = done;
+ break;
+ default:
+ rError = nStringNoValueError;
+ break;
+ }
+ eState = static_cast<State>(eState + 1);
+ }
+ else
+ rError = nStringNoValueError;
+ ++p;
+ }
+ if (eState == blank)
+ {
+ while (p < pStop && *p == ' ')
+ ++p;
+ if (p < pStop)
+ rError = nStringNoValueError;
+ eState = stop;
+ }
+
+ // Month without day, or hour without minute.
+ if (eState == month || (eState == day && p <= pLastStart) ||
+ eState == hour || (eState == minute && p <= pLastStart))
+ rError = nStringNoValueError;
+
+ if (rError == FormulaError::NONE)
+ {
+ // Catch the very last unit at end of string.
+ if (p > pLastStart && eState < done)
+ {
+ nUnit[eState] = o3tl::toInt32(rStr.subView( pLastStart - pStart, p - pLastStart));
+ if (nLimit[eState] && nLimit[eState] < nUnit[eState])
+ rError = nStringNoValueError;
+ }
+ if (bDate && nUnit[hour] > 23)
+ rError = nStringNoValueError;
+ if (rError == FormulaError::NONE)
+ {
+ if (bDate && nUnit[day] == 0)
+ nUnit[day] = 1;
+ double fFraction = (nUnit[fraction] <= 0 ? 0.0 :
+ ::rtl::math::pow10Exp( nUnit[fraction],
+ static_cast<int>( -ceil( log10( static_cast<double>( nUnit[fraction]))))));
+ if (!bDate)
+ fValue = 0.0;
+ else
+ {
+ Date aDate(
+ sal::static_int_cast<sal_Int16>(nUnit[day]),
+ sal::static_int_cast<sal_Int16>(nUnit[month]),
+ sal::static_int_cast<sal_Int16>(nUnit[year]));
+ if (!aDate.IsValidDate())
+ rError = nStringNoValueError;
+ else
+ {
+ if (pFormatter)
+ fValue = aDate - pFormatter->GetNullDate();
+ else
+ {
+ SAL_WARN("sc.core","ScGlobal::ConvertStringToValue - fixed null date");
+ static Date aDefaultNullDate( 30, 12, 1899);
+ fValue = aDate - aDefaultNullDate;
+ }
+ }
+ }
+ fValue += ((nUnit[hour] * 3600) + (nUnit[minute] * 60) + nUnit[second] + fFraction) / 86400.0;
+ }
+ }
+ }
+ break;
+ default:
+ rError = nStringNoValueError;
+ }
+ if (rError != FormulaError::NONE)
+ fValue = 0.0;
+ }
+ return fValue;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/globalx.cxx b/sc/source/core/data/globalx.cxx
new file mode 100644
index 000000000..f08869cb1
--- /dev/null
+++ b/sc/source/core/data/globalx.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 <callform.hxx>
+#include <global.hxx>
+#include <osl/diagnose.h>
+#include <osl/file.hxx>
+#include <tools/urlobj.hxx>
+#include <tools/diagnose_ex.h>
+#include <ucbhelper/content.hxx>
+
+#include <unotools/pathoptions.hxx>
+
+#include <com/sun/star/sdbc/XResultSet.hpp>
+#include <com/sun/star/ucb/XContentAccess.hpp>
+
+#include <com/sun/star/i18n/OrdinalSuffix.hpp>
+#include <comphelper/processfactory.hxx>
+#include <unotools/configmgr.hxx>
+#include <unotools/localedatawrapper.hxx>
+
+namespace com::sun::star::ucb { class XCommandEnvironment; }
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::ucb;
+
+void ScGlobal::InitAddIns()
+{
+ if (utl::ConfigManager::IsFuzzing())
+ return;
+
+ // multi paths separated by semicolons
+ SvtPathOptions aPathOpt;
+ const OUString& aMultiPath = aPathOpt.GetAddinPath();
+ if (aMultiPath.isEmpty())
+ return;
+
+ sal_Int32 nIdx {0};
+ do
+ {
+ OUString aPath = aMultiPath.getToken(0, ';', nIdx);
+ if (aPath.isEmpty())
+ continue;
+
+ OUString aUrl;
+ if ( osl::FileBase::getFileURLFromSystemPath( aPath, aUrl ) == osl::FileBase::E_None )
+ aPath = aUrl;
+
+ INetURLObject aObj;
+ aObj.SetSmartURL( aPath );
+ aObj.setFinalSlash();
+ try
+ {
+ ::ucbhelper::Content aCnt( aObj.GetMainURL(INetURLObject::DecodeMechanism::NONE),
+ Reference< XCommandEnvironment >(),
+ comphelper::getProcessComponentContext() );
+ Reference< sdbc::XResultSet > xResultSet;
+ Sequence< OUString > aProps;
+ try
+ {
+ xResultSet = aCnt.createCursor(
+ aProps, ::ucbhelper::INCLUDE_DOCUMENTS_ONLY );
+ }
+ catch ( Exception& )
+ {
+ // ucb may throw different exceptions on failure now
+ // no assertion if AddIn directory doesn't exist
+ }
+
+ if ( xResultSet.is() )
+ {
+ Reference< XContentAccess > xContentAccess( xResultSet, UNO_QUERY );
+ try
+ {
+ if ( xResultSet->first() )
+ {
+ do
+ {
+ OUString aId = xContentAccess->queryContentIdentifierString();
+ InitExternalFunc( aId );
+ }
+ while ( xResultSet->next() );
+ }
+ }
+ catch ( Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "sc", "" );
+ }
+ }
+ }
+ catch ( Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "sc", "" );
+ }
+ catch ( ... )
+ {
+ OSL_FAIL( "unexpected exception caught!" );
+ }
+ }
+ while (nIdx>0);
+}
+
+OUString ScGlobal::GetOrdinalSuffix( sal_Int32 nNumber)
+{
+ try
+ {
+ if (!xOrdinalSuffix.is())
+ {
+ xOrdinalSuffix = i18n::OrdinalSuffix::create( ::comphelper::getProcessComponentContext() );
+ }
+ uno::Sequence< OUString > aSuffixes = xOrdinalSuffix->getOrdinalSuffix( nNumber,
+ ScGlobal::getLocaleData().getLanguageTag().getLocale());
+ if ( aSuffixes.hasElements() )
+ return aSuffixes[0];
+ else
+ return OUString();
+ }
+ catch ( Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "sc", "GetOrdinalSuffix: exception caught during init" );
+ }
+ return OUString();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/grouptokenconverter.cxx b/sc/source/core/data/grouptokenconverter.cxx
new file mode 100644
index 000000000..07fefbccb
--- /dev/null
+++ b/sc/source/core/data/grouptokenconverter.cxx
@@ -0,0 +1,316 @@
+/* -*- 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 <grouptokenconverter.hxx>
+#include <document.hxx>
+#include <formulacell.hxx>
+#include <tokenarray.hxx>
+#include <refdata.hxx>
+
+#include <formula/token.hxx>
+#include <formula/vectortoken.hxx>
+
+using namespace formula;
+
+bool ScGroupTokenConverter::isSelfReferenceRelative(const ScAddress& rRefPos, SCROW nRelRow)
+{
+ if (rRefPos.Col() != mrPos.Col() || rRefPos.Tab() != mrPos.Tab())
+ return false;
+
+ SCROW nLen = mrCell.GetCellGroup()->mnLength;
+ SCROW nEndRow = mrPos.Row() + nLen - 1;
+
+ if (nRelRow < 0)
+ {
+ SCROW nTest = nEndRow;
+ nTest += nRelRow;
+ if (nTest >= mrPos.Row())
+ return true;
+ }
+ else if (nRelRow > 0)
+ {
+ SCROW nTest = mrPos.Row(); // top row.
+ nTest += nRelRow;
+ if (nTest <= nEndRow)
+ return true;
+ }
+
+ return false;
+}
+
+bool ScGroupTokenConverter::isSelfReferenceAbsolute(const ScAddress& rRefPos)
+{
+ if (rRefPos.Col() != mrPos.Col() || rRefPos.Tab() != mrPos.Tab())
+ return false;
+
+ SCROW nLen = mrCell.GetCellGroup()->mnLength;
+ SCROW nEndRow = mrPos.Row() + nLen - 1;
+
+ if (rRefPos.Row() < mrPos.Row())
+ return false;
+
+ if (rRefPos.Row() > nEndRow)
+ return false;
+
+ return true;
+}
+
+SCROW ScGroupTokenConverter::trimLength(SCTAB nTab, SCCOL nCol1, SCCOL nCol2, SCROW nRow, SCROW nRowLen)
+{
+ SCROW nLastRow = nRow + nRowLen - 1; // current last row.
+ nLastRow = mrDoc.GetLastDataRow(nTab, nCol1, nCol2, nLastRow);
+ if (nLastRow < (nRow + nRowLen - 1))
+ {
+ // This can end up negative! Was that the original intent, or
+ // is it accidental? Was it not like that originally but the
+ // surrounding conditions changed?
+ nRowLen = nLastRow - nRow + 1;
+ // Anyway, let's assume it doesn't make sense to return a
+ // negative value here. But should we then return 0 or 1? In
+ // the "Column is empty" case below, we return 1, why!? And,
+ // at the callsites there are tests for a zero value returned
+ // from this function (but not for a negative one).
+ if (nRowLen < 0)
+ nRowLen = 0;
+ }
+ else if (nLastRow == 0)
+ // Column is empty.
+ nRowLen = 1;
+
+ return nRowLen;
+}
+
+ScGroupTokenConverter::ScGroupTokenConverter(
+ ScTokenArray& rGroupTokens, ScDocument& rDoc, const ScFormulaCell& rCell, const ScAddress& rPos) :
+ mrGroupTokens(rGroupTokens),
+ mrDoc(rDoc),
+ mrCell(rCell),
+ mrPos(rPos)
+{
+}
+
+bool ScGroupTokenConverter::convert( const ScTokenArray& rCode, sc::FormulaLogger::GroupScope& rScope )
+{
+#if 0
+ { // debug to start with:
+ ScCompiler aComp( &mrDoc, mrPos, rCode, formula::FormulaGrammar::GRAM_NATIVE_XL_R1C1);
+ OUStringBuffer aAsString;
+ aComp.CreateStringFromTokenArray(aAsString);
+ }
+#endif
+
+ const SCROW nLen = mrCell.GetCellGroup()->mnLength;
+ formula::FormulaTokenArrayPlainIterator aIter(rCode);
+ for (const formula::FormulaToken* p = aIter.First(); p; p = aIter.Next())
+ {
+ // A reference can be either absolute or relative. If it's absolute,
+ // convert it to a static value token. If relative, convert it to a
+ // vector reference token. Note: we only care about relative vs
+ // absolute reference state for row directions.
+
+ switch (p->GetType())
+ {
+ case svSingleRef:
+ {
+ ScSingleRefData aRef = *p->GetSingleRef();
+ if( aRef.IsDeleted())
+ return false;
+ ScAddress aRefPos = aRef.toAbs(mrDoc, mrPos);
+ if (aRef.IsRowRel())
+ {
+ if (isSelfReferenceRelative(aRefPos, aRef.Row()))
+ return false;
+
+ // Trim data array length to actual data range.
+ SCROW nTrimLen = trimLength(aRefPos.Tab(), aRefPos.Col(), aRefPos.Col(), aRefPos.Row(), nLen);
+ // Fetch double array guarantees that the length of the
+ // returned array equals or greater than the requested
+ // length.
+
+ formula::VectorRefArray aArray;
+ if (nTrimLen)
+ {
+#ifdef DBG_UTIL
+ // All the necessary Interpret() calls for all the cells
+ // should have been already handled by ScDependantsCalculator
+ // calling HandleRefArrayForParallelism(), and that handling also checks
+ // for cycles etc. Recursively calling Interpret() from here (which shouldn't
+ // happen) could lead to unhandled problems.
+ // Also, because of caching FetchVectorRefArray() fetches values for all rows
+ // up to the maximum one, so check those too.
+ mrDoc.AssertNoInterpretNeeded(
+ ScAddress(aRefPos.Col(), 0, aRefPos.Tab()), nTrimLen + aRefPos.Row());
+#endif
+ aArray = mrDoc.FetchVectorRefArray(aRefPos, nTrimLen);
+ }
+
+ if (!aArray.isValid())
+ return false;
+
+ formula::SingleVectorRefToken aTok(aArray, nTrimLen);
+ mrGroupTokens.AddToken(aTok);
+ rScope.addRefMessage(mrPos, aRefPos, nLen, aArray);
+
+ if (nTrimLen && !mxFormulaGroupContext)
+ {
+ //tdf#98880 if the SingleVectorRefToken relies on the
+ //underlying storage provided by the Document
+ //FormulaGroupContext, take a reference to it here to
+ //ensure that backing storage exists for our lifetime
+ mxFormulaGroupContext = mrDoc.GetFormulaGroupContext();
+ }
+ }
+ else
+ {
+ // Absolute row reference.
+ if (isSelfReferenceAbsolute(aRefPos))
+ return false;
+
+ formula::FormulaTokenRef pNewToken = mrDoc.ResolveStaticReference(aRefPos);
+ if (!pNewToken)
+ return false;
+
+ mrGroupTokens.AddToken(*pNewToken);
+ rScope.addRefMessage(mrPos, aRefPos, *pNewToken);
+ }
+ }
+ break;
+ case svDoubleRef:
+ {
+ // This code may break in case of implicit intersection, leading to unnecessarily large
+ // matrix operations and possibly incorrect results (=C:C/D:D). That is handled by
+ // having ScCompiler check that there are no possible implicit intersections.
+ // Additionally some functions such as INDEX() and OFFSET() require a reference,
+ // that is handled by denylisting those opcodes in ScTokenArray::CheckToken().
+
+ ScComplexRefData aRef = *p->GetDoubleRef();
+ if( aRef.IsDeleted())
+ return false;
+ ScRange aAbs = aRef.toAbs(mrDoc, mrPos);
+
+ // Multiple sheets not handled by vector/matrix.
+ if (aRef.Ref1.Tab() != aRef.Ref2.Tab())
+ return false;
+
+ // Check for self reference.
+ if (aRef.Ref1.IsRowRel())
+ {
+ if (isSelfReferenceRelative(aAbs.aStart, aRef.Ref1.Row()))
+ return false;
+ }
+ else if (isSelfReferenceAbsolute(aAbs.aStart))
+ return false;
+
+ if (aRef.Ref2.IsRowRel())
+ {
+ if (isSelfReferenceRelative(aAbs.aEnd, aRef.Ref2.Row()))
+ return false;
+ }
+ else if (isSelfReferenceAbsolute(aAbs.aEnd))
+ return false;
+
+ // Row reference is relative.
+ bool bAbsFirst = !aRef.Ref1.IsRowRel();
+ bool bAbsLast = !aRef.Ref2.IsRowRel();
+ ScAddress aRefPos = aAbs.aStart;
+ size_t nCols = aAbs.aEnd.Col() - aAbs.aStart.Col() + 1;
+ std::vector<formula::VectorRefArray> aArrays;
+ aArrays.reserve(nCols);
+ SCROW nRefRowSize = aAbs.aEnd.Row() - aAbs.aStart.Row() + 1;
+ SCROW nArrayLength = nRefRowSize;
+ if (!bAbsLast)
+ {
+ // range end position is relative. Extend the array length.
+ SCROW nLastRefRowOffset = aAbs.aEnd.Row() - mrPos.Row();
+ SCROW nLastRefRow = mrPos.Row() + nLen - 1 + nLastRefRowOffset;
+ SCROW nNewLength = nLastRefRow - aAbs.aStart.Row() + 1;
+ if (nNewLength > nArrayLength)
+ nArrayLength = nNewLength;
+ }
+
+ // Trim trailing empty rows.
+ SCROW nRequestedLength = nArrayLength; // keep the original length.
+ nArrayLength = trimLength(aRefPos.Tab(), aAbs.aStart.Col(), aAbs.aEnd.Col(), aRefPos.Row(), nArrayLength);
+
+ for (SCCOL i = aAbs.aStart.Col(); i <= aAbs.aEnd.Col(); ++i)
+ {
+ aRefPos.SetCol(i);
+ formula::VectorRefArray aArray;
+ if (nArrayLength)
+ {
+#ifdef DBG_UTIL
+ mrDoc.AssertNoInterpretNeeded(
+ ScAddress(aRefPos.Col(), 0, aRefPos.Tab()), nArrayLength + aRefPos.Row());
+#endif
+ aArray = mrDoc.FetchVectorRefArray(aRefPos, nArrayLength);
+ }
+
+ if (!aArray.isValid())
+ return false;
+
+ aArrays.push_back(aArray);
+ }
+
+ std::vector<formula::VectorRefArray> aArraysTmp = aArrays;
+ formula::DoubleVectorRefToken aTok( std::move(aArraysTmp), nArrayLength, nRefRowSize, bAbsFirst, bAbsLast );
+ mrGroupTokens.AddToken(aTok);
+ rScope.addRefMessage(mrPos, aAbs.aStart, nRequestedLength, aArrays);
+
+ if (nArrayLength && !aArrays.empty() && !mxFormulaGroupContext)
+ {
+ //tdf#98880 if the DoubleVectorRefToken relies on the
+ //underlying storage provided by the Document
+ //FormulaGroupContext, take a reference to it here to
+ //ensure that backing storage exists for our lifetime
+ mxFormulaGroupContext = mrDoc.GetFormulaGroupContext();
+ }
+ }
+ break;
+ case svIndex:
+ {
+ if (p->GetOpCode() != ocName)
+ {
+ // May be DB-range or TableRef
+ mrGroupTokens.AddToken(*p);
+ break;
+ }
+
+ // Named range.
+ ScRangeName* pNames = mrDoc.GetRangeName();
+ if (!pNames)
+ // This should never fail.
+ return false;
+
+ ScRangeData* pRange = pNames->findByIndex(p->GetIndex());
+ if (!pRange)
+ // No named range exists by that index.
+ return false;
+
+ ScTokenArray* pNamedTokens = pRange->GetCode();
+ if (!pNamedTokens)
+ // This named range is empty.
+ return false;
+
+ mrGroupTokens.AddOpCode(ocOpen);
+
+ if (!convert(*pNamedTokens, rScope))
+ return false;
+
+ mrGroupTokens.AddOpCode(ocClose);
+ }
+ break;
+ default:
+ mrGroupTokens.AddToken(*p);
+ }
+ }
+
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/listenercontext.cxx b/sc/source/core/data/listenercontext.cxx
new file mode 100644
index 000000000..dac2380bf
--- /dev/null
+++ b/sc/source/core/data/listenercontext.cxx
@@ -0,0 +1,95 @@
+/* -*- 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 <listenercontext.hxx>
+#include <document.hxx>
+#include <mtvelements.hxx>
+
+namespace sc {
+
+StartListeningContext::StartListeningContext(ScDocument& rDoc) :
+ mrDoc(rDoc), mpSet(std::make_shared<ColumnBlockPositionSet>(rDoc)) {}
+
+StartListeningContext::StartListeningContext(
+ ScDocument& rDoc, const std::shared_ptr<ColumnBlockPositionSet>& pSet) :
+ mrDoc(rDoc), mpSet(pSet) {}
+
+void StartListeningContext::setColumnSet( const std::shared_ptr<const ColumnSet>& rpColSet )
+{
+ mpColSet = rpColSet;
+}
+
+const std::shared_ptr<const ColumnSet>& StartListeningContext::getColumnSet() const
+{
+ return mpColSet;
+}
+
+ColumnBlockPosition* StartListeningContext::getBlockPosition(SCTAB nTab, SCCOL nCol)
+{
+ return mpSet->getBlockPosition(nTab, nCol);
+}
+
+EndListeningContext::EndListeningContext(ScDocument& rDoc, ScTokenArray* pOldCode) :
+ mrDoc(rDoc), mpPosSet(std::make_shared<ColumnBlockPositionSet>(rDoc)),
+ mpOldCode(pOldCode), maPosDelta(0,0,0) {}
+
+EndListeningContext::EndListeningContext(
+ ScDocument& rDoc, const std::shared_ptr<ColumnBlockPositionSet>& pSet, ScTokenArray* pOldCode) :
+ mrDoc(rDoc), mpPosSet(pSet),
+ mpOldCode(pOldCode), maPosDelta(0,0,0) {}
+
+void EndListeningContext::setPositionDelta( const ScAddress& rDelta )
+{
+ maPosDelta = rDelta;
+}
+
+ScAddress EndListeningContext::getOldPosition( const ScAddress& rPos ) const
+{
+ ScAddress aOldPos = rPos;
+ aOldPos.IncCol(maPosDelta.Col());
+ aOldPos.IncRow(maPosDelta.Row());
+ aOldPos.IncTab(maPosDelta.Tab());
+ return aOldPos;
+}
+
+ColumnBlockPosition* EndListeningContext::getBlockPosition(SCTAB nTab, SCCOL nCol)
+{
+ return mpPosSet->getBlockPosition(nTab, nCol);
+}
+
+void EndListeningContext::addEmptyBroadcasterPosition(SCTAB nTab, SCCOL nCol, SCROW nRow)
+{
+ maSet.set(mrDoc, nTab, nCol, nRow, true);
+}
+
+void EndListeningContext::purgeEmptyBroadcasters()
+{
+ PurgeListenerAction aAction(mrDoc);
+ maSet.executeAction(mrDoc, aAction);
+}
+
+PurgeListenerAction::PurgeListenerAction(ScDocument& rDoc) :
+ mrDoc(rDoc), mpBlockPos(new ColumnBlockPosition) {}
+
+void PurgeListenerAction::startColumn( SCTAB nTab, SCCOL nCol )
+{
+ mrDoc.InitColumnBlockPosition(*mpBlockPos, nTab, nCol);
+}
+
+void PurgeListenerAction::execute( const ScAddress& rPos, SCROW nLength, bool bVal )
+{
+ if (bVal)
+ {
+ mrDoc.DeleteBroadcasters(*mpBlockPos, rPos, nLength);
+ }
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/markarr.cxx b/sc/source/core/data/markarr.cxx
new file mode 100644
index 000000000..9b93c32f2
--- /dev/null
+++ b/sc/source/core/data/markarr.cxx
@@ -0,0 +1,462 @@
+/* -*- 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 <markarr.hxx>
+#include <address.hxx>
+#include <sheetlimits.hxx>
+#include <vector>
+
+ScMarkArray::ScMarkArray(const ScSheetLimits& rLimits) :
+ mrSheetLimits(rLimits)
+{
+ Reset(false);
+}
+
+// Move constructor
+ScMarkArray::ScMarkArray( ScMarkArray&& rOther ) noexcept
+ : mrSheetLimits(rOther.mrSheetLimits)
+{
+ operator=(std::move(rOther));
+}
+
+// Copy constructor
+ScMarkArray::ScMarkArray( const ScMarkArray & rOther )
+ : mrSheetLimits(rOther.mrSheetLimits)
+{
+ operator=(rOther);
+}
+
+void ScMarkArray::Reset( bool bMarked, SCSIZE nNeeded )
+{
+ // always create pData here
+ // (or have separate method to ensure pData)
+
+ assert(nNeeded);
+ mvData.resize(1);
+ mvData.reserve(nNeeded);
+ mvData[0].nRow = mrSheetLimits.mnMaxRow;
+ mvData[0].bMarked = bMarked;
+}
+
+// Iterative implementation of Binary Search
+bool ScMarkArray::Search( SCROW nRow, SCSIZE& nIndex ) const
+{
+ assert(mvData.size() > 0);
+ SCSIZE nHi = mvData.size() - 1;
+ SCSIZE nLo = 0;
+
+ while ( nLo <= nHi )
+ {
+ SCSIZE i = (nLo + nHi) / 2;
+
+ if (mvData[i].nRow < nRow)
+ {
+ // If [nRow] greater, ignore left half
+ nLo = i + 1;
+ }
+ else if ((i > 0) && (mvData[i - 1].nRow >= nRow))
+ {
+ // If [nRow] is smaller, ignore right half
+ nHi = i - 1;
+ }
+ else
+ {
+ // found
+ nIndex=i;
+ return true;
+ }
+ }
+
+ // not found
+ nIndex=0;
+ return false;
+}
+
+bool ScMarkArray::GetMark( SCROW nRow ) const
+{
+ SCSIZE i;
+ if (Search( nRow, i ))
+ return mvData[i].bMarked;
+ else
+ return false;
+
+}
+
+void ScMarkArray::SetMarkArea( SCROW nStartRow, SCROW nEndRow, bool bMarked )
+{
+ if (!(mrSheetLimits.ValidRow(nStartRow) && mrSheetLimits.ValidRow(nEndRow)))
+ return;
+
+ if ((nStartRow == 0) && (nEndRow == mrSheetLimits.mnMaxRow))
+ {
+ Reset(bMarked);
+ }
+ else
+ {
+ SCSIZE ni; // number of entries in beginning
+ SCSIZE nInsert; // insert position (mnMaxRow+1 := no insert)
+ bool bCombined = false;
+ bool bSplit = false;
+ if ( nStartRow > 0 )
+ {
+ // skip beginning
+ SCSIZE nIndex;
+ Search( nStartRow, nIndex );
+ ni = nIndex;
+
+ nInsert = mrSheetLimits.GetMaxRowCount();
+ if ( mvData[ni].bMarked != bMarked )
+ {
+ if ( ni == 0 || (mvData[ni-1].nRow < nStartRow - 1) )
+ { // may be a split or a simple insert or just a shrink,
+ // row adjustment is done further down
+ if ( mvData[ni].nRow > nEndRow )
+ bSplit = true;
+ ni++;
+ nInsert = ni;
+ }
+ else if ( ni > 0 && mvData[ni-1].nRow == nStartRow - 1 )
+ nInsert = ni;
+ }
+ if ( ni > 0 && mvData[ni-1].bMarked == bMarked )
+ { // combine
+ mvData[ni-1].nRow = nEndRow;
+ nInsert = mrSheetLimits.GetMaxRowCount();
+ bCombined = true;
+ }
+ }
+ else
+ {
+ nInsert = 0;
+ ni = 0;
+ }
+
+ SCSIZE nj = ni; // stop position of range to replace
+ while ( nj < mvData.size() && mvData[nj].nRow <= nEndRow )
+ nj++;
+ if ( !bSplit )
+ {
+ if ( nj < mvData.size() && mvData[nj].bMarked == bMarked )
+ { // combine
+ if ( ni > 0 )
+ {
+ if ( mvData[ni-1].bMarked == bMarked )
+ { // adjacent entries
+ mvData[ni-1].nRow = mvData[nj].nRow;
+ nj++;
+ }
+ else if ( ni == nInsert )
+ mvData[ni-1].nRow = nStartRow - 1; // shrink
+ }
+ nInsert = mrSheetLimits.GetMaxRowCount();
+ bCombined = true;
+ }
+ else if ( ni > 0 && ni == nInsert )
+ mvData[ni-1].nRow = nStartRow - 1; // shrink
+ }
+ if ( ni < nj )
+ { // remove middle entries
+ if ( !bCombined )
+ { // replace one entry
+ mvData[ni].nRow = nEndRow;
+ mvData[ni].bMarked = bMarked;
+ ni++;
+ nInsert = mrSheetLimits.GetMaxRowCount();
+ }
+ if ( ni < nj )
+ { // remove entries
+ mvData.erase(mvData.begin() + ni, mvData.begin() + nj);
+ }
+ }
+
+ if ( nInsert < sal::static_int_cast<SCSIZE>(mrSheetLimits.GetMaxRowCount()) )
+ { // insert or append new entry
+ if ( nInsert <= mvData.size() )
+ {
+ if ( !bSplit )
+ mvData.insert(mvData.begin() + nInsert, { nEndRow, bMarked });
+ else
+ {
+ mvData.insert(mvData.begin() + nInsert, 2, { nEndRow, bMarked });
+ mvData[nInsert+1] = mvData[nInsert-1];
+ }
+ }
+ else
+ mvData.push_back(ScMarkEntry{ nEndRow, bMarked });
+ if ( nInsert )
+ mvData[nInsert-1].nRow = nStartRow - 1;
+ }
+ }
+}
+
+/**
+ optimised init-from-range-list. Specifically this is optimised for cases
+ where we have very large data columns with lots and lots of ranges.
+*/
+void ScMarkArray::Set( std::vector<ScMarkEntry> && rMarkEntries )
+{
+ mvData = std::move(rMarkEntries);
+}
+
+bool ScMarkArray::IsAllMarked( SCROW nStartRow, SCROW nEndRow ) const
+{
+ SCSIZE nStartIndex;
+ SCSIZE nEndIndex;
+
+ if (Search( nStartRow, nStartIndex ))
+ if (mvData[nStartIndex].bMarked)
+ if (Search( nEndRow, nEndIndex ))
+ if (nEndIndex==nStartIndex)
+ return true;
+
+ return false;
+}
+
+bool ScMarkArray::HasOneMark( SCROW& rStartRow, SCROW& rEndRow ) const
+{
+ bool bRet = false;
+ if ( mvData.size() == 1 )
+ {
+ if ( mvData[0].bMarked )
+ {
+ rStartRow = 0;
+ rEndRow = mrSheetLimits.mnMaxRow;
+ bRet = true;
+ }
+ }
+ else if ( mvData.size() == 2 )
+ {
+ if ( mvData[0].bMarked )
+ {
+ rStartRow = 0;
+ rEndRow = mvData[0].nRow;
+ }
+ else
+ {
+ rStartRow = mvData[0].nRow + 1;
+ rEndRow = mrSheetLimits.mnMaxRow;
+ }
+ bRet = true;
+ }
+ else if ( mvData.size() == 3 )
+ {
+ if ( mvData[1].bMarked )
+ {
+ rStartRow = mvData[0].nRow + 1;
+ rEndRow = mvData[1].nRow;
+ bRet = true;
+ }
+ }
+ return bRet;
+}
+
+bool ScMarkArray::operator==( const ScMarkArray& rOther ) const
+{
+ return mvData == rOther.mvData;
+}
+
+ScMarkArray& ScMarkArray::operator=( const ScMarkArray& rOther )
+{
+ mvData = rOther.mvData;
+ return *this;
+}
+
+ScMarkArray& ScMarkArray::operator=(ScMarkArray&& rOther) noexcept
+{
+ mvData = std::move(rOther.mvData);
+ return *this;
+}
+
+SCROW ScMarkArray::GetNextMarked( SCROW nRow, bool bUp ) const
+{
+ SCROW nRet = nRow;
+ if (mrSheetLimits.ValidRow(nRow))
+ {
+ SCSIZE nIndex;
+ Search(nRow, nIndex);
+ if (!mvData[nIndex].bMarked)
+ {
+ if (bUp)
+ {
+ if (nIndex>0)
+ nRet = mvData[nIndex-1].nRow;
+ else
+ nRet = -1;
+ }
+ else
+ nRet = mvData[nIndex].nRow + 1;
+ }
+ }
+ return nRet;
+}
+
+SCROW ScMarkArray::GetMarkEnd( SCROW nRow, bool bUp ) const
+{
+ SCROW nRet;
+ SCSIZE nIndex;
+ Search(nRow, nIndex);
+ assert( mvData[nIndex].bMarked && "GetMarkEnd without bMarked" );
+ if (bUp)
+ {
+ if (nIndex>0)
+ nRet = mvData[nIndex-1].nRow + 1;
+ else
+ nRet = 0;
+ }
+ else
+ nRet = mvData[nIndex].nRow;
+
+ return nRet;
+}
+
+void ScMarkArray::Shift(SCROW nStartRow, tools::Long nOffset)
+{
+ if (nOffset == 0 || nStartRow > mrSheetLimits.mnMaxRow)
+ return;
+
+ for (size_t i=0; i < mvData.size(); ++i)
+ {
+ auto& rEntry = mvData[i];
+
+ if (rEntry.nRow < nStartRow)
+ continue;
+ rEntry.nRow += nOffset;
+ if (rEntry.nRow < 0)
+ {
+ rEntry.nRow = 0;
+ }
+ else if (rEntry.nRow > mrSheetLimits.mnMaxRow)
+ {
+ rEntry.nRow = mrSheetLimits.mnMaxRow;
+ }
+ }
+}
+
+void ScMarkArray::Intersect(const ScMarkArray& rOther)
+{
+ size_t i = 0;
+ size_t j = 0;
+
+ std::vector<ScMarkEntry> aEntryArray;
+ aEntryArray.reserve(std::max(mvData.size(), rOther.mvData.size()));
+
+ while (i < mvData.size() && j < rOther.mvData.size())
+ {
+ const auto& rEntry = mvData[i];
+ const auto& rOtherEntry = rOther.mvData[j];
+
+ if (rEntry.bMarked != rOtherEntry.bMarked)
+ {
+ if (!rOtherEntry.bMarked)
+ {
+ aEntryArray.push_back(rOther.mvData[j++]);
+ while (i < mvData.size() && mvData[i].nRow <= rOtherEntry.nRow)
+ ++i;
+ }
+ else // rEntry not marked
+ {
+ aEntryArray.push_back(mvData[i++]);
+ while (j < rOther.mvData.size() && rOther.mvData[j].nRow <= rEntry.nRow)
+ ++j;
+ }
+ }
+ else // rEntry.bMarked == rOtherEntry.bMarked
+ {
+ if (rEntry.bMarked) // both marked
+ {
+ if (rEntry.nRow <= rOtherEntry.nRow)
+ {
+ aEntryArray.push_back(mvData[i++]); // upper row
+ if (rEntry.nRow == rOtherEntry.nRow)
+ ++j;
+ }
+ else
+ {
+ aEntryArray.push_back(rOther.mvData[j++]); // upper row
+ }
+ }
+ else // both not marked
+ {
+ if (rEntry.nRow <= rOtherEntry.nRow)
+ {
+ aEntryArray.push_back(rOther.mvData[j++]); // lower row
+ while (i < mvData.size() && mvData[i].nRow <= rOtherEntry.nRow)
+ ++i;
+ }
+ else
+ {
+ aEntryArray.push_back(mvData[i++]); // lower row
+ while (j < rOther.mvData.size() && rOther.mvData[j].nRow <= rEntry.nRow)
+ ++j;
+ }
+ }
+ }
+ }
+
+ assert((i == mvData.size() || j == rOther.mvData.size()) && "Unexpected case.");
+
+ if (i == mvData.size())
+ {
+ aEntryArray.insert(aEntryArray.end(), rOther.mvData.begin() + j, rOther.mvData.end());
+ }
+ else // j == rOther.nCount
+ {
+ aEntryArray.insert(aEntryArray.end(), mvData.begin() + i, mvData.end());
+ }
+
+ mvData = std::move(aEntryArray);
+}
+
+
+// -------------- Iterator ----------------------------------------------
+
+ScMarkArrayIter::ScMarkArrayIter( const ScMarkArray* pNewArray ) :
+ pArray( pNewArray ),
+ nPos( 0 )
+{
+}
+
+void ScMarkArrayIter::reset( const ScMarkArray* pNewArray )
+{
+ pArray = pNewArray;
+ nPos = 0;
+}
+
+bool ScMarkArrayIter::Next( SCROW& rTop, SCROW& rBottom )
+{
+ if (!pArray)
+ return false;
+ if ( nPos >= pArray->mvData.size() )
+ return false;
+ while (!pArray->mvData[nPos].bMarked)
+ {
+ ++nPos;
+ if ( nPos >= pArray->mvData.size() )
+ return false;
+ }
+ rBottom = pArray->mvData[nPos].nRow;
+ if (nPos==0)
+ rTop = 0;
+ else
+ rTop = pArray->mvData[nPos-1].nRow + 1;
+ ++nPos;
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/markdata.cxx b/sc/source/core/data/markdata.cxx
new file mode 100644
index 000000000..54379a554
--- /dev/null
+++ b/sc/source/core/data/markdata.cxx
@@ -0,0 +1,950 @@
+/* -*- 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 <markdata.hxx>
+#include <markarr.hxx>
+#include <markmulti.hxx>
+#include <rangelst.hxx>
+#include <segmenttree.hxx>
+#include <sheetlimits.hxx>
+#include <document.hxx>
+#include <columnspanset.hxx>
+#include <fstalgorithm.hxx>
+#include <unordered_map>
+
+#include <osl/diagnose.h>
+
+#include <mdds/flat_segment_tree.hpp>
+#include <cassert>
+
+
+ScMarkData::ScMarkData(const ScSheetLimits& rSheetLimits) :
+ aMultiSel(rSheetLimits),
+ mrSheetLimits(rSheetLimits)
+{
+ ResetMark();
+}
+
+ScMarkData& ScMarkData::operator=(const ScMarkData& rOther)
+{
+ maTabMarked = rOther.maTabMarked;
+ aMarkRange = rOther.aMarkRange;
+ aMultiRange = rOther.aMultiRange;
+ aMultiSel = rOther.aMultiSel;
+ aTopEnvelope = rOther.aTopEnvelope;
+ aBottomEnvelope = rOther.aBottomEnvelope;
+ aLeftEnvelope = rOther.aLeftEnvelope;
+ aRightEnvelope = rOther.aRightEnvelope;
+ bMarked = rOther.bMarked;
+ bMultiMarked = rOther.bMultiMarked;
+ bMarking = rOther.bMarking;
+ bMarkIsNeg = rOther.bMarkIsNeg;
+ return *this;
+}
+
+ScMarkData& ScMarkData::operator=(ScMarkData&& rOther)
+{
+ maTabMarked = std::move(rOther.maTabMarked);
+ aMarkRange = std::move(rOther.aMarkRange);
+ aMultiRange = std::move(rOther.aMultiRange);
+ aMultiSel = std::move(rOther.aMultiSel);
+ aTopEnvelope = std::move(rOther.aTopEnvelope);
+ aBottomEnvelope = std::move(rOther.aBottomEnvelope);
+ aLeftEnvelope = std::move(rOther.aLeftEnvelope);
+ aRightEnvelope = std::move(rOther.aRightEnvelope);
+ bMarked = rOther.bMarked;
+ bMultiMarked = rOther.bMultiMarked;
+ bMarking = rOther.bMarking;
+ bMarkIsNeg = rOther.bMarkIsNeg;
+ return *this;
+}
+
+
+void ScMarkData::ResetMark()
+{
+ aMultiSel.Clear();
+
+ bMarked = bMultiMarked = false;
+ bMarking = bMarkIsNeg = false;
+ aTopEnvelope.RemoveAll();
+ aBottomEnvelope.RemoveAll();
+ aLeftEnvelope.RemoveAll();
+ aRightEnvelope.RemoveAll();
+}
+
+void ScMarkData::SetMarkArea( const ScRange& rRange )
+{
+ aMarkRange = rRange;
+ aMarkRange.PutInOrder();
+ if ( !bMarked )
+ {
+ // Upon creation of a document ScFormatShell GetTextAttrState
+ // may query (default) attributes although no sheet is marked yet.
+ // => mark that one.
+ if ( !GetSelectCount() )
+ maTabMarked.insert( aMarkRange.aStart.Tab() );
+ bMarked = true;
+ }
+}
+
+void ScMarkData::SetMultiMarkArea( const ScRange& rRange, bool bMark, bool bSetupMulti )
+{
+ if ( aMultiSel.IsEmpty() )
+ {
+ // if simple mark range is set, copy to multi marks
+ if ( bMarked && !bMarkIsNeg && !bSetupMulti )
+ {
+ bMarked = false;
+ SCCOL nStartCol = aMarkRange.aStart.Col();
+ SCCOL nEndCol = aMarkRange.aEnd.Col();
+ PutInOrder( nStartCol, nEndCol );
+ SetMultiMarkArea( aMarkRange, true, true );
+ }
+ }
+
+ SCCOL nStartCol = rRange.aStart.Col();
+ SCROW nStartRow = rRange.aStart.Row();
+ SCCOL nEndCol = rRange.aEnd.Col();
+ SCROW nEndRow = rRange.aEnd.Row();
+ PutInOrder( nStartRow, nEndRow );
+ PutInOrder( nStartCol, nEndCol );
+
+ aMultiSel.SetMarkArea( nStartCol, nEndCol, nStartRow, nEndRow, bMark );
+
+ if ( bMultiMarked ) // Update aMultiRange
+ {
+ if ( nStartCol < aMultiRange.aStart.Col() )
+ aMultiRange.aStart.SetCol( nStartCol );
+ if ( nStartRow < aMultiRange.aStart.Row() )
+ aMultiRange.aStart.SetRow( nStartRow );
+ if ( nEndCol > aMultiRange.aEnd.Col() )
+ aMultiRange.aEnd.SetCol( nEndCol );
+ if ( nEndRow > aMultiRange.aEnd.Row() )
+ aMultiRange.aEnd.SetRow( nEndRow );
+ }
+ else
+ {
+ aMultiRange = rRange; // new
+ bMultiMarked = true;
+ }
+}
+
+void ScMarkData::SetAreaTab( SCTAB nTab )
+{
+ aMarkRange.aStart.SetTab(nTab);
+ aMarkRange.aEnd.SetTab(nTab);
+ aMultiRange.aStart.SetTab(nTab);
+ aMultiRange.aEnd.SetTab(nTab);
+}
+
+void ScMarkData::SelectTable( SCTAB nTab, bool bNew )
+{
+ if ( bNew )
+ {
+ maTabMarked.insert( nTab );
+ }
+ else
+ {
+ maTabMarked.erase( nTab );
+ }
+}
+
+bool ScMarkData::GetTableSelect( SCTAB nTab ) const
+{
+ return (maTabMarked.find( nTab ) != maTabMarked.end());
+}
+
+void ScMarkData::SelectOneTable( SCTAB nTab )
+{
+ maTabMarked.clear();
+ maTabMarked.insert( nTab );
+}
+
+SCTAB ScMarkData::GetSelectCount() const
+{
+ return static_cast<SCTAB> ( maTabMarked.size() );
+}
+
+SCTAB ScMarkData::GetFirstSelected() const
+{
+ if (!maTabMarked.empty())
+ return (*maTabMarked.begin());
+
+ OSL_FAIL("GetFirstSelected: nothing selected");
+ return 0;
+}
+
+SCTAB ScMarkData::GetLastSelected() const
+{
+ if (!maTabMarked.empty())
+ return (*maTabMarked.rbegin());
+
+ OSL_FAIL("GetLastSelected: nothing selected");
+ return 0;
+}
+
+void ScMarkData::SetSelectedTabs(const MarkedTabsType& rTabs)
+{
+ MarkedTabsType aTabs(rTabs.begin(), rTabs.end());
+ maTabMarked.swap(aTabs);
+}
+
+void ScMarkData::MarkToMulti()
+{
+ if ( bMarked && !bMarking )
+ {
+ SetMultiMarkArea( aMarkRange, !bMarkIsNeg );
+ bMarked = false;
+
+ // check if all multi mark ranges have been removed
+ if ( bMarkIsNeg && !HasAnyMultiMarks() )
+ ResetMark();
+ }
+}
+
+void ScMarkData::MarkToSimple()
+{
+ if ( bMarking )
+ return;
+
+ if ( bMultiMarked && bMarked )
+ MarkToMulti(); // may result in bMarked and bMultiMarked reset
+
+ if ( !bMultiMarked )
+ return;
+
+ ScRange aNew = aMultiRange;
+
+ bool bOk = false;
+ SCCOL nStartCol = aNew.aStart.Col();
+ SCCOL nEndCol = aNew.aEnd.Col();
+
+ while ( nStartCol < nEndCol && !aMultiSel.HasMarks( nStartCol ) )
+ ++nStartCol;
+ while ( nStartCol < nEndCol && !aMultiSel.HasMarks( nEndCol ) )
+ --nEndCol;
+
+ // Rows are only taken from MarkArray
+ SCROW nStartRow, nEndRow;
+ if ( aMultiSel.HasOneMark( nStartCol, nStartRow, nEndRow ) )
+ {
+ bOk = true;
+ SCROW nCmpStart, nCmpEnd;
+ for (SCCOL nCol=nStartCol+1; nCol<=nEndCol && bOk; nCol++)
+ if ( !aMultiSel.HasOneMark( nCol, nCmpStart, nCmpEnd )
+ || nCmpStart != nStartRow || nCmpEnd != nEndRow )
+ bOk = false;
+ }
+
+ if (bOk)
+ {
+ aNew.aStart.SetCol(nStartCol);
+ aNew.aStart.SetRow(nStartRow);
+ aNew.aEnd.SetCol(nEndCol);
+ aNew.aEnd.SetRow(nEndRow);
+
+ ResetMark();
+ aMarkRange = aNew;
+ bMarked = true;
+ bMarkIsNeg = false;
+ }
+}
+
+bool ScMarkData::IsCellMarked( SCCOL nCol, SCROW nRow, bool bNoSimple ) const
+{
+ if ( bMarked && !bNoSimple && !bMarkIsNeg )
+ if ( aMarkRange.aStart.Col() <= nCol && aMarkRange.aEnd.Col() >= nCol &&
+ aMarkRange.aStart.Row() <= nRow && aMarkRange.aEnd.Row() >= nRow )
+ return true;
+
+ if (bMultiMarked)
+ {
+ //TODO: test here for negative Marking ?
+
+ return aMultiSel.GetMark( nCol, nRow );
+ }
+
+ return false;
+}
+
+bool ScMarkData::IsColumnMarked( SCCOL nCol ) const
+{
+ // bMarkIsNeg meanwhile also for columns heads
+ //TODO: GetMarkColumnRanges for completely marked column
+
+ if ( bMarked && !bMarkIsNeg &&
+ aMarkRange.aStart.Col() <= nCol && aMarkRange.aEnd.Col() >= nCol &&
+ aMarkRange.aStart.Row() == 0 && aMarkRange.aEnd.Row() == mrSheetLimits.mnMaxRow )
+ return true;
+
+ if ( bMultiMarked && aMultiSel.IsAllMarked( nCol, 0, mrSheetLimits.mnMaxRow ) )
+ return true;
+
+ return false;
+}
+
+bool ScMarkData::IsRowMarked( SCROW nRow ) const
+{
+ // bMarkIsNeg meanwhile also for row heads
+ //TODO: GetMarkRowRanges for completely marked rows
+
+ if ( bMarked && !bMarkIsNeg &&
+ aMarkRange.aStart.Col() == 0 && aMarkRange.aEnd.Col() == mrSheetLimits.mnMaxCol &&
+ aMarkRange.aStart.Row() <= nRow && aMarkRange.aEnd.Row() >= nRow )
+ return true;
+
+ if ( bMultiMarked )
+ return aMultiSel.IsRowMarked( nRow );
+
+ return false;
+}
+
+void ScMarkData::MarkFromRangeList( const ScRangeList& rList, bool bReset )
+{
+ if (bReset)
+ {
+ maTabMarked.clear();
+ ResetMark();
+ }
+
+ size_t nCount = rList.size();
+ if ( nCount == 1 && !bMarked && !bMultiMarked )
+ {
+ const ScRange& rRange = rList[ 0 ];
+ SetMarkArea( rRange );
+ SelectTable( rRange.aStart.Tab(), true );
+ }
+ else
+ {
+ for (size_t i=0; i < nCount; i++)
+ {
+ const ScRange& rRange = rList[ i ];
+ SetMultiMarkArea( rRange );
+ SelectTable( rRange.aStart.Tab(), true );
+ }
+ }
+}
+
+/**
+ Optimise the case of constructing from a range list, speeds up import.
+*/
+ScMarkData::ScMarkData(const ScSheetLimits& rLimits, const ScRangeList& rList)
+ : aMultiSel(rLimits),
+ mrSheetLimits(rLimits)
+{
+ ResetMark();
+
+ for (const ScRange& rRange : rList)
+ maTabMarked.insert( rRange.aStart.Tab() );
+
+ if (rList.size() > 1)
+ {
+ bMultiMarked = true;
+ aMultiRange = rList.Combine();
+
+ aMultiSel.Set( rList );
+ }
+ else if (rList.size() == 1)
+ {
+ const ScRange& rRange = rList[ 0 ];
+ SetMarkArea( rRange );
+ }
+}
+
+
+void ScMarkData::FillRangeListWithMarks( ScRangeList* pList, bool bClear, SCTAB nForTab ) const
+{
+ if (!pList)
+ return;
+
+ if (bClear)
+ pList->RemoveAll();
+
+ //TODO: for multiple selected tables enter multiple ranges !!!
+
+ if ( bMultiMarked )
+ {
+ SCTAB nTab = (nForTab < 0 ? aMultiRange.aStart.Tab() : nForTab);
+
+ SCCOL nStartCol = aMultiRange.aStart.Col();
+ SCCOL nEndCol = aMultiRange.aEnd.Col();
+ for (SCCOL nCol=nStartCol; nCol<=nEndCol; nCol++)
+ {
+ if (aMultiSel.HasMarks( nCol ))
+ {
+ // Feeding column-wise fragments to ScRangeList::Join() is a
+ // huge bottleneck, speed this up for multiple columns
+ // consisting of identical row sets by building a column span
+ // first. This is usually the case for filtered data, for
+ // example.
+ SCCOL nToCol = nCol+1;
+ for ( ; nToCol <= nEndCol; ++nToCol)
+ {
+ if (!aMultiSel.HasEqualRowsMarked(nCol, nToCol))
+ break;
+ }
+ --nToCol;
+ ScRange aRange( nCol, 0, nTab, nToCol, 0, nTab );
+ SCROW nTop, nBottom;
+ ScMultiSelIter aMultiIter( aMultiSel, nCol );
+ while ( aMultiIter.Next( nTop, nBottom ) )
+ {
+ aRange.aStart.SetRow( nTop );
+ aRange.aEnd.SetRow( nBottom );
+ pList->Join( aRange );
+ }
+ nCol = nToCol;
+ }
+ }
+ }
+
+ if ( bMarked )
+ {
+ if (nForTab < 0)
+ pList->push_back( aMarkRange );
+ else
+ {
+ ScRange aRange( aMarkRange );
+ aRange.aStart.SetTab( nForTab );
+ aRange.aEnd.SetTab( nForTab );
+ pList->push_back( aRange );
+ }
+ }
+}
+
+void ScMarkData::ExtendRangeListTables( ScRangeList* pList ) const
+{
+ if (!pList)
+ return;
+
+ ScRangeList aOldList(*pList);
+ pList->RemoveAll(); //TODO: or skip the existing below
+
+ for (const auto& rTab : maTabMarked)
+ for ( size_t i=0, nCount = aOldList.size(); i<nCount; i++)
+ {
+ ScRange aRange = aOldList[ i ];
+ aRange.aStart.SetTab(rTab);
+ aRange.aEnd.SetTab(rTab);
+ pList->push_back( aRange );
+ }
+}
+
+ScRangeList ScMarkData::GetMarkedRanges() const
+{
+ ScRangeList aRet;
+ FillRangeListWithMarks(&aRet, false);
+ return aRet;
+}
+
+ScRangeList ScMarkData::GetMarkedRangesForTab( SCTAB nTab ) const
+{
+ ScRangeList aRet;
+ FillRangeListWithMarks(&aRet, false, nTab);
+ return aRet;
+}
+
+std::vector<sc::ColRowSpan> ScMarkData::GetMarkedRowSpans() const
+{
+ typedef mdds::flat_segment_tree<SCCOLROW, bool> SpansType;
+
+ ScRangeList aRanges = GetMarkedRanges();
+ SpansType aSpans(0, mrSheetLimits.mnMaxRow+1, false);
+ SpansType::const_iterator itPos = aSpans.begin();
+
+ for (size_t i = 0, n = aRanges.size(); i < n; ++i)
+ {
+ const ScRange& r = aRanges[i];
+ itPos = aSpans.insert(itPos, r.aStart.Row(), r.aEnd.Row()+1, true).first;
+ }
+
+ return sc::toSpanArray<SCCOLROW,sc::ColRowSpan>(aSpans);
+}
+
+std::vector<sc::ColRowSpan> ScMarkData::GetMarkedColSpans() const
+{
+
+ if (bMultiMarked)
+ {
+ SCCOL nStartCol = aMultiRange.aStart.Col();
+ SCCOL nEndCol = aMultiRange.aEnd.Col();
+ if (bMarked)
+ {
+ // Use segment tree to merge marked with multi marked.
+ typedef mdds::flat_segment_tree<SCCOLROW, bool> SpansType;
+ SpansType aSpans(0, mrSheetLimits.mnMaxCol+1, false);
+ SpansType::const_iterator itPos = aSpans.begin();
+ do
+ {
+ if (aMultiSel.GetRowSelArray().HasMarks())
+ {
+ itPos = aSpans.insert(itPos, nStartCol, nEndCol+1, true).first;
+ break; // do; all columns marked
+ }
+
+ /* XXX if it turns out that span insert is too slow for lots of
+ * subsequent columns we could gather each span first and then
+ * insert. */
+ for (SCCOL nCol = nStartCol; nCol <= nEndCol; ++nCol)
+ {
+ const ScMarkArray* pMultiArray = aMultiSel.GetMultiSelArray( nCol );
+ if (pMultiArray && pMultiArray->HasMarks())
+ itPos = aSpans.insert(itPos, nCol, nCol+1, true).first;
+ }
+ }
+ while(false);
+
+ // Merge marked.
+ aSpans.insert(itPos, aMarkRange.aStart.Col(), aMarkRange.aEnd.Col()+1, true);
+
+ return sc::toSpanArray<SCCOLROW,sc::ColRowSpan>(aSpans);
+ }
+ else
+ {
+ // A plain vector is sufficient, avoid segment tree and conversion
+ // to vector overhead.
+ std::vector<sc::ColRowSpan> aVec;
+ if (aMultiSel.GetRowSelArray().HasMarks())
+ {
+ aVec.emplace_back( nStartCol, nEndCol);
+ return aVec; // all columns marked
+ }
+ sc::ColRowSpan aSpan( -1, -1);
+ for (SCCOL nCol = nStartCol; nCol <= nEndCol; ++nCol)
+ {
+ const ScMarkArray* pMultiArray = aMultiSel.GetMultiSelArray( nCol );
+ if (pMultiArray && pMultiArray->HasMarks())
+ {
+ if (aSpan.mnStart == -1)
+ aSpan.mnStart = nCol;
+ aSpan.mnEnd = nCol;
+ }
+ else
+ {
+ // Add span gathered so far, if any.
+ if (aSpan.mnStart != -1)
+ {
+ aVec.push_back( aSpan);
+ aSpan.mnStart = -1;
+ }
+ }
+ }
+ // Add last span, if any.
+ if (aSpan.mnStart != -1)
+ aVec.push_back( aSpan);
+ return aVec;
+ }
+ }
+
+ // Only reached if not multi marked.
+ std::vector<sc::ColRowSpan> aVec;
+ if (bMarked)
+ {
+ aVec.emplace_back( aMarkRange.aStart.Col(), aMarkRange.aEnd.Col());
+ }
+ return aVec;
+}
+
+bool ScMarkData::IsAllMarked( const ScRange& rRange ) const
+{
+ SCCOL nStartCol = rRange.aStart.Col();
+ SCROW nStartRow = rRange.aStart.Row();
+ SCCOL nEndCol = rRange.aEnd.Col();
+ SCROW nEndRow = rRange.aEnd.Row();
+
+ if ( !bMultiMarked )
+ {
+ if ( bMarked && !bMarkIsNeg &&
+ aMarkRange.aStart.Col() <= nStartCol && aMarkRange.aEnd.Col() >= nEndCol &&
+ aMarkRange.aStart.Row() <= nStartRow && aMarkRange.aEnd.Row() >= nEndRow )
+ return true;
+ return false;
+ }
+
+ bool bOk = true;
+
+ if ( nStartCol == 0 && nEndCol == mrSheetLimits.mnMaxCol )
+ return aMultiSel.IsRowRangeMarked( nStartRow, nEndRow );
+
+ for (SCCOL nCol=nStartCol; nCol<=nEndCol && bOk; nCol++)
+ if ( !aMultiSel.IsAllMarked( nCol, nStartRow, nEndRow ) )
+ bOk = false;
+
+ return bOk;
+}
+
+SCCOL ScMarkData::GetStartOfEqualColumns( SCCOL nLastCol, SCCOL nMinCol ) const
+{
+ if( !bMultiMarked )
+ {
+ if ( bMarked && !bMarkIsNeg )
+ {
+ if( aMarkRange.aEnd.Col() >= nMinCol && aMarkRange.aStart.Col() < nLastCol )
+ return aMarkRange.aEnd.Col() + 1;
+ if( aMarkRange.aEnd.Col() >= nLastCol && aMarkRange.aStart.Col() <= nMinCol )
+ return aMarkRange.aStart.Col();
+ }
+ return nMinCol;
+ }
+ return aMultiSel.GetStartOfEqualColumns( nLastCol, nMinCol );
+}
+
+SCROW ScMarkData::GetNextMarked( SCCOL nCol, SCROW nRow, bool bUp ) const
+{
+ if ( !bMultiMarked )
+ return nRow;
+
+ return aMultiSel.GetNextMarked( nCol, nRow, bUp );
+}
+
+bool ScMarkData::HasMultiMarks( SCCOL nCol ) const
+{
+ if ( !bMultiMarked )
+ return false;
+
+ return aMultiSel.HasMarks( nCol );
+}
+
+bool ScMarkData::HasAnyMultiMarks() const
+{
+ if ( !bMultiMarked )
+ return false;
+
+ return aMultiSel.HasAnyMarks();
+}
+
+void ScMarkData::InsertTab( SCTAB nTab )
+{
+ std::set<SCTAB> tabMarked;
+ for (const auto& rTab : maTabMarked)
+ {
+ if (rTab < nTab)
+ tabMarked.insert(rTab);
+ else
+ tabMarked.insert(rTab + 1);
+ }
+ maTabMarked.swap(tabMarked);
+}
+
+void ScMarkData::DeleteTab( SCTAB nTab )
+{
+ std::set<SCTAB> tabMarked;
+ for (const auto& rTab : maTabMarked)
+ {
+ if (rTab < nTab)
+ tabMarked.insert(rTab);
+ else if (rTab > nTab)
+ tabMarked.insert(rTab - 1);
+ }
+ maTabMarked.swap(tabMarked);
+}
+
+void ScMarkData::ShiftCols(const ScDocument& rDoc, SCCOL nStartCol, sal_Int32 nColOffset)
+{
+ if (bMarked)
+ {
+ aMarkRange.IncColIfNotLessThan(rDoc, nStartCol, nColOffset);
+ }
+ else if (bMultiMarked)
+ {
+ aMultiSel.ShiftCols(nStartCol, nColOffset);
+ aMultiRange.IncColIfNotLessThan(rDoc, nStartCol, nColOffset);
+ }
+}
+
+void ScMarkData::ShiftRows(const ScDocument& rDoc, SCROW nStartRow, sal_Int32 nRowOffset)
+{
+ if (bMarked)
+ {
+ aMarkRange.IncRowIfNotLessThan(rDoc, nStartRow, nRowOffset);
+ }
+ else if (bMultiMarked)
+ {
+ aMultiSel.ShiftRows(nStartRow, nRowOffset);
+ aMultiRange.IncRowIfNotLessThan(rDoc, nStartRow, nRowOffset);
+ }
+}
+
+static void lcl_AddRanges(ScRange& rRangeDest, const ScRange& rNewRange )
+{
+ SCCOL nStartCol = rNewRange.aStart.Col();
+ SCROW nStartRow = rNewRange.aStart.Row();
+ SCCOL nEndCol = rNewRange.aEnd.Col();
+ SCROW nEndRow = rNewRange.aEnd.Row();
+ PutInOrder( nStartRow, nEndRow );
+ PutInOrder( nStartCol, nEndCol );
+ if ( nStartCol < rRangeDest.aStart.Col() )
+ rRangeDest.aStart.SetCol( nStartCol );
+ if ( nStartRow < rRangeDest.aStart.Row() )
+ rRangeDest.aStart.SetRow( nStartRow );
+ if ( nEndCol > rRangeDest.aEnd.Col() )
+ rRangeDest.aEnd.SetCol( nEndCol );
+ if ( nEndRow > rRangeDest.aEnd.Row() )
+ rRangeDest.aEnd.SetRow( nEndRow );
+}
+
+void ScMarkData::GetSelectionCover( ScRange& rRange )
+{
+ if( bMultiMarked )
+ {
+ rRange = aMultiRange;
+ SCCOL nStartCol = aMultiRange.aStart.Col(), nEndCol = aMultiRange.aEnd.Col();
+ PutInOrder( nStartCol, nEndCol );
+ nStartCol = ( nStartCol == 0 ) ? nStartCol : nStartCol - 1;
+ nEndCol = ( nEndCol == mrSheetLimits.mnMaxCol ) ? nEndCol : nEndCol + 1;
+ std::unique_ptr<ScFlatBoolRowSegments> pPrevColMarkedRows;
+ std::unique_ptr<ScFlatBoolRowSegments> pCurColMarkedRows;
+ std::unordered_map<SCROW,ScFlatBoolColSegments> aRowToColSegmentsInTopEnvelope;
+ std::unordered_map<SCROW,ScFlatBoolColSegments> aRowToColSegmentsInBottomEnvelope;
+ ScFlatBoolRowSegments aNoRowsMarked(mrSheetLimits.mnMaxRow);
+ aNoRowsMarked.setFalse( 0, mrSheetLimits.mnMaxRow );
+
+ bool bPrevColUnMarked = false;
+
+ for ( SCCOL nCol=nStartCol; nCol <= nEndCol; nCol++ )
+ {
+ SCROW nTop, nBottom;
+ bool bCurColUnMarked = !aMultiSel.HasMarks( nCol );
+ if ( !bCurColUnMarked )
+ {
+ pCurColMarkedRows.reset( new ScFlatBoolRowSegments(mrSheetLimits.mnMaxRow) );
+ pCurColMarkedRows->setFalse( 0, mrSheetLimits.mnMaxRow );
+ ScMultiSelIter aMultiIter( aMultiSel, nCol );
+ ScFlatBoolRowSegments::ForwardIterator aPrevItr(
+ pPrevColMarkedRows ? *pPrevColMarkedRows
+ : aNoRowsMarked); // For finding left envelope
+ ScFlatBoolRowSegments::ForwardIterator aPrevItr1(
+ pPrevColMarkedRows ? *pPrevColMarkedRows
+ : aNoRowsMarked); // For finding right envelope
+ SCROW nTopPrev = 0, nBottomPrev = 0; // For right envelope
+ while ( aMultiIter.Next( nTop, nBottom ) )
+ {
+ pCurColMarkedRows->setTrue( nTop, nBottom );
+ if( bPrevColUnMarked && ( nCol > nStartCol ))
+ {
+ ScRange aAddRange(nCol - 1, nTop, aMultiRange.aStart.Tab(),
+ nCol - 1, nBottom, aMultiRange.aStart.Tab());
+ lcl_AddRanges( rRange, aAddRange ); // Left envelope
+ aLeftEnvelope.push_back( aAddRange );
+ }
+ else if( nCol > nStartCol )
+ {
+ SCROW nTop1 = nTop, nBottom1 = nTop;
+ while( nTop1 <= nBottom && nBottom1 <= nBottom )
+ {
+ bool bRangeMarked = false;
+ const bool bHasValue = aPrevItr.getValue( nTop1, bRangeMarked );
+ assert(bHasValue); (void)bHasValue;
+ if( bRangeMarked )
+ {
+ nTop1 = aPrevItr.getLastPos() + 1;
+ nBottom1 = nTop1;
+ }
+ else
+ {
+ nBottom1 = aPrevItr.getLastPos();
+ if( nBottom1 > nBottom )
+ nBottom1 = nBottom;
+ ScRange aAddRange( nCol - 1, nTop1, aMultiRange.aStart.Tab(),
+ nCol - 1, nBottom1, aMultiRange.aStart.Tab() );
+ lcl_AddRanges( rRange, aAddRange ); // Left envelope
+ aLeftEnvelope.push_back( aAddRange );
+ nTop1 = ++nBottom1;
+ }
+ }
+ while( nTopPrev <= nBottom && nBottomPrev <= nBottom )
+ {
+ bool bRangeMarked;
+ const bool bHasValue = aPrevItr1.getValue( nTopPrev, bRangeMarked );
+ assert(bHasValue); (void)bHasValue;
+ if( bRangeMarked )
+ {
+ nBottomPrev = aPrevItr1.getLastPos();
+ if( nTopPrev < nTop )
+ {
+ if( nBottomPrev >= nTop )
+ {
+ nBottomPrev = nTop - 1;
+ ScRange aAddRange( nCol, nTopPrev, aMultiRange.aStart.Tab(),
+ nCol, nBottomPrev, aMultiRange.aStart.Tab());
+ lcl_AddRanges( rRange, aAddRange ); // Right envelope
+ aRightEnvelope.push_back( aAddRange );
+ nTopPrev = nBottomPrev = (nBottom + 1);
+ }
+ else
+ {
+ ScRange aAddRange( nCol, nTopPrev, aMultiRange.aStart.Tab(),
+ nCol, nBottomPrev, aMultiRange.aStart.Tab());
+ lcl_AddRanges( rRange, aAddRange ); // Right envelope
+ aRightEnvelope.push_back( aAddRange );
+ nTopPrev = ++nBottomPrev;
+ }
+ }
+ else
+ nTopPrev = nBottomPrev = ( nBottom + 1 );
+ }
+ else
+ {
+ nBottomPrev = aPrevItr1.getLastPos();
+ nTopPrev = ++nBottomPrev;
+ }
+ }
+ }
+ if( nTop )
+ {
+ ScRange aAddRange( nCol, nTop - 1, aMultiRange.aStart.Tab(),
+ nCol, nTop - 1, aMultiRange.aStart.Tab());
+ lcl_AddRanges( rRange, aAddRange ); // Top envelope
+ auto it = aRowToColSegmentsInTopEnvelope.find(nTop - 1);
+ if (it == aRowToColSegmentsInTopEnvelope.end())
+ it = aRowToColSegmentsInTopEnvelope.emplace(nTop - 1, ScFlatBoolColSegments(mrSheetLimits.mnMaxCol)).first;
+ it->second.setTrue( nCol, nCol );
+ }
+ if( nBottom < mrSheetLimits.mnMaxRow )
+ {
+ ScRange aAddRange(nCol, nBottom + 1, aMultiRange.aStart.Tab(),
+ nCol, nBottom + 1, aMultiRange.aStart.Tab());
+ lcl_AddRanges( rRange, aAddRange ); // Bottom envelope
+ auto it = aRowToColSegmentsInBottomEnvelope.find(nBottom + 1);
+ if (it == aRowToColSegmentsInBottomEnvelope.end())
+ it = aRowToColSegmentsInBottomEnvelope.emplace(nBottom + 1, ScFlatBoolColSegments(mrSheetLimits.mnMaxCol)).first;
+ it->second.setTrue( nCol, nCol );
+ }
+ }
+
+ while( nTopPrev <= mrSheetLimits.mnMaxRow && nBottomPrev <= mrSheetLimits.mnMaxRow && ( nCol > nStartCol ) )
+ {
+ bool bRangeMarked;
+ const bool bHasValue = aPrevItr1.getValue( nTopPrev, bRangeMarked );
+ assert(bHasValue); (void)bHasValue;
+ if( bRangeMarked )
+ {
+ nBottomPrev = aPrevItr1.getLastPos();
+ ScRange aAddRange(nCol, nTopPrev, aMultiRange.aStart.Tab(),
+ nCol, nBottomPrev, aMultiRange.aStart.Tab());
+ lcl_AddRanges( rRange, aAddRange ); // Right envelope
+ aRightEnvelope.push_back( aAddRange );
+ nTopPrev = ++nBottomPrev;
+ }
+ else
+ {
+ nBottomPrev = aPrevItr1.getLastPos();
+ nTopPrev = ++nBottomPrev;
+ }
+ }
+ }
+ else if( nCol > nStartCol )
+ {
+ bPrevColUnMarked = true;
+ SCROW nTopPrev = 0, nBottomPrev = 0;
+ bool bRangeMarked = false;
+ ScFlatBoolRowSegments::ForwardIterator aPrevItr(
+ pPrevColMarkedRows ? *pPrevColMarkedRows : aNoRowsMarked);
+ while( nTopPrev <= mrSheetLimits.mnMaxRow && nBottomPrev <= mrSheetLimits.mnMaxRow )
+ {
+ const bool bHasValue = aPrevItr.getValue(nTopPrev, bRangeMarked);
+ assert(bHasValue); (void)bHasValue;
+ if( bRangeMarked )
+ {
+ nBottomPrev = aPrevItr.getLastPos();
+ ScRange aAddRange(nCol, nTopPrev, aMultiRange.aStart.Tab(),
+ nCol, nBottomPrev, aMultiRange.aStart.Tab());
+ lcl_AddRanges( rRange, aAddRange ); // Right envelope
+ aRightEnvelope.push_back( aAddRange );
+ nTopPrev = ++nBottomPrev;
+ }
+ else
+ {
+ nBottomPrev = aPrevItr.getLastPos();
+ nTopPrev = ++nBottomPrev;
+ }
+ }
+ }
+ if ( bCurColUnMarked )
+ pPrevColMarkedRows.reset();
+ else
+ pPrevColMarkedRows = std::move( pCurColMarkedRows );
+ }
+ for( auto& rKV : aRowToColSegmentsInTopEnvelope )
+ {
+ SCCOL nStart = nStartCol;
+ ScFlatBoolColSegments::RangeData aRange;
+ while( nStart <= nEndCol )
+ {
+ if( !rKV.second.getRangeData( nStart, aRange ) )
+ break;
+ if( aRange.mbValue ) // is marked
+ aTopEnvelope.push_back( ScRange( aRange.mnCol1, rKV.first, aMultiRange.aStart.Tab(),
+ aRange.mnCol2, rKV.first, aMultiRange.aStart.Tab() ) );
+ nStart = aRange.mnCol2 + 1;
+ }
+ }
+ for( auto& rKV : aRowToColSegmentsInBottomEnvelope )
+ {
+ SCCOL nStart = nStartCol;
+ ScFlatBoolColSegments::RangeData aRange;
+ while( nStart <= nEndCol )
+ {
+ if( !rKV.second.getRangeData( nStart, aRange ) )
+ break;
+ if( aRange.mbValue ) // is marked
+ aBottomEnvelope.push_back( ScRange( aRange.mnCol1, rKV.first, aMultiRange.aStart.Tab(),
+ aRange.mnCol2, rKV.first, aMultiRange.aStart.Tab() ) );
+ nStart = aRange.mnCol2 + 1;
+ }
+ }
+ }
+ else if( bMarked )
+ {
+ aMarkRange.PutInOrder();
+ SCROW nRow1, nRow2, nRow1New, nRow2New;
+ SCCOL nCol1, nCol2, nCol1New, nCol2New;
+ SCTAB nTab1, nTab2;
+ aMarkRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 );
+ nCol1New = nCol1;
+ nCol2New = nCol2;
+ nRow1New = nRow1;
+ nRow2New = nRow2;
+ // Each envelope will have zero or more ranges for single rectangle selection.
+ if( nCol1 > 0 )
+ {
+ aLeftEnvelope.push_back( ScRange( nCol1 - 1, nRow1, nTab1, nCol1 - 1, nRow2, nTab2 ) );
+ --nCol1New;
+ }
+ if( nRow1 > 0 )
+ {
+ aTopEnvelope.push_back( ScRange( nCol1, nRow1 - 1, nTab1, nCol2, nRow1 - 1, nTab2 ) );
+ --nRow1New;
+ }
+ if( nCol2 < mrSheetLimits.mnMaxCol )
+ {
+ aRightEnvelope.push_back( ScRange( nCol2 + 1, nRow1, nTab1, nCol2 + 1, nRow2, nTab2 ) );
+ ++nCol2New;
+ }
+ if( nRow2 < mrSheetLimits.mnMaxRow )
+ {
+ aBottomEnvelope.push_back( ScRange( nCol1, nRow2 + 1, nTab1, nCol2, nRow2 + 1, nTab2 ) );
+ ++nRow2New;
+ }
+ rRange = ScRange( nCol1New, nRow1New, nTab1, nCol2New, nRow2New, nTab2 );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/markmulti.cxx b/sc/source/core/data/markmulti.cxx
new file mode 100644
index 000000000..4c92f5f25
--- /dev/null
+++ b/sc/source/core/data/markmulti.cxx
@@ -0,0 +1,485 @@
+/* -*- 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 <markmulti.hxx>
+#include <markarr.hxx>
+#include <rangelst.hxx>
+#include <segmenttree.hxx>
+#include <sheetlimits.hxx>
+
+#include <o3tl/safeint.hxx>
+
+#include <algorithm>
+
+ScMultiSel::ScMultiSel(const ScSheetLimits& rSheetLimits)
+ : aRowSel(rSheetLimits), mrSheetLimits(rSheetLimits)
+{
+}
+
+ScMultiSel& ScMultiSel::operator=(const ScMultiSel& rOther)
+{
+ aMultiSelContainer = rOther.aMultiSelContainer;
+ aRowSel = rOther.aRowSel;
+ return *this;
+}
+
+ScMultiSel& ScMultiSel::operator=(ScMultiSel&& rOther)
+{
+ aMultiSelContainer = std::move(rOther.aMultiSelContainer);
+ aRowSel = std::move(rOther.aRowSel);
+ return *this;
+}
+
+
+void ScMultiSel::Clear()
+{
+ aMultiSelContainer.clear();
+ aRowSel.Reset();
+}
+
+SCCOL ScMultiSel::GetMultiSelectionCount() const
+{
+ SCCOL nCount = 0;
+ for (const auto & i : aMultiSelContainer)
+ if (i.HasMarks())
+ ++nCount;
+ return nCount;
+}
+
+bool ScMultiSel::HasMarks( SCCOL nCol ) const
+{
+ if ( aRowSel.HasMarks() )
+ return true;
+ return nCol < static_cast<SCCOL>(aMultiSelContainer.size()) && aMultiSelContainer[nCol].HasMarks();
+}
+
+bool ScMultiSel::HasOneMark( SCCOL nCol, SCROW& rStartRow, SCROW& rEndRow ) const
+{
+ SCROW nRow1 = -1, nRow2 = -1, nRow3 = -1, nRow4 = -1;
+ bool aResult1 = aRowSel.HasOneMark( nRow1, nRow2 );
+ bool aResult2 = nCol < static_cast<SCCOL>(aMultiSelContainer.size())
+ && aMultiSelContainer[nCol].HasOneMark( nRow3, nRow4 );
+
+ if ( aResult1 || aResult2 )
+ {
+ if ( aResult1 && aResult2 )
+ {
+ if ( ( nRow2 + 1 ) < nRow3 )
+ return false;
+ if ( ( nRow4 + 1 ) < nRow1 )
+ return false;
+
+ auto aRows = std::minmax( { nRow1, nRow2, nRow3, nRow4 } );
+ rStartRow = aRows.first;
+ rEndRow = aRows.second;
+ return true;
+ }
+ if ( aResult1 )
+ {
+ rStartRow = nRow1;
+ rEndRow = nRow2;
+ return true;
+ }
+
+ rStartRow = nRow3;
+ rEndRow = nRow4;
+ return true;
+ }
+
+ return false;
+}
+
+bool ScMultiSel::GetMark( SCCOL nCol, SCROW nRow ) const
+{
+ if ( aRowSel.GetMark( nRow ) )
+ return true;
+ return nCol < static_cast<SCCOL>(aMultiSelContainer.size()) && aMultiSelContainer[nCol].GetMark(nRow);
+}
+
+bool ScMultiSel::IsAllMarked( SCCOL nCol, SCROW nStartRow, SCROW nEndRow ) const
+{
+ bool bHasMarks1 = aRowSel.HasMarks();
+ bool bHasMarks2 = nCol < static_cast<SCCOL>(aMultiSelContainer.size()) && aMultiSelContainer[nCol].HasMarks();
+
+ if ( !bHasMarks1 && !bHasMarks2 )
+ return false;
+
+ if ( bHasMarks1 && bHasMarks2 )
+ {
+ if ( aRowSel.IsAllMarked( nStartRow, nEndRow ) ||
+ aMultiSelContainer[nCol].IsAllMarked( nStartRow, nEndRow ) )
+ return true;
+ ScMultiSelIter aMultiIter( *this, nCol );
+ ScFlatBoolRowSegments::RangeData aRowRange;
+ bool bRet = aMultiIter.GetRangeData( nStartRow, aRowRange );
+ return bRet && aRowRange.mbValue && aRowRange.mnRow2 >= nEndRow;
+ }
+
+ if ( bHasMarks1 )
+ return aRowSel.IsAllMarked( nStartRow, nEndRow );
+
+ return aMultiSelContainer[nCol].IsAllMarked( nStartRow, nEndRow );
+}
+
+bool ScMultiSel::HasEqualRowsMarked( SCCOL nCol1, SCCOL nCol2 ) const
+{
+ bool bCol1Exists = nCol1 < static_cast<SCCOL>(aMultiSelContainer.size());
+ bool bCol2Exists = nCol2 < static_cast<SCCOL>(aMultiSelContainer.size());
+ if ( bCol1Exists || bCol2Exists )
+ {
+ if ( bCol1Exists && bCol2Exists )
+ return aMultiSelContainer[nCol1] == aMultiSelContainer[nCol2];
+ else if ( bCol1Exists )
+ return !aMultiSelContainer[nCol1].HasMarks();
+ else
+ return !aMultiSelContainer[nCol2].HasMarks();
+ }
+
+ return true;
+}
+
+SCCOL ScMultiSel::GetStartOfEqualColumns( SCCOL nLastCol, SCCOL nMinCol ) const
+{
+ if( nMinCol > nLastCol )
+ return nMinCol;
+ if( nLastCol >= static_cast<SCCOL>(aMultiSelContainer.size()))
+ {
+ if( nMinCol >= static_cast<SCCOL>(aMultiSelContainer.size()))
+ return nMinCol;
+ SCCOL nCol = static_cast<SCCOL>(aMultiSelContainer.size()) - 1;
+ while( nCol >= nMinCol && aMultiSelContainer[nCol] == aRowSel )
+ --nCol;
+ return nCol + 1;
+ }
+ SCCOL nCol = nLastCol - 1;
+ while( nCol >= nMinCol && aMultiSelContainer[nCol] == aMultiSelContainer[nLastCol] )
+ --nCol;
+ return nCol + 1;
+}
+
+SCROW ScMultiSel::GetNextMarked( SCCOL nCol, SCROW nRow, bool bUp ) const
+{
+ if ( nCol >= static_cast<SCCOL>(aMultiSelContainer.size()) || !aMultiSelContainer[nCol].HasMarks() )
+ return aRowSel.GetNextMarked( nRow, bUp );
+
+ SCROW nRow1, nRow2;
+ nRow1 = aRowSel.GetNextMarked( nRow, bUp );
+ nRow2 = aMultiSelContainer[nCol].GetNextMarked( nRow, bUp );
+ if ( nRow1 == nRow2 )
+ return nRow1;
+ if ( nRow1 == -1 )
+ return nRow2;
+ if ( nRow2 == -1 )
+ return nRow1;
+
+ PutInOrder( nRow1, nRow2 );
+ return ( bUp ? nRow2 : nRow1 );
+}
+
+void ScMultiSel::MarkAllCols( SCROW nStartRow, SCROW nEndRow )
+{
+ aMultiSelContainer.resize(mrSheetLimits.mnMaxCol+1, ScMarkArray(mrSheetLimits));
+ for ( SCCOL nCol = mrSheetLimits.mnMaxCol; nCol >= 0; --nCol )
+ {
+ aMultiSelContainer[nCol].SetMarkArea( nStartRow, nEndRow, true );
+ }
+}
+
+void ScMultiSel::SetMarkArea( SCCOL nStartCol, SCCOL nEndCol, SCROW nStartRow, SCROW nEndRow, bool bMark )
+{
+ if ( nStartCol == 0 && nEndCol == mrSheetLimits.mnMaxCol )
+ {
+ aRowSel.SetMarkArea( nStartRow, nEndRow, bMark );
+ if ( !bMark )
+ {
+ // Remove any per column marks for the row range.
+ for ( auto& aIter : aMultiSelContainer )
+ if ( aIter.HasMarks() )
+ aIter.SetMarkArea( nStartRow, nEndRow, false );
+ }
+ return;
+ }
+
+ // Bad case - we need to extend aMultiSelContainer size to MAXCOL
+ // and move row marks from aRowSel to aMultiSelContainer
+ if ( !bMark && aRowSel.HasMarks() )
+ {
+ SCROW nBeg, nLast = nEndRow;
+ if ( aRowSel.GetMark( nStartRow ) )
+ {
+ nBeg = nStartRow;
+ nLast = aRowSel.GetMarkEnd( nStartRow, false );
+ }
+ else
+ {
+ nBeg = aRowSel.GetNextMarked( nStartRow, false );
+ if ( nBeg != mrSheetLimits.GetMaxRowCount() )
+ nLast = aRowSel.GetMarkEnd( nBeg, false );
+ }
+
+ if ( nBeg != mrSheetLimits.GetMaxRowCount() && nLast >= nEndRow && nBeg <= nEndRow )
+ MarkAllCols( nBeg, nEndRow );
+ else
+ {
+ while ( nBeg != mrSheetLimits.GetMaxRowCount() && nLast < nEndRow )
+ {
+ MarkAllCols( nBeg, nLast );
+ nBeg = aRowSel.GetNextMarked( nLast + 1, false );
+ if ( nBeg != mrSheetLimits.GetMaxRowCount() )
+ nLast = aRowSel.GetMarkEnd( nBeg, false );
+ }
+ if ( nBeg != mrSheetLimits.GetMaxRowCount() && nLast >= nEndRow && nBeg <= nEndRow )
+ MarkAllCols( nBeg, nEndRow );
+ }
+
+ aRowSel.SetMarkArea( nStartRow, nEndRow, false );
+ }
+
+ if (nEndCol >= static_cast<SCCOL>(aMultiSelContainer.size()))
+ aMultiSelContainer.resize(nEndCol+1, ScMarkArray(mrSheetLimits));
+ for ( SCCOL nColIter = nEndCol; nColIter >= nStartCol; --nColIter )
+ aMultiSelContainer[nColIter].SetMarkArea( nStartRow, nEndRow, bMark );
+}
+
+/**
+ optimised init-from-range-list. Specifically this is optimised for cases
+ where we have very large data columns with lots and lots of ranges.
+*/
+void ScMultiSel::Set( ScRangeList const & rList )
+{
+ Clear();
+ if (rList.size() == 0)
+ return;
+
+ // sort by row to make the combining/merging faster
+ auto aNewList = rList;
+ std::sort(aNewList.begin(), aNewList.end(),
+ [](const ScRange& lhs, const ScRange& rhs)
+ {
+ return lhs.aStart.Row() < rhs.aStart.Row();
+ });
+
+ std::vector<std::vector<ScMarkEntry>> aMarkEntriesPerCol(mrSheetLimits.mnMaxCol+1);
+
+ SCCOL nMaxCol = -1;
+ for (const ScRange& rRange : aNewList)
+ {
+ SCCOL nStartCol = rRange.aStart.Col();
+ SCROW nStartRow = rRange.aStart.Row();
+ SCCOL nEndCol = rRange.aEnd.Col();
+ SCROW nEndRow = rRange.aEnd.Row();
+ assert( nEndRow >= nStartRow && "this method assumes the input data has ranges with endrow>=startrow");
+ assert( nEndCol >= nStartCol && "this method assumes the input data has ranges with endcol>=startcol");
+ if ( nStartCol == 0 && nEndCol == mrSheetLimits.mnMaxCol )
+ aRowSel.SetMarkArea( nStartRow, nEndRow, /*bMark*/true );
+ else
+ {
+ for ( SCCOL nCol = nStartCol; nCol <= nEndCol; ++nCol )
+ {
+ auto & rMarkEntries = aMarkEntriesPerCol[nCol];
+ int nEntries = rMarkEntries.size();
+ if (nEntries > 1 && nStartRow >= rMarkEntries[nEntries-2].nRow+1
+ && nStartRow <= rMarkEntries[nEntries-1].nRow+1)
+ {
+ // overlaps or directly adjacent previous range
+ rMarkEntries.back().nRow = std::max(nEndRow, rMarkEntries.back().nRow);
+ }
+ else
+ {
+ // new range
+ if (nStartRow > 0)
+ rMarkEntries.emplace_back(ScMarkEntry{nStartRow-1, false});
+ rMarkEntries.emplace_back(ScMarkEntry{nEndRow, true});
+ }
+ }
+ nMaxCol = std::max(nMaxCol, nEndCol);
+ }
+ }
+
+ aMultiSelContainer.resize(nMaxCol+1, ScMarkArray(mrSheetLimits));
+ for (SCCOL nCol = 0; nCol<=nMaxCol; ++nCol)
+ if (!aMarkEntriesPerCol[nCol].empty())
+ aMultiSelContainer[nCol].Set( std::move(aMarkEntriesPerCol[nCol]) );
+}
+
+bool ScMultiSel::IsRowMarked( SCROW nRow ) const
+{
+ return aRowSel.GetMark( nRow );
+}
+
+bool ScMultiSel::IsRowRangeMarked( SCROW nStartRow, SCROW nEndRow ) const
+{
+ if ( !aRowSel.GetMark( nStartRow ) )
+ return false;
+ SCROW nLast = aRowSel.GetMarkEnd( nStartRow, false );
+ return ( nLast >= nEndRow );
+}
+
+ScMarkArray ScMultiSel::GetMarkArray( SCCOL nCol ) const
+{
+ ScMultiSelIter aMultiIter( *this, nCol );
+ ScMarkArray aMarkArray(mrSheetLimits);
+ SCROW nTop, nBottom;
+ while( aMultiIter.Next( nTop, nBottom ) )
+ aMarkArray.SetMarkArea( nTop, nBottom, true );
+ return aMarkArray;
+}
+
+bool ScMultiSel::HasAnyMarks() const
+{
+ if ( aRowSel.HasMarks() )
+ return true;
+ for ( const auto& aPair : aMultiSelContainer )
+ if ( aPair.HasMarks() )
+ return true;
+ return false;
+}
+
+void ScMultiSel::ShiftCols(SCCOL nStartCol, sal_Int32 nColOffset)
+{
+ if (nStartCol > mrSheetLimits.mnMaxCol)
+ return;
+
+ ScMultiSel aNewMultiSel(*this);
+ Clear();
+
+ if (nColOffset < 0)
+ {
+ // columns that would be moved on the left of nStartCol must be removed
+ const SCCOL nEndPos = std::min<SCCOL>(aNewMultiSel.aMultiSelContainer.size(), nStartCol - nColOffset);
+ for (SCCOL nSearchPos = nStartCol; nSearchPos < nEndPos; ++nSearchPos)
+ aNewMultiSel.aMultiSelContainer[nSearchPos].Reset();
+ }
+
+ SCCOL nCol = 0;
+ for (const auto& aSourceArray : aNewMultiSel.aMultiSelContainer)
+ {
+ SCCOL nDestCol = nCol;
+ if (nDestCol >= nStartCol)
+ {
+ nDestCol += nColOffset;
+ if (nDestCol < 0)
+ nDestCol = 0;
+ else if (nDestCol > mrSheetLimits.mnMaxCol)
+ nDestCol = mrSheetLimits.mnMaxCol;
+ }
+ if (nDestCol >= static_cast<SCCOL>(aMultiSelContainer.size()))
+ aMultiSelContainer.resize(nDestCol, ScMarkArray(mrSheetLimits));
+ aMultiSelContainer[nDestCol] = aSourceArray;
+ ++nCol;
+ }
+ aRowSel = aNewMultiSel.aRowSel;
+
+ if (!(nColOffset > 0 && nStartCol > 0 && o3tl::make_unsigned(nStartCol) < aNewMultiSel.aMultiSelContainer.size()))
+ return;
+
+ // insert nColOffset new columns, and select their cells if they are selected
+ // both in the old column at nStartPos and in the previous column
+ auto& rPrevPos = aNewMultiSel.aMultiSelContainer[nStartCol - 1];
+ auto& rStartPos = aNewMultiSel.aMultiSelContainer[nStartCol];
+ auto& rNewCol = aMultiSelContainer[nStartCol];
+ rNewCol = rStartPos;
+ rNewCol.Intersect(rPrevPos);
+ if (nStartCol + nColOffset >= static_cast<SCCOL>(aNewMultiSel.aMultiSelContainer.size()))
+ aNewMultiSel.aMultiSelContainer.resize(nStartCol + nColOffset, ScMarkArray(mrSheetLimits));
+ for (tools::Long i = 1; i < nColOffset; ++i)
+ aMultiSelContainer[nStartCol + i] = rNewCol;
+}
+
+void ScMultiSel::ShiftRows(SCROW nStartRow, sal_Int32 nRowOffset)
+{
+ for (auto& aPair: aMultiSelContainer)
+ aPair.Shift(nStartRow, nRowOffset);
+ aRowSel.Shift(nStartRow, nRowOffset);
+}
+
+const ScMarkArray* ScMultiSel::GetMultiSelArray( SCCOL nCol ) const
+{
+ if (nCol >= static_cast<SCCOL>(aMultiSelContainer.size()))
+ return nullptr;
+ return &aMultiSelContainer[nCol];
+}
+
+ScMultiSelIter::ScMultiSelIter( const ScMultiSel& rMultiSel, SCCOL nCol ) :
+ aMarkArrayIter(nullptr),
+ nNextSegmentStart(0)
+{
+ bool bHasMarks1 = rMultiSel.aRowSel.HasMarks();
+ bool bHasMarks2 = nCol < static_cast<SCCOL>(rMultiSel.aMultiSelContainer.size())
+ && rMultiSel.aMultiSelContainer[nCol].HasMarks();
+
+ if (bHasMarks1 && bHasMarks2)
+ {
+ pRowSegs.reset( new ScFlatBoolRowSegments(rMultiSel.mrSheetLimits.mnMaxRow) );
+ pRowSegs->setFalse( 0, rMultiSel.mrSheetLimits.mnMaxRow );
+ {
+ ScMarkArrayIter aMarkIter( &rMultiSel.aRowSel );
+ SCROW nTop, nBottom;
+ while ( aMarkIter.Next( nTop, nBottom ) )
+ pRowSegs->setTrue( nTop, nBottom );
+ }
+
+ {
+ ScMarkArrayIter aMarkIter( &rMultiSel.aMultiSelContainer[nCol] );
+ SCROW nTop, nBottom;
+ while ( aMarkIter.Next( nTop, nBottom ) )
+ pRowSegs->setTrue( nTop, nBottom );
+ }
+ }
+ else if (bHasMarks1)
+ {
+ aMarkArrayIter.reset( &rMultiSel.aRowSel);
+ }
+ else if (bHasMarks2)
+ {
+ aMarkArrayIter.reset( &rMultiSel.aMultiSelContainer[nCol]);
+ }
+}
+
+bool ScMultiSelIter::Next( SCROW& rTop, SCROW& rBottom )
+{
+ if (pRowSegs)
+ {
+ ScFlatBoolRowSegments::RangeData aRowRange;
+ bool bRet = pRowSegs->getRangeData( nNextSegmentStart, aRowRange );
+ if ( bRet && !aRowRange.mbValue )
+ {
+ nNextSegmentStart = aRowRange.mnRow2 + 1;
+ bRet = pRowSegs->getRangeData( nNextSegmentStart, aRowRange );
+ }
+ if ( bRet )
+ {
+ rTop = aRowRange.mnRow1;
+ rBottom = aRowRange.mnRow2;
+ nNextSegmentStart = rBottom + 1;
+ }
+ return bRet;
+ }
+
+ return aMarkArrayIter.Next( rTop, rBottom);
+}
+
+bool ScMultiSelIter::GetRangeData( SCROW nRow, ScFlatBoolRowSegments::RangeData& rRowRange ) const
+{
+ assert(pRowSegs);
+ return pRowSegs->getRangeData( nRow, rRowRange);
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/mtvcellfunc.cxx b/sc/source/core/data/mtvcellfunc.cxx
new file mode 100644
index 000000000..98f6998cc
--- /dev/null
+++ b/sc/source/core/data/mtvcellfunc.cxx
@@ -0,0 +1,31 @@
+/* -*- 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 <mtvcellfunc.hxx>
+
+namespace sc {
+
+CellStoreType::iterator ProcessFormula(
+ const CellStoreType::iterator& it, CellStoreType& rStore, SCROW nRow1, SCROW nRow2,
+ std::function<void(size_t,ScFormulaCell*)> aFuncElem )
+{
+ using FuncType = std::function<void(size_t,ScFormulaCell*)>;
+ using ElseFuncType = std::function<void(mdds::mtv::element_t, size_t, size_t)>;
+
+ // empty function for handling the 'else' part.
+ static ElseFuncType aFuncElse =
+ [](mdds::mtv::element_t,size_t,size_t) {};
+
+ return ProcessElements1<
+ CellStoreType, formula_block,
+ FuncType, ElseFuncType>(
+ it, rStore, nRow1, nRow2, aFuncElem, aFuncElse);
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/mtvelements.cxx b/sc/source/core/data/mtvelements.cxx
new file mode 100644
index 000000000..2c2c8daa6
--- /dev/null
+++ b/sc/source/core/data/mtvelements.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/.
+ */
+
+#include <mtvelements.hxx>
+#include <document.hxx>
+#include <cellvalue.hxx>
+#include <column.hxx>
+#include <table.hxx>
+
+#include <sstream>
+
+namespace sc {
+
+CellStoreEvent::CellStoreEvent() : mpCol(nullptr) {}
+
+CellStoreEvent::CellStoreEvent(ScColumn* pCol) : mpCol(pCol) {}
+
+void CellStoreEvent::element_block_acquired(const mdds::mtv::base_element_block* block)
+{
+ if (!mpCol)
+ return;
+
+ switch (mdds::mtv::get_block_type(*block))
+ {
+ case sc::element_type_formula:
+ ++mpCol->mnBlkCountFormula;
+ break;
+ default:
+ ;
+ }
+}
+
+void CellStoreEvent::element_block_released(const mdds::mtv::base_element_block* block)
+{
+ if (!mpCol)
+ return;
+
+ switch (mdds::mtv::get_block_type(*block))
+ {
+ case sc::element_type_formula:
+ --mpCol->mnBlkCountFormula;
+ break;
+ default:
+ ;
+ }
+}
+
+void CellStoreEvent::stop()
+{
+ mpCol = nullptr;
+}
+
+void CellStoreEvent::swap(CellStoreEvent& other)
+{
+ std::swap(mpCol, other.mpCol);
+}
+
+const ScColumn* CellStoreEvent::getColumn() const
+{
+ return mpCol;
+}
+
+ColumnBlockPositionSet::ColumnBlockPositionSet(ScDocument& rDoc) : mrDoc(rDoc) {}
+
+ColumnBlockPosition* ColumnBlockPositionSet::getBlockPosition(SCTAB nTab, SCCOL nCol)
+{
+ std::scoped_lock aGuard(maMtxTables);
+
+ TablesType::iterator itTab = maTables.find(nTab);
+ if (itTab == maTables.end())
+ {
+ std::pair<TablesType::iterator,bool> r =
+ maTables.emplace(nTab, ColumnsType());
+ if (!r.second)
+ // insertion failed.
+ return nullptr;
+
+ itTab = r.first;
+ }
+
+ ColumnsType& rCols = itTab->second;
+
+ ColumnsType::iterator it = rCols.find(nCol);
+ if (it != rCols.end())
+ // Block position for this column has already been fetched.
+ return &it->second;
+
+ std::pair<ColumnsType::iterator,bool> r =
+ rCols.emplace(nCol, ColumnBlockPosition());
+
+ if (!r.second)
+ // insertion failed.
+ return nullptr;
+
+ it = r.first;
+
+ if (!mrDoc.InitColumnBlockPosition(it->second, nTab, nCol))
+ return nullptr;
+
+ return &it->second;
+}
+
+void ColumnBlockPositionSet::clear()
+{
+ std::scoped_lock aGuard(maMtxTables);
+ maTables.clear();
+}
+
+struct TableColumnBlockPositionSet::Impl
+{
+ typedef std::unordered_map<SCCOL, ColumnBlockPosition> ColumnsType;
+
+ ScTable* mpTab;
+ ColumnsType maColumns;
+
+ Impl() : mpTab(nullptr) {}
+};
+
+TableColumnBlockPositionSet::TableColumnBlockPositionSet( ScDocument& rDoc, SCTAB nTab ) :
+ mpImpl(std::make_unique<Impl>())
+{
+ mpImpl->mpTab = rDoc.FetchTable(nTab);
+
+ if (!mpImpl->mpTab)
+ {
+ std::ostringstream os;
+ os << "Passed table index " << nTab << " is invalid.";
+ throw std::invalid_argument(os.str());
+ }
+}
+
+TableColumnBlockPositionSet::TableColumnBlockPositionSet( TableColumnBlockPositionSet&& rOther ) noexcept :
+ mpImpl(std::move(rOther.mpImpl)) {}
+
+TableColumnBlockPositionSet::~TableColumnBlockPositionSet() {}
+
+ColumnBlockPosition* TableColumnBlockPositionSet::getBlockPosition( SCCOL nCol )
+{
+ using ColumnsType = Impl::ColumnsType;
+
+ ColumnsType::iterator it = mpImpl->maColumns.find(nCol);
+
+ if (it != mpImpl->maColumns.end())
+ // Block position for this column has already been fetched.
+ return &it->second;
+
+ std::pair<ColumnsType::iterator,bool> r =
+ mpImpl->maColumns.emplace(nCol, ColumnBlockPosition());
+
+ if (!r.second)
+ // insertion failed.
+ return nullptr;
+
+ it = r.first;
+
+ if (!mpImpl->mpTab->InitColumnBlockPosition(it->second, nCol))
+ return nullptr;
+
+ return &it->second;
+}
+
+void TableColumnBlockPositionSet::invalidate()
+{
+ mpImpl->maColumns.clear();
+}
+
+ScRefCellValue toRefCell( const sc::CellStoreType::const_iterator& itPos, size_t nOffset )
+{
+ switch (itPos->type)
+ {
+ case sc::element_type_numeric:
+ // Numeric cell
+ return ScRefCellValue(sc::numeric_block::at(*itPos->data, nOffset));
+ case sc::element_type_string:
+ // String cell
+ return ScRefCellValue(&sc::string_block::at(*itPos->data, nOffset));
+ case sc::element_type_edittext:
+ // Edit cell
+ return ScRefCellValue(sc::edittext_block::at(*itPos->data, nOffset));
+ case sc::element_type_formula:
+ // Formula cell
+ return ScRefCellValue(sc::formula_block::at(*itPos->data, nOffset));
+ default:
+ ;
+ }
+
+ return ScRefCellValue();
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/olinetab.cxx b/sc/source/core/data/olinetab.cxx
new file mode 100644
index 000000000..13fc17a7b
--- /dev/null
+++ b/sc/source/core/data/olinetab.cxx
@@ -0,0 +1,873 @@
+/* -*- 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 <olinetab.hxx>
+#include <address.hxx>
+#include <table.hxx>
+
+#include <osl/diagnose.h>
+
+ScOutlineEntry::ScOutlineEntry( SCCOLROW nNewStart, SCCOLROW nNewSize, bool bNewHidden ) :
+ nStart ( nNewStart ),
+ nSize ( nNewSize ),
+ bHidden ( bNewHidden ),
+ bVisible( true )
+{
+}
+
+ScOutlineEntry::ScOutlineEntry( const ScOutlineEntry& rEntry ) :
+ nStart ( rEntry.nStart ),
+ nSize ( rEntry.nSize ),
+ bHidden ( rEntry.bHidden ),
+ bVisible( rEntry.bVisible )
+{
+}
+
+SCCOLROW ScOutlineEntry::GetEnd() const
+{
+ return nStart+nSize-1;
+}
+
+void ScOutlineEntry::Move( SCCOLROW nDelta )
+{
+ SCCOLROW nNewPos = nStart + nDelta;
+ if (nNewPos<0)
+ {
+ OSL_FAIL("OutlineEntry < 0");
+ nNewPos = 0;
+ }
+ nStart = nNewPos;
+}
+
+void ScOutlineEntry::SetSize( SCSIZE nNewSize )
+{
+ if (nNewSize>0)
+ nSize = nNewSize;
+ else
+ {
+ OSL_FAIL("ScOutlineEntry Size == 0");
+ }
+}
+
+void ScOutlineEntry::SetPosSize( SCCOLROW nNewPos, SCSIZE nNewSize )
+{
+ nStart = nNewPos;
+ SetSize( nNewSize );
+}
+
+void ScOutlineEntry::SetHidden( bool bNewHidden )
+{
+ bHidden = bNewHidden;
+}
+
+void ScOutlineEntry::SetVisible( bool bNewVisible )
+{
+ bVisible = bNewVisible;
+}
+
+OString ScOutlineEntry::dumpAsString() const
+{
+ const char* const pSep = ":";
+ return OString::number(nStart) + pSep + OString::number(nSize) +
+ pSep + (bHidden ? "1" : "0") + pSep + (bVisible ? "1" : "0");
+}
+
+ScOutlineCollection::ScOutlineCollection() {}
+
+size_t ScOutlineCollection::size() const
+{
+ return m_Entries.size();
+}
+
+void ScOutlineCollection::clear()
+{
+ m_Entries.clear();
+}
+
+void ScOutlineCollection::insert(ScOutlineEntry const& rEntry)
+{
+ SCCOLROW nStart = rEntry.GetStart();
+ m_Entries.insert(std::make_pair(nStart, rEntry));
+}
+
+ScOutlineCollection::iterator ScOutlineCollection::begin()
+{
+ return m_Entries.begin();
+}
+
+ScOutlineCollection::iterator ScOutlineCollection::end()
+{
+ return m_Entries.end();
+}
+
+ScOutlineCollection::const_iterator ScOutlineCollection::begin() const
+{
+ return m_Entries.begin();
+}
+
+ScOutlineCollection::const_iterator ScOutlineCollection::end() const
+{
+ return m_Entries.end();
+}
+
+ScOutlineCollection::iterator ScOutlineCollection::erase(const iterator& pos)
+{
+ return m_Entries.erase(pos);
+}
+
+bool ScOutlineCollection::empty() const
+{
+ return m_Entries.empty();
+}
+
+ScOutlineCollection::iterator ScOutlineCollection::FindStart(SCCOLROW nMinStart)
+{
+ return m_Entries.lower_bound(nMinStart);
+}
+
+OString ScOutlineCollection::dumpAsString() const
+{
+ OString aOutput;
+ const char* const pGroupEntrySep = ",";
+ for (const auto& rKeyValuePair : m_Entries)
+ aOutput += rKeyValuePair.second.dumpAsString() + pGroupEntrySep;
+
+ return aOutput;
+}
+
+ScOutlineArray::ScOutlineArray() :
+ nDepth(0) {}
+
+ScOutlineArray::ScOutlineArray( const ScOutlineArray& rArray ) :
+ nDepth( rArray.nDepth )
+{
+ for (size_t nLevel = 0; nLevel < nDepth; ++nLevel)
+ {
+ const ScOutlineCollection& rColl = rArray.aCollections[nLevel];
+ for (const auto& rEntry : rColl)
+ {
+ const ScOutlineEntry *const pEntry = &rEntry.second;
+ aCollections[nLevel].insert(*pEntry);
+ }
+ }
+}
+
+void ScOutlineArray::FindEntry(
+ SCCOLROW nSearchPos, size_t& rFindLevel, size_t& rFindIndex,
+ size_t nMaxLevel )
+{
+ rFindLevel = rFindIndex = 0;
+
+ if (nMaxLevel > nDepth)
+ nMaxLevel = nDepth;
+
+ for (size_t nLevel = 0; nLevel < nMaxLevel; ++nLevel) //TODO: Search backwards?
+ {
+ ScOutlineCollection* pCollect = &aCollections[nLevel];
+ size_t nIndex = 0;
+ for (auto& rEntry : *pCollect)
+ {
+ ScOutlineEntry *const pEntry = &rEntry.second;
+ if (pEntry->GetStart() <= nSearchPos && pEntry->GetEnd() >= nSearchPos)
+ {
+ rFindLevel = nLevel + 1; // Next Level (for insertion)
+ rFindIndex = nIndex;
+ }
+ ++nIndex;
+ }
+ }
+}
+
+bool ScOutlineArray::Insert(
+ SCCOLROW nStartCol, SCCOLROW nEndCol, bool& rSizeChanged, bool bHidden )
+{
+ rSizeChanged = false;
+
+ size_t nStartLevel, nEndLevel, nStartIndex, nEndIndex;
+ bool bFound = false;
+
+ bool bCont;
+ sal_uInt16 nFindMax;
+ FindEntry( nStartCol, nStartLevel, nStartIndex ); // nLevel = new Level (old+1)
+ FindEntry( nEndCol, nEndLevel, nEndIndex );
+ nFindMax = std::max(nStartLevel,nEndLevel);
+ do
+ {
+ bCont = false;
+
+ if (nStartLevel == nEndLevel && nStartIndex == nEndIndex && nStartLevel < SC_OL_MAXDEPTH)
+ bFound = true;
+
+ if (!bFound && nFindMax>0)
+ {
+ --nFindMax;
+ if (nStartLevel)
+ {
+ ScOutlineCollection::const_iterator it = aCollections[nStartLevel-1].begin();
+ std::advance(it, nStartIndex);
+ if (it->second.GetStart() == nStartCol)
+ FindEntry(nStartCol, nStartLevel, nStartIndex, nFindMax);
+ }
+
+ if (nEndLevel)
+ {
+ ScOutlineCollection::const_iterator it = aCollections[nEndLevel-1].begin();
+ std::advance(it, nEndIndex);
+ if (it->second.GetEnd() == nEndCol)
+ FindEntry(nEndCol, nEndLevel, nEndIndex, nFindMax);
+ }
+ bCont = true;
+ }
+ }
+ while ( !bFound && bCont );
+
+ if (!bFound)
+ return false;
+
+ size_t nLevel = nStartLevel;
+
+ // Move the ones underneath
+ bool bNeedSize = false;
+ if (nDepth > 0)
+ {
+ for (size_t nMoveLevel = nDepth-1; nMoveLevel >= nLevel; --nMoveLevel)
+ {
+ ScOutlineCollection& rColl = aCollections[nMoveLevel];
+ ScOutlineCollection::iterator it = rColl.begin(), itEnd = rColl.end();
+ while (it != itEnd)
+ {
+ ScOutlineEntry *const pEntry = &it->second;
+ SCCOLROW nEntryStart = pEntry->GetStart();
+ if (nEntryStart >= nStartCol && nEntryStart <= nEndCol)
+ {
+ if (nMoveLevel >= SC_OL_MAXDEPTH - 1)
+ {
+ rSizeChanged = false; // No more room
+ return false;
+ }
+ aCollections[nMoveLevel+1].insert(*pEntry);
+ it = rColl.erase(it);
+ if (nMoveLevel == nDepth - 1)
+ bNeedSize = true;
+ }
+ else
+ ++it;
+ }
+ if (nMoveLevel == 0)
+ break;
+ }
+ }
+
+ if (bNeedSize)
+ {
+ ++nDepth;
+ rSizeChanged = true;
+ }
+
+ if (nDepth <= nLevel)
+ {
+ nDepth = nLevel+1;
+ rSizeChanged = true;
+ }
+
+ ScOutlineEntry aNewEntry(nStartCol, nEndCol+1-nStartCol, bHidden);
+ aNewEntry.SetVisible( true );
+ aCollections[nLevel].insert(aNewEntry);
+
+ return true;
+}
+
+bool ScOutlineArray::FindTouchedLevel(
+ SCCOLROW nBlockStart, SCCOLROW nBlockEnd, size_t& rFindLevel) const
+{
+ bool bFound = false;
+ rFindLevel = 0;
+
+ for (size_t nLevel = 0; nLevel < nDepth; ++nLevel)
+ {
+ const ScOutlineCollection* pCollect = &aCollections[nLevel];
+ for (const auto& rEntry : *pCollect)
+ {
+ const ScOutlineEntry *const pEntry = &rEntry.second;
+ SCCOLROW nStart = pEntry->GetStart();
+ SCCOLROW nEnd = pEntry->GetEnd();
+
+ if ( ( nBlockStart>=nStart && nBlockStart<=nEnd ) ||
+ ( nBlockEnd >=nStart && nBlockEnd <=nEnd ) )
+ {
+ rFindLevel = nLevel; // Actual Level
+ bFound = true;
+ }
+ }
+ }
+
+ return bFound;
+}
+
+void ScOutlineArray::PromoteSub(SCCOLROW nStartPos, SCCOLROW nEndPos, size_t nStartLevel)
+{
+ if (nStartLevel==0)
+ {
+ OSL_FAIL("PromoteSub with Level 0");
+ return;
+ }
+
+ for (size_t nLevel = nStartLevel; nLevel < nDepth; ++nLevel)
+ {
+ ScOutlineCollection& rColl = aCollections[nLevel];
+ ScOutlineCollection::iterator it = rColl.begin(), itEnd = rColl.end();
+ while (it != itEnd)
+ {
+ ScOutlineEntry *const pEntry = &it->second;
+ SCCOLROW nStart = pEntry->GetStart();
+ SCCOLROW nEnd = pEntry->GetEnd();
+ if (nStart >= nStartPos && nEnd <= nEndPos)
+ {
+ aCollections[nLevel-1].insert(*pEntry);
+
+ it = rColl.erase(it);
+ }
+ else
+ ++it;
+ }
+
+ it = rColl.begin();
+ itEnd = rColl.end();
+
+ while (it != itEnd)
+ {
+ ScOutlineEntry *const pEntry = &it->second;
+ SCCOLROW nStart = pEntry->GetStart();
+ SCCOLROW nEnd = pEntry->GetEnd();
+ if (nStart >= nStartPos && nEnd <= nEndPos)
+ {
+ aCollections[nLevel-1].insert(*pEntry);
+
+ it = rColl.erase(it);
+ }
+ else
+ ++it;
+ }
+ }
+}
+
+/**
+ * Adapt nDepth for empty Levels
+ */
+bool ScOutlineArray::DecDepth()
+{
+ bool bChanged = false;
+ bool bCont;
+ do
+ {
+ bCont = false;
+ if (nDepth)
+ {
+ if (aCollections[nDepth-1].empty())
+ {
+ --nDepth;
+ bChanged = true;
+ bCont = true;
+ }
+ }
+ }
+ while (bCont);
+
+ return bChanged;
+}
+
+bool ScOutlineArray::Remove( SCCOLROW nBlockStart, SCCOLROW nBlockEnd, bool& rSizeChanged )
+{
+ size_t nLevel;
+ FindTouchedLevel( nBlockStart, nBlockEnd, nLevel );
+
+ ScOutlineCollection* pCollect = &aCollections[nLevel];
+ ScOutlineCollection::iterator it = pCollect->begin(), itEnd = pCollect->end();
+ bool bAny = false;
+ while (it != itEnd)
+ {
+ ScOutlineEntry *const pEntry = &it->second;
+ SCCOLROW nStart = pEntry->GetStart();
+ SCCOLROW nEnd = pEntry->GetEnd();
+ if (nBlockStart <= nEnd && nBlockEnd >= nStart)
+ {
+ // Overlaps
+ pCollect->erase(it);
+ PromoteSub( nStart, nEnd, nLevel+1 );
+ itEnd = pCollect->end();
+ it = pCollect->FindStart( nEnd+1 );
+ bAny = true;
+ }
+ else
+ ++it;
+ }
+
+ if (bAny) // Adapt Depth
+ if (DecDepth())
+ rSizeChanged = true;
+
+ return bAny;
+}
+
+ScOutlineEntry* ScOutlineArray::GetEntry(size_t nLevel, size_t nIndex)
+{
+ if (nLevel >= nDepth)
+ return nullptr;
+
+ ScOutlineCollection& rColl = aCollections[nLevel];
+ if (nIndex >= rColl.size())
+ return nullptr;
+
+ ScOutlineCollection::iterator it = rColl.begin();
+ std::advance(it, nIndex);
+ return &it->second;
+}
+
+const ScOutlineEntry* ScOutlineArray::GetEntry(size_t nLevel, size_t nIndex) const
+{
+ if (nLevel >= nDepth)
+ return nullptr;
+
+ const ScOutlineCollection& rColl = aCollections[nLevel];
+ if (nIndex >= rColl.size())
+ return nullptr;
+
+ ScOutlineCollection::const_iterator it = rColl.begin();
+ std::advance(it, nIndex);
+ return &it->second;
+}
+
+size_t ScOutlineArray::GetCount(size_t nLevel) const
+{
+ if (nLevel >= nDepth)
+ return 0;
+
+ return aCollections[nLevel].size();
+}
+
+const ScOutlineEntry* ScOutlineArray::GetEntryByPos(size_t nLevel, SCCOLROW nPos) const
+{
+ if (nLevel >= nDepth)
+ return nullptr;
+
+ const ScOutlineCollection& rColl = aCollections[nLevel];
+ ScOutlineCollection::const_iterator it = std::find_if(rColl.begin(), rColl.end(),
+ [&nPos](const auto& rEntry) {
+ const ScOutlineEntry *const pEntry = &rEntry.second;
+ return pEntry->GetStart() <= nPos && nPos <= pEntry->GetEnd();
+ });
+ if (it != rColl.end())
+ return &it->second;
+
+ return nullptr;
+}
+
+bool ScOutlineArray::GetEntryIndex(size_t nLevel, SCCOLROW nPos, size_t& rnIndex) const
+{
+ if (nLevel >= nDepth)
+ return false;
+
+ // Found entry contains passed position
+ const ScOutlineCollection& rColl = aCollections[nLevel];
+ ScOutlineCollection::const_iterator it = std::find_if(rColl.begin(), rColl.end(),
+ [&nPos](const auto& rEntry) {
+ const ScOutlineEntry *const p = &rEntry.second;
+ return p->GetStart() <= nPos && nPos <= p->GetEnd();
+ });
+ if (it != rColl.end())
+ {
+ rnIndex = std::distance(rColl.begin(), it);
+ return true;
+ }
+ return false;
+}
+
+bool ScOutlineArray::GetEntryIndexInRange(
+ size_t nLevel, SCCOLROW nBlockStart, SCCOLROW nBlockEnd, size_t& rnIndex) const
+{
+ if (nLevel >= nDepth)
+ return false;
+
+ // Found entry will be completely inside of passed range
+ const ScOutlineCollection& rColl = aCollections[nLevel];
+ ScOutlineCollection::const_iterator it = std::find_if(rColl.begin(), rColl.end(),
+ [&nBlockStart, &nBlockEnd](const auto& rEntry) {
+ const ScOutlineEntry *const p = &rEntry.second;
+ return nBlockStart <= p->GetStart() && p->GetEnd() <= nBlockEnd;
+ });
+ if (it != rColl.end())
+ {
+ rnIndex = std::distance(rColl.begin(), it);
+ return true;
+ }
+ return false;
+}
+
+void ScOutlineArray::SetVisibleBelow(
+ size_t nLevel, size_t nEntry, bool bValue, bool bSkipHidden)
+{
+ const ScOutlineEntry* pEntry = GetEntry( nLevel, nEntry );
+ if (!pEntry)
+ return;
+
+ SCCOLROW nStart = pEntry->GetStart();
+ SCCOLROW nEnd = pEntry->GetEnd();
+
+ for (size_t nSubLevel = nLevel+1; nSubLevel < nDepth; ++nSubLevel)
+ {
+ ScOutlineCollection& rColl = aCollections[nSubLevel];
+ size_t nPos = 0;
+ for (auto& rEntry : rColl)
+ {
+ ScOutlineEntry *const p = &rEntry.second;
+ if (p->GetStart() >= nStart && p->GetEnd() <= nEnd)
+ {
+ p->SetVisible(bValue);
+ if (bSkipHidden && !p->IsHidden())
+ {
+ SetVisibleBelow(nSubLevel, nPos, bValue, true);
+ }
+ }
+ ++nPos;
+ }
+
+ if (bSkipHidden)
+ nSubLevel = nDepth; // Bail out
+ }
+}
+
+void ScOutlineArray::GetRange(SCCOLROW& rStart, SCCOLROW& rEnd) const
+{
+ const ScOutlineCollection& rColl = aCollections[0];
+ if (!rColl.empty())
+ {
+ ScOutlineCollection::const_iterator it = rColl.begin();
+ rStart = it->second.GetStart();
+ std::advance(it, rColl.size()-1);
+ rEnd = it->second.GetEnd();
+ }
+ else
+ rStart = rEnd = 0;
+}
+
+void ScOutlineArray::ExtendBlock(size_t nLevel, SCCOLROW& rBlkStart, SCCOLROW& rBlkEnd)
+{
+ if (nLevel >= nDepth)
+ return;
+
+ const ScOutlineCollection& rColl = aCollections[nLevel];
+ for (const auto& rEntry : rColl)
+ {
+ const ScOutlineEntry *const pEntry = &rEntry.second;
+ SCCOLROW nStart = pEntry->GetStart();
+ SCCOLROW nEnd = pEntry->GetEnd();
+
+ if (rBlkStart <= nEnd && rBlkEnd >= nStart)
+ {
+ if (nStart < rBlkStart)
+ rBlkStart = nStart;
+ if (nEnd > rBlkEnd)
+ rBlkEnd = nEnd;
+ }
+ }
+}
+
+bool ScOutlineArray::TestInsertSpace(SCSIZE nSize, SCCOLROW nMaxVal) const
+{
+ const ScOutlineCollection& rColl = aCollections[0];
+ if (rColl.empty())
+ return true;
+
+ ScOutlineCollection::const_iterator it = rColl.begin();
+ std::advance(it, rColl.size()-1);
+ SCCOLROW nEnd = it->second.GetEnd();
+ return sal::static_int_cast<SCCOLROW>(nEnd+nSize) <= nMaxVal;
+}
+
+void ScOutlineArray::InsertSpace(SCCOLROW nStartPos, SCSIZE nSize)
+{
+ ScSubOutlineIterator aIter( this );
+ ScOutlineEntry* pEntry;
+ while ((pEntry = aIter.GetNext()) != nullptr)
+ {
+ if ( pEntry->GetStart() >= nStartPos )
+ pEntry->Move(static_cast<SCCOLROW>(nSize));
+ else
+ {
+ SCCOLROW nEnd = pEntry->GetEnd();
+ // Always expand if inserted within the group
+ // When inserting at the end, only if the group is not hidden
+ if ( nEnd >= nStartPos || ( nEnd+1 >= nStartPos && !pEntry->IsHidden() ) )
+ {
+ SCSIZE nEntrySize = pEntry->GetSize();
+ nEntrySize += nSize;
+ pEntry->SetSize( nEntrySize );
+ }
+ }
+ }
+}
+
+bool ScOutlineArray::DeleteSpace(SCCOLROW nStartPos, SCSIZE nSize)
+{
+ SCCOLROW nEndPos = nStartPos + nSize - 1;
+ bool bNeedSave = false; // Do we need the original one for Undo?
+ bool bChanged = false; // For Level test
+
+ ScSubOutlineIterator aIter( this );
+ ScOutlineEntry* pEntry;
+ while((pEntry=aIter.GetNext())!=nullptr)
+ {
+ SCCOLROW nEntryStart = pEntry->GetStart();
+ SCCOLROW nEntryEnd = pEntry->GetEnd();
+ SCSIZE nEntrySize = pEntry->GetSize();
+
+ if ( nEntryEnd >= nStartPos )
+ {
+ if ( nEntryStart > nEndPos ) // Right
+ pEntry->Move(-static_cast<SCCOLROW>(nSize));
+ else if ( nEntryStart < nStartPos && nEntryEnd >= nEndPos ) // Outside
+ pEntry->SetSize( nEntrySize-nSize );
+ else
+ {
+ bNeedSave = true;
+ if ( nEntryStart >= nStartPos && nEntryEnd <= nEndPos ) // Inside
+ {
+ aIter.DeleteLast();
+ bChanged = true;
+ }
+ else if ( nEntryStart >= nStartPos ) // Top right
+ pEntry->SetPosSize( nStartPos, static_cast<SCSIZE>(nEntryEnd-nEndPos) );
+ else // Top left
+ pEntry->SetSize( static_cast<SCSIZE>(nStartPos-nEntryStart) );
+ }
+ }
+ }
+
+ if (bChanged)
+ DecDepth();
+
+ return bNeedSave;
+}
+
+bool ScOutlineArray::ManualAction(
+ SCCOLROW nStartPos, SCCOLROW nEndPos, bool bShow, const ScTable& rTable, bool bCol)
+{
+ bool bModified = false;
+ ScSubOutlineIterator aIter( this );
+ ScOutlineEntry* pEntry;
+ while((pEntry=aIter.GetNext())!=nullptr)
+ {
+ SCCOLROW nEntryStart = pEntry->GetStart();
+ SCCOLROW nEntryEnd = pEntry->GetEnd();
+
+ if (nEntryEnd>=nStartPos && nEntryStart<=nEndPos)
+ {
+ if ( pEntry->IsHidden() == bShow )
+ {
+ // #i12341# hide if all columns/rows are hidden, show if at least one
+ // is visible
+ SCCOLROW nEnd = rTable.LastHiddenColRow(nEntryStart, bCol);
+ bool bAllHidden = (nEntryEnd <= nEnd && nEnd <
+ ::std::numeric_limits<SCCOLROW>::max());
+
+ bool bToggle = ( bShow != bAllHidden );
+ if ( bToggle )
+ {
+ pEntry->SetHidden( !bShow );
+ SetVisibleBelow( aIter.LastLevel(), aIter.LastEntry(), bShow, bShow );
+ bModified = true;
+ }
+ }
+ }
+ }
+ return bModified;
+}
+
+void ScOutlineArray::RemoveAll()
+{
+ for (size_t nLevel = 0; nLevel < nDepth; ++nLevel)
+ aCollections[nLevel].clear();
+
+ nDepth = 0;
+}
+
+void ScOutlineArray::finalizeImport(const ScTable& rTable)
+{
+ ScSubOutlineIterator aIter( this );
+ ScOutlineEntry* pEntry;
+ while((pEntry=aIter.GetNext())!=nullptr)
+ {
+
+ if (!pEntry->IsHidden())
+ continue;
+
+ SCCOLROW nEntryStart = pEntry->GetStart();
+ SCCOLROW nEntryEnd = pEntry->GetEnd();
+ SCCOLROW nEnd = rTable.LastHiddenColRow(nEntryStart, false/*bCol*/);
+ bool bAllHidden = (nEntryEnd <= nEnd && nEnd <
+ ::std::numeric_limits<SCCOLROW>::max());
+
+ pEntry->SetHidden(bAllHidden);
+ SetVisibleBelow(aIter.LastLevel(), aIter.LastEntry(), !bAllHidden, !bAllHidden);
+ }
+}
+
+OString ScOutlineArray::dumpAsString() const
+{
+ OString aOutput;
+ const char* const pLevelSep = " ";
+ for (const auto& rCollection : aCollections)
+ {
+ if (rCollection.empty())
+ continue;
+ aOutput += rCollection.dumpAsString() + pLevelSep;
+ }
+
+ return aOutput;
+}
+
+ScOutlineTable::ScOutlineTable()
+{
+}
+
+ScOutlineTable::ScOutlineTable( const ScOutlineTable& rOutline ) :
+ aColOutline( rOutline.aColOutline ),
+ aRowOutline( rOutline.aRowOutline )
+{
+}
+
+bool ScOutlineTable::TestInsertCol( SCSIZE nSize )
+{
+ return aColOutline.TestInsertSpace( nSize, MAXCOL );
+}
+
+void ScOutlineTable::InsertCol( SCCOL nStartCol, SCSIZE nSize )
+{
+ aColOutline.InsertSpace( nStartCol, nSize );
+}
+
+bool ScOutlineTable::DeleteCol( SCCOL nStartCol, SCSIZE nSize )
+{
+ return aColOutline.DeleteSpace( nStartCol, nSize );
+}
+
+bool ScOutlineTable::TestInsertRow( SCSIZE nSize )
+{
+ return aRowOutline.TestInsertSpace( nSize, MAXROW );
+}
+
+void ScOutlineTable::InsertRow( SCROW nStartRow, SCSIZE nSize )
+{
+ aRowOutline.InsertSpace( nStartRow, nSize );
+}
+
+bool ScOutlineTable::DeleteRow( SCROW nStartRow, SCSIZE nSize )
+{
+ return aRowOutline.DeleteSpace( nStartRow, nSize );
+}
+
+ScSubOutlineIterator::ScSubOutlineIterator( ScOutlineArray* pOutlineArray ) :
+ pArray( pOutlineArray ),
+ nStart( 0 ),
+ nEnd( SCCOLROW_MAX ), // Iterate over all of them
+ nSubLevel( 0 ),
+ nSubEntry( 0 )
+{
+ nDepth = pArray->nDepth;
+}
+
+ScSubOutlineIterator::ScSubOutlineIterator(
+ ScOutlineArray* pOutlineArray, size_t nLevel, size_t nEntry ) :
+ pArray( pOutlineArray )
+{
+ const ScOutlineCollection& rColl = pArray->aCollections[nLevel];
+ ScOutlineCollection::const_iterator it = rColl.begin();
+ std::advance(it, nEntry);
+ const ScOutlineEntry* pEntry = &it->second;
+ nStart = pEntry->GetStart();
+ nEnd = pEntry->GetEnd();
+ nSubLevel = nLevel + 1;
+ nSubEntry = 0;
+ nDepth = pArray->nDepth;
+}
+
+ScOutlineEntry* ScSubOutlineIterator::GetNext()
+{
+ ScOutlineEntry* pEntry = nullptr;
+ bool bFound = false;
+ do
+ {
+ if (nSubLevel >= nDepth)
+ return nullptr;
+
+ ScOutlineCollection& rColl = pArray->aCollections[nSubLevel];
+ if (nSubEntry < rColl.size())
+ {
+ ScOutlineCollection::iterator it = rColl.begin();
+ std::advance(it, nSubEntry);
+ pEntry = &it->second;
+
+ if (pEntry->GetStart() >= nStart && pEntry->GetEnd() <= nEnd)
+ bFound = true;
+
+ ++nSubEntry;
+ }
+ else
+ {
+ // Go to the next sub-level
+ nSubEntry = 0;
+ ++nSubLevel;
+ }
+ }
+ while (!bFound);
+ return pEntry; // nSubLevel valid, if pEntry != 0
+}
+
+size_t ScSubOutlineIterator::LastEntry() const
+{
+ if (nSubEntry == 0)
+ {
+ OSL_FAIL("ScSubOutlineIterator::LastEntry before GetNext");
+ return 0;
+ }
+ return nSubEntry-1;
+}
+
+void ScSubOutlineIterator::DeleteLast()
+{
+ if (nSubLevel >= nDepth)
+ {
+ OSL_FAIL("ScSubOutlineIterator::DeleteLast after End");
+ return;
+ }
+ if (nSubEntry == 0)
+ {
+ OSL_FAIL("ScSubOutlineIterator::DeleteLast before GetNext");
+ return;
+ }
+
+ --nSubEntry;
+ ScOutlineCollection& rColl = pArray->aCollections[nSubLevel];
+ assert(nSubEntry < rColl.size());
+ ScOutlineCollection::iterator it = rColl.begin();
+ std::advance(it, nSubEntry);
+ rColl.erase(it);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/pagepar.cxx b/sc/source/core/data/pagepar.cxx
new file mode 100644
index 000000000..7e996fc64
--- /dev/null
+++ b/sc/source/core/data/pagepar.cxx
@@ -0,0 +1,56 @@
+/* -*- 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 <pagepar.hxx>
+
+// struct ScPageTableParam:
+
+ScPageTableParam::ScPageTableParam()
+{
+ Reset();
+}
+
+void ScPageTableParam::Reset()
+{
+ bCellContent = true;
+ bNotes=bGrid=bHeaders=bDrawings=
+ bLeftRight=bScaleAll=bScaleTo=bScalePageNum=
+ bFormulas=bNullVals=bSkipEmpty=bForceBreaks = false;
+ bTopDown=bScaleNone=bCharts=bObjects = true;
+ nScaleAll = 100;
+ nScalePageNum = nScaleWidth = nScaleHeight = 0;
+ nFirstPageNo = 1;
+}
+
+// struct ScPageAreaParam:
+
+ScPageAreaParam::ScPageAreaParam()
+{
+ Reset();
+}
+
+void ScPageAreaParam::Reset()
+{
+ bPrintArea = bRepeatRow = bRepeatCol = false;
+
+ aPrintArea = ScRange();
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/patattr.cxx b/sc/source/core/data/patattr.cxx
new file mode 100644
index 000000000..8a88e9449
--- /dev/null
+++ b/sc/source/core/data/patattr.cxx
@@ -0,0 +1,1422 @@
+/* -*- 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 <scitems.hxx>
+#include <editeng/adjustitem.hxx>
+#include <editeng/boxitem.hxx>
+#include <editeng/lineitem.hxx>
+#include <editeng/brushitem.hxx>
+#include <editeng/charreliefitem.hxx>
+#include <editeng/contouritem.hxx>
+#include <svtools/colorcfg.hxx>
+#include <editeng/colritem.hxx>
+#include <editeng/crossedoutitem.hxx>
+#include <editeng/eeitem.hxx>
+#include <editeng/emphasismarkitem.hxx>
+#include <editeng/fhgtitem.hxx>
+#include <editeng/fontitem.hxx>
+#include <editeng/forbiddenruleitem.hxx>
+#include <editeng/frmdiritem.hxx>
+#include <editeng/langitem.hxx>
+#include <editeng/postitem.hxx>
+#include <svx/rotmodit.hxx>
+#include <editeng/scriptspaceitem.hxx>
+#include <editeng/shaditem.hxx>
+#include <editeng/shdditem.hxx>
+#include <editeng/udlnitem.hxx>
+#include <editeng/wghtitem.hxx>
+#include <editeng/wrlmitem.hxx>
+#include <editeng/justifyitem.hxx>
+#include <svl/intitem.hxx>
+#include <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+#include <vcl/outdev.hxx>
+#include <tools/fract.hxx>
+#include <tools/UnitConversion.hxx>
+#include <osl/diagnose.h>
+
+#include <attrib.hxx>
+#include <patattr.hxx>
+#include <docpool.hxx>
+#include <stlsheet.hxx>
+#include <stlpool.hxx>
+#include <document.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <validat.hxx>
+#include <scmod.hxx>
+#include <fillinfo.hxx>
+#include <boost/functional/hash.hpp>
+
+ScPatternAttr::ScPatternAttr( SfxItemSet&& pItemSet, const OUString& rStyleName )
+ : SfxSetItem ( ATTR_PATTERN, std::move(pItemSet) ),
+ pName ( rStyleName ),
+ pStyle ( nullptr ),
+ mnKey(0)
+{
+}
+
+ScPatternAttr::ScPatternAttr( SfxItemSet&& pItemSet )
+ : SfxSetItem ( ATTR_PATTERN, std::move(pItemSet) ),
+ pStyle ( nullptr ),
+ mnKey(0)
+{
+}
+
+ScPatternAttr::ScPatternAttr( SfxItemPool* pItemPool )
+ : SfxSetItem ( ATTR_PATTERN, SfxItemSetFixed<ATTR_PATTERN_START, ATTR_PATTERN_END>( *pItemPool ) ),
+ pStyle ( nullptr ),
+ mnKey(0)
+{
+}
+
+ScPatternAttr::ScPatternAttr( const ScPatternAttr& rPatternAttr )
+ : SfxSetItem ( rPatternAttr ),
+ pName ( rPatternAttr.pName ),
+ pStyle ( rPatternAttr.pStyle ),
+ mnKey(rPatternAttr.mnKey)
+{
+}
+
+ScPatternAttr* ScPatternAttr::Clone( SfxItemPool *pPool ) const
+{
+ ScPatternAttr* pPattern = new ScPatternAttr( GetItemSet().CloneAsValue(true, pPool) );
+
+ pPattern->pStyle = pStyle;
+ pPattern->pName = pName;
+
+ return pPattern;
+}
+
+static bool StrCmp( const OUString* pStr1, const OUString* pStr2 )
+{
+ if (pStr1 == pStr2)
+ return true;
+ if (pStr1 && !pStr2)
+ return false;
+ if (!pStr1 && pStr2)
+ return false;
+ return *pStr1 == *pStr2;
+}
+
+constexpr size_t compareSize = ATTR_PATTERN_END - ATTR_PATTERN_START + 1;
+
+std::optional<bool> ScPatternAttr::FastEqualPatternSets( const SfxItemSet& rSet1, const SfxItemSet& rSet2 )
+{
+ // #i62090# The SfxItemSet in the SfxSetItem base class always has the same ranges
+ // (single range from ATTR_PATTERN_START to ATTR_PATTERN_END), and the items are pooled,
+ // so it's enough to compare just the pointers (Count just because it's even faster).
+
+ if ( rSet1.Count() != rSet2.Count() )
+ return { false };
+
+ // Actually test_tdf133629 from UITest_calc_tests9 somehow manages to have
+ // a different range (and I don't understand enough why), so better be safe and compare fully.
+ if( rSet1.TotalCount() != compareSize || rSet2.TotalCount() != compareSize )
+ return std::nullopt;
+
+ SfxPoolItem const ** pItems1 = rSet1.GetItems_Impl(); // inline method of SfxItemSet
+ SfxPoolItem const ** pItems2 = rSet2.GetItems_Impl();
+
+ return { memcmp( pItems1, pItems2, compareSize * sizeof(pItems1[0]) ) == 0 };
+}
+
+static bool EqualPatternSets( const SfxItemSet& rSet1, const SfxItemSet& rSet2 )
+{
+ std::optional<bool> equal = ScPatternAttr::FastEqualPatternSets( rSet1, rSet2 );
+ if(equal.has_value())
+ return *equal;
+ return rSet1 == rSet2;
+}
+
+bool ScPatternAttr::operator==( const SfxPoolItem& rCmp ) const
+{
+ // #i62090# Use quick comparison between ScPatternAttr's ItemSets
+
+ if (!SfxPoolItem::operator==(rCmp) )
+ return false;
+ if (!mxHashCode)
+ CalcHashCode();
+ auto const & rOther = static_cast<const ScPatternAttr&>(rCmp);
+ if (!rOther.mxHashCode)
+ rOther.CalcHashCode();
+ if (*mxHashCode != *rOther.mxHashCode)
+ return false;
+ return EqualPatternSets( GetItemSet(), rOther.GetItemSet() ) &&
+ StrCmp( GetStyleName(), rOther.GetStyleName() );
+}
+
+SfxPoolItem::lookup_iterator ScPatternAttr::Lookup(lookup_iterator begin, lookup_iterator end ) const
+{
+ if( !mxHashCode )
+ CalcHashCode();
+ if( *mxHashCode != 0 )
+ {
+ for( auto it = begin; it != end; ++it)
+ {
+ const ScPatternAttr* other = static_cast<const ScPatternAttr*>(*it);
+ if( !other->mxHashCode )
+ other->CalcHashCode();
+ if (*mxHashCode == *other->mxHashCode
+ && EqualPatternSets( GetItemSet(), other->GetItemSet())
+ && StrCmp( GetStyleName(), other->GetStyleName()))
+ {
+ return it;
+ }
+ }
+ }
+ return end;
+}
+
+SvxCellOrientation ScPatternAttr::GetCellOrientation( const SfxItemSet& rItemSet, const SfxItemSet* pCondSet )
+{
+ SvxCellOrientation eOrient = SvxCellOrientation::Standard;
+
+ if( GetItem( ATTR_STACKED, rItemSet, pCondSet ).GetValue() )
+ {
+ eOrient = SvxCellOrientation::Stacked;
+ }
+ else
+ {
+ Degree100 nAngle = GetItem( ATTR_ROTATE_VALUE, rItemSet, pCondSet ).GetValue();
+ if( nAngle == 9000_deg100 )
+ eOrient = SvxCellOrientation::BottomUp;
+ else if( nAngle == 27000_deg100 )
+ eOrient = SvxCellOrientation::TopBottom;
+ }
+
+ return eOrient;
+}
+
+SvxCellOrientation ScPatternAttr::GetCellOrientation( const SfxItemSet* pCondSet ) const
+{
+ return GetCellOrientation( GetItemSet(), pCondSet );
+}
+
+namespace {
+
+void getFontIDsByScriptType(SvtScriptType nScript,
+ TypedWhichId<SvxFontItem>& nFontId,
+ TypedWhichId<SvxFontHeightItem>& nHeightId,
+ TypedWhichId<SvxWeightItem>& nWeightId,
+ TypedWhichId<SvxPostureItem>& nPostureId,
+ TypedWhichId<SvxLanguageItem>& nLangId)
+{
+ if ( nScript == SvtScriptType::ASIAN )
+ {
+ nFontId = ATTR_CJK_FONT;
+ nHeightId = ATTR_CJK_FONT_HEIGHT;
+ nWeightId = ATTR_CJK_FONT_WEIGHT;
+ nPostureId = ATTR_CJK_FONT_POSTURE;
+ nLangId = ATTR_CJK_FONT_LANGUAGE;
+ }
+ else if ( nScript == SvtScriptType::COMPLEX )
+ {
+ nFontId = ATTR_CTL_FONT;
+ nHeightId = ATTR_CTL_FONT_HEIGHT;
+ nWeightId = ATTR_CTL_FONT_WEIGHT;
+ nPostureId = ATTR_CTL_FONT_POSTURE;
+ nLangId = ATTR_CTL_FONT_LANGUAGE;
+ }
+ else
+ {
+ nFontId = ATTR_FONT;
+ nHeightId = ATTR_FONT_HEIGHT;
+ nWeightId = ATTR_FONT_WEIGHT;
+ nPostureId = ATTR_FONT_POSTURE;
+ nLangId = ATTR_FONT_LANGUAGE;
+ }
+}
+
+}
+
+void ScPatternAttr::GetFont(
+ vcl::Font& rFont, const SfxItemSet& rItemSet, ScAutoFontColorMode eAutoMode,
+ const OutputDevice* pOutDev, const Fraction* pScale,
+ const SfxItemSet* pCondSet, SvtScriptType nScript,
+ const Color* pBackConfigColor, const Color* pTextConfigColor )
+{
+ // Read items
+
+ const SvxFontItem* pFontAttr;
+ sal_uInt32 nFontHeight;
+ FontWeight eWeight;
+ FontItalic eItalic;
+ FontLineStyle eUnder;
+ FontLineStyle eOver;
+ bool bWordLine;
+ FontStrikeout eStrike;
+ bool bOutline;
+ bool bShadow;
+ FontEmphasisMark eEmphasis;
+ FontRelief eRelief;
+ Color aColor;
+ LanguageType eLang;
+
+ TypedWhichId<SvxFontItem> nFontId(0);
+ TypedWhichId<SvxFontHeightItem> nHeightId(0);
+ TypedWhichId<SvxWeightItem> nWeightId(0);
+ TypedWhichId<SvxPostureItem> nPostureId(0);
+ TypedWhichId<SvxLanguageItem> nLangId(0);
+ getFontIDsByScriptType(nScript, nFontId, nHeightId, nWeightId, nPostureId, nLangId);
+
+ if ( pCondSet )
+ {
+ pFontAttr = pCondSet->GetItemIfSet( nFontId );
+ if ( !pFontAttr )
+ pFontAttr = &rItemSet.Get( nFontId );
+
+ const SvxFontHeightItem* pFontHeightItem = pCondSet->GetItemIfSet( nHeightId );
+ if ( !pFontHeightItem )
+ pFontHeightItem = &rItemSet.Get( nHeightId );
+ nFontHeight = pFontHeightItem->GetHeight();
+
+ const SvxWeightItem* pFontHWeightItem = pCondSet->GetItemIfSet( nWeightId );
+ if ( !pFontHWeightItem )
+ pFontHWeightItem = &rItemSet.Get( nWeightId );
+ eWeight = pFontHWeightItem->GetValue();
+
+ const SvxPostureItem* pPostureItem = pCondSet->GetItemIfSet( nPostureId );
+ if ( !pPostureItem )
+ pPostureItem = &rItemSet.Get( nPostureId );
+ eItalic = pPostureItem->GetValue();
+
+ const SvxUnderlineItem* pUnderlineItem = pCondSet->GetItemIfSet( ATTR_FONT_UNDERLINE );
+ if ( !pUnderlineItem )
+ pUnderlineItem = &rItemSet.Get( ATTR_FONT_UNDERLINE );
+ eUnder = pUnderlineItem->GetValue();
+
+ const SvxOverlineItem* pOverlineItem = pCondSet->GetItemIfSet( ATTR_FONT_OVERLINE );
+ if ( !pOverlineItem )
+ pOverlineItem = &rItemSet.Get( ATTR_FONT_OVERLINE );
+ eOver = pOverlineItem->GetValue();
+
+ const SvxWordLineModeItem* pWordlineItem = pCondSet->GetItemIfSet( ATTR_FONT_WORDLINE );
+ if ( !pWordlineItem )
+ pWordlineItem = &rItemSet.Get( ATTR_FONT_WORDLINE );
+ bWordLine = pWordlineItem->GetValue();
+
+ const SvxCrossedOutItem* pCrossedOutItem = pCondSet->GetItemIfSet( ATTR_FONT_CROSSEDOUT );
+ if ( !pCrossedOutItem )
+ pCrossedOutItem = &rItemSet.Get( ATTR_FONT_CROSSEDOUT );
+ eStrike = pCrossedOutItem->GetValue();
+
+ const SvxContourItem* pContourItem = pCondSet->GetItemIfSet( ATTR_FONT_CONTOUR );
+ if ( !pContourItem )
+ pContourItem = &rItemSet.Get( ATTR_FONT_CONTOUR );
+ bOutline = pContourItem->GetValue();
+
+ const SvxShadowedItem* pShadowedItem = pCondSet->GetItemIfSet( ATTR_FONT_SHADOWED );
+ if ( !pShadowedItem )
+ pShadowedItem = &rItemSet.Get( ATTR_FONT_SHADOWED );
+ bShadow = pShadowedItem->GetValue();
+
+ const SvxEmphasisMarkItem* pEmphasisMarkItem = pCondSet->GetItemIfSet( ATTR_FONT_EMPHASISMARK );
+ if ( !pEmphasisMarkItem )
+ pEmphasisMarkItem = &rItemSet.Get( ATTR_FONT_EMPHASISMARK );
+ eEmphasis = pEmphasisMarkItem->GetEmphasisMark();
+
+ const SvxCharReliefItem* pCharReliefItem = pCondSet->GetItemIfSet( ATTR_FONT_RELIEF );
+ if ( !pCharReliefItem )
+ pCharReliefItem = &rItemSet.Get( ATTR_FONT_RELIEF );
+ eRelief = pCharReliefItem->GetValue();
+
+ const SvxColorItem* pColorItem = pCondSet->GetItemIfSet( ATTR_FONT_COLOR );
+ if ( !pColorItem )
+ pColorItem = &rItemSet.Get( ATTR_FONT_COLOR );
+ aColor = pColorItem->GetValue();
+
+ const SvxLanguageItem* pLanguageItem = pCondSet->GetItemIfSet( nLangId );
+ if ( !pLanguageItem )
+ pLanguageItem = &rItemSet.Get( nLangId );
+ eLang = pLanguageItem->GetLanguage();
+ }
+ else // Everything from rItemSet
+ {
+ pFontAttr = &rItemSet.Get( nFontId );
+ nFontHeight = rItemSet.Get( nHeightId ).GetHeight();
+ eWeight = rItemSet.Get( nWeightId ).GetValue();
+ eItalic = rItemSet.Get( nPostureId ).GetValue();
+ eUnder = rItemSet.Get( ATTR_FONT_UNDERLINE ).GetValue();
+ eOver = rItemSet.Get( ATTR_FONT_OVERLINE ).GetValue();
+ bWordLine = rItemSet.Get( ATTR_FONT_WORDLINE ).GetValue();
+ eStrike = rItemSet.Get( ATTR_FONT_CROSSEDOUT ).GetValue();
+ bOutline = rItemSet.Get( ATTR_FONT_CONTOUR ).GetValue();
+ bShadow = rItemSet.Get( ATTR_FONT_SHADOWED ).GetValue();
+ eEmphasis = rItemSet.Get( ATTR_FONT_EMPHASISMARK ).GetEmphasisMark();
+ eRelief = rItemSet.Get( ATTR_FONT_RELIEF ).GetValue();
+ aColor = rItemSet.Get( ATTR_FONT_COLOR ).GetValue();
+ // for graphite language features
+ eLang = rItemSet.Get( nLangId ).GetLanguage();
+ }
+ OSL_ENSURE(pFontAttr,"Oops?");
+
+ // Evaluate
+
+ // FontItem:
+
+ if (rFont.GetFamilyName() != pFontAttr->GetFamilyName())
+ rFont.SetFamilyName( pFontAttr->GetFamilyName() );
+ if (rFont.GetStyleName() != pFontAttr->GetStyleName())
+ rFont.SetStyleName( pFontAttr->GetStyleName() );
+
+ rFont.SetFamily( pFontAttr->GetFamily() );
+ rFont.SetCharSet( pFontAttr->GetCharSet() );
+ rFont.SetPitch( pFontAttr->GetPitch() );
+
+ rFont.SetLanguage(eLang);
+
+ // Size
+
+ if ( pOutDev != nullptr )
+ {
+ Size aEffSize;
+ Fraction aFraction( 1,1 );
+ if (pScale)
+ aFraction = *pScale;
+ Size aSize( 0, static_cast<tools::Long>(nFontHeight) );
+ MapMode aDestMode = pOutDev->GetMapMode();
+ MapMode aSrcMode( MapUnit::MapTwip, Point(), aFraction, aFraction );
+ if (aDestMode.GetMapUnit() == MapUnit::MapPixel && pOutDev->GetDPIX() > 0)
+ aEffSize = pOutDev->LogicToPixel( aSize, aSrcMode );
+ else
+ {
+ Fraction aFractOne(1,1);
+ aDestMode.SetScaleX( aFractOne );
+ aDestMode.SetScaleY( aFractOne );
+ aEffSize = OutputDevice::LogicToLogic( aSize, aSrcMode, aDestMode );
+ }
+ rFont.SetFontSize( aEffSize );
+ }
+ else /* if pOutDev != NULL */
+ {
+ rFont.SetFontSize( Size( 0, static_cast<tools::Long>(nFontHeight) ) );
+ }
+
+ // determine effective font color
+
+ if ( ( aColor == COL_AUTO && eAutoMode != SC_AUTOCOL_RAW ) ||
+ eAutoMode == SC_AUTOCOL_IGNOREFONT || eAutoMode == SC_AUTOCOL_IGNOREALL )
+ {
+ if ( eAutoMode == SC_AUTOCOL_BLACK )
+ aColor = COL_BLACK;
+ else
+ {
+ // get background color from conditional or own set
+ Color aBackColor;
+ if ( pCondSet )
+ {
+ const SvxBrushItem* pItem = pCondSet->GetItemIfSet( ATTR_BACKGROUND );
+ if ( !pItem )
+ pItem = &rItemSet.Get( ATTR_BACKGROUND );
+ aBackColor = pItem->GetColor();
+ }
+ else
+ aBackColor = rItemSet.Get( ATTR_BACKGROUND ).GetColor();
+
+ // if background color attribute is transparent, use window color for brightness comparisons
+ if ( aBackColor == COL_TRANSPARENT ||
+ eAutoMode == SC_AUTOCOL_IGNOREBACK || eAutoMode == SC_AUTOCOL_IGNOREALL )
+ {
+ if ( eAutoMode == SC_AUTOCOL_PRINT )
+ aBackColor = COL_WHITE;
+ else if ( pBackConfigColor )
+ {
+ // pBackConfigColor can be used to avoid repeated lookup of the configured color
+ aBackColor = *pBackConfigColor;
+ }
+ else
+ aBackColor = SC_MOD()->GetColorConfig().GetColorValue(svtools::DOCCOLOR).nColor;
+ }
+
+ // get system text color for comparison
+ Color aSysTextColor;
+ if ( eAutoMode == SC_AUTOCOL_PRINT )
+ aSysTextColor = COL_BLACK;
+ else if ( pTextConfigColor )
+ {
+ // pTextConfigColor can be used to avoid repeated lookup of the configured color
+ aSysTextColor = *pTextConfigColor;
+ }
+ else
+ aSysTextColor = SC_MOD()->GetColorConfig().GetColorValue(svtools::FONTCOLOR).nColor;
+
+ // select the resulting color
+ if ( aBackColor.IsDark() && aSysTextColor.IsDark() )
+ {
+ // use white instead of dark on dark
+ aColor = COL_WHITE;
+ }
+ else if ( aBackColor.IsBright() && aSysTextColor.IsBright() )
+ {
+ // use black instead of bright on bright
+ aColor = COL_BLACK;
+ }
+ else
+ {
+ // use aSysTextColor (black for SC_AUTOCOL_PRINT, from style settings otherwise)
+ aColor = aSysTextColor;
+ }
+ }
+ }
+
+ // set font effects
+ rFont.SetWeight( eWeight );
+ rFont.SetItalic( eItalic );
+ rFont.SetUnderline( eUnder );
+ rFont.SetOverline( eOver );
+ rFont.SetWordLineMode( bWordLine );
+ rFont.SetStrikeout( eStrike );
+ rFont.SetOutline( bOutline );
+ rFont.SetShadow( bShadow );
+ rFont.SetEmphasisMark( eEmphasis );
+ rFont.SetRelief( eRelief );
+ rFont.SetColor( aColor );
+ rFont.SetTransparent( true );
+}
+
+void ScPatternAttr::GetFont(
+ vcl::Font& rFont, ScAutoFontColorMode eAutoMode,
+ const OutputDevice* pOutDev, const Fraction* pScale,
+ const SfxItemSet* pCondSet, SvtScriptType nScript,
+ const Color* pBackConfigColor, const Color* pTextConfigColor ) const
+{
+ GetFont( rFont, GetItemSet(), eAutoMode, pOutDev, pScale, pCondSet, nScript, pBackConfigColor, pTextConfigColor );
+}
+
+ScDxfFont ScPatternAttr::GetDxfFont(const SfxItemSet& rItemSet, SvtScriptType nScript)
+{
+ TypedWhichId<SvxFontItem> nFontId(0);
+ TypedWhichId<SvxFontHeightItem> nHeightId(0);
+ TypedWhichId<SvxWeightItem> nWeightId(0);
+ TypedWhichId<SvxPostureItem> nPostureId(0);
+ TypedWhichId<SvxLanguageItem> nLangId(0);
+ getFontIDsByScriptType(nScript, nFontId, nHeightId, nWeightId, nPostureId, nLangId);
+
+ ScDxfFont aReturn;
+
+ if ( const SvxFontItem* pItem = rItemSet.GetItemIfSet( nFontId ) )
+ {
+ aReturn.pFontAttr = pItem;
+ }
+
+ if ( const SvxFontHeightItem* pItem = rItemSet.GetItemIfSet( nHeightId ) )
+ {
+ aReturn.nFontHeight = pItem->GetHeight();
+ }
+
+ if ( const SvxWeightItem* pItem = rItemSet.GetItemIfSet( nWeightId ) )
+ {
+ aReturn.eWeight = pItem->GetValue();
+ }
+
+ if ( const SvxPostureItem* pItem = rItemSet.GetItemIfSet( nPostureId ) )
+ {
+ aReturn.eItalic = pItem->GetValue();
+ }
+
+ if ( const SvxUnderlineItem* pItem = rItemSet.GetItemIfSet( ATTR_FONT_UNDERLINE ) )
+ {
+ pItem = &rItemSet.Get( ATTR_FONT_UNDERLINE );
+ aReturn.eUnder = pItem->GetValue();
+ }
+
+ if ( const SvxOverlineItem* pItem = rItemSet.GetItemIfSet( ATTR_FONT_OVERLINE ) )
+ {
+ aReturn.eOver = pItem->GetValue();
+ }
+
+ if ( const SvxWordLineModeItem* pItem = rItemSet.GetItemIfSet( ATTR_FONT_WORDLINE ) )
+ {
+ aReturn.bWordLine = pItem->GetValue();
+ }
+
+ if ( const SvxCrossedOutItem* pItem = rItemSet.GetItemIfSet( ATTR_FONT_CROSSEDOUT ) )
+ {
+ pItem = &rItemSet.Get( ATTR_FONT_CROSSEDOUT );
+ aReturn.eStrike = pItem->GetValue();
+ }
+
+ if ( const SvxContourItem* pItem = rItemSet.GetItemIfSet( ATTR_FONT_CONTOUR ) )
+ {
+ aReturn.bOutline = pItem->GetValue();
+ }
+
+ if ( const SvxShadowedItem* pItem = rItemSet.GetItemIfSet( ATTR_FONT_SHADOWED ) )
+ {
+ pItem = &rItemSet.Get( ATTR_FONT_SHADOWED );
+ aReturn.bShadow = pItem->GetValue();
+ }
+
+ if ( const SvxEmphasisMarkItem* pItem = rItemSet.GetItemIfSet( ATTR_FONT_EMPHASISMARK ) )
+ {
+ aReturn.eEmphasis = pItem->GetEmphasisMark();
+ }
+
+ if ( const SvxCharReliefItem* pItem = rItemSet.GetItemIfSet( ATTR_FONT_RELIEF ) )
+ {
+ aReturn.eRelief = pItem->GetValue();
+ }
+
+ if ( const SvxColorItem* pItem = rItemSet.GetItemIfSet( ATTR_FONT_COLOR ) )
+ {
+ aReturn.aColor = pItem->GetValue();
+ }
+
+ if ( const SvxLanguageItem* pItem = rItemSet.GetItemIfSet( nLangId ) )
+ {
+ aReturn.eLang = pItem->GetLanguage();
+ }
+
+ return aReturn;
+}
+
+template <class T>
+static void lcl_populate( std::unique_ptr<T>& rxItem, TypedWhichId<T> nWhich, const SfxItemSet& rSrcSet, const SfxItemSet* pCondSet )
+{
+ const T* pItem = pCondSet->GetItemIfSet( nWhich );
+ if ( !pItem )
+ pItem = &rSrcSet.Get( nWhich );
+ rxItem.reset(pItem->Clone());
+}
+
+void ScPatternAttr::FillToEditItemSet( SfxItemSet& rEditSet, const SfxItemSet& rSrcSet, const SfxItemSet* pCondSet )
+{
+ // Read Items
+
+ std::unique_ptr<SvxColorItem> aColorItem(std::make_unique<SvxColorItem>(EE_CHAR_COLOR)); // use item as-is
+ std::unique_ptr<SvxFontItem> aFontItem(std::make_unique<SvxFontItem>(EE_CHAR_FONTINFO)); // use item as-is
+ std::unique_ptr<SvxFontItem> aCjkFontItem(std::make_unique<SvxFontItem>(EE_CHAR_FONTINFO_CJK)); // use item as-is
+ std::unique_ptr<SvxFontItem> aCtlFontItem(std::make_unique<SvxFontItem>(EE_CHAR_FONTINFO_CTL)); // use item as-is
+ tools::Long nTHeight, nCjkTHeight, nCtlTHeight; // Twips
+ FontWeight eWeight, eCjkWeight, eCtlWeight;
+ std::unique_ptr<SvxUnderlineItem> aUnderlineItem(std::make_unique<SvxUnderlineItem>(LINESTYLE_NONE, EE_CHAR_UNDERLINE));
+ std::unique_ptr<SvxOverlineItem> aOverlineItem(std::make_unique<SvxOverlineItem>(LINESTYLE_NONE, EE_CHAR_OVERLINE));
+ bool bWordLine;
+ FontStrikeout eStrike;
+ FontItalic eItalic, eCjkItalic, eCtlItalic;
+ bool bOutline;
+ bool bShadow;
+ bool bForbidden;
+ FontEmphasisMark eEmphasis;
+ FontRelief eRelief;
+ LanguageType eLang, eCjkLang, eCtlLang;
+ bool bHyphenate;
+ SvxFrameDirection eDirection;
+
+ //TODO: additional parameter to control if language is needed?
+
+ if ( pCondSet )
+ {
+ lcl_populate(aColorItem, ATTR_FONT_COLOR, rSrcSet, pCondSet);
+ lcl_populate(aFontItem, ATTR_FONT, rSrcSet, pCondSet);
+ lcl_populate(aCjkFontItem, ATTR_CJK_FONT, rSrcSet, pCondSet);
+ lcl_populate(aCtlFontItem, ATTR_CTL_FONT, rSrcSet, pCondSet);
+
+ const SvxFontHeightItem* pFontHeightItem = pCondSet->GetItemIfSet( ATTR_FONT_HEIGHT );
+ if (!pFontHeightItem)
+ pFontHeightItem = &rSrcSet.Get( ATTR_FONT_HEIGHT );
+ nTHeight = pFontHeightItem->GetHeight();
+ pFontHeightItem = pCondSet->GetItemIfSet( ATTR_CJK_FONT_HEIGHT );
+ if ( !pFontHeightItem )
+ pFontHeightItem = &rSrcSet.Get( ATTR_CJK_FONT_HEIGHT );
+ nCjkTHeight = pFontHeightItem->GetHeight();
+ pFontHeightItem = pCondSet->GetItemIfSet( ATTR_CTL_FONT_HEIGHT );
+ if ( !pFontHeightItem )
+ pFontHeightItem = &rSrcSet.Get( ATTR_CTL_FONT_HEIGHT );
+ nCtlTHeight = pFontHeightItem->GetHeight();
+
+ const SvxWeightItem* pWeightItem = pCondSet->GetItemIfSet( ATTR_FONT_WEIGHT );
+ if ( !pWeightItem )
+ pWeightItem = &rSrcSet.Get( ATTR_FONT_WEIGHT );
+ eWeight = pWeightItem->GetValue();
+ pWeightItem = pCondSet->GetItemIfSet( ATTR_CJK_FONT_WEIGHT );
+ if ( !pWeightItem )
+ pWeightItem = &rSrcSet.Get( ATTR_CJK_FONT_WEIGHT );
+ eCjkWeight = pWeightItem->GetValue();
+ pWeightItem = pCondSet->GetItemIfSet( ATTR_CTL_FONT_WEIGHT );
+ if ( !pWeightItem )
+ pWeightItem = &rSrcSet.Get( ATTR_CTL_FONT_WEIGHT );
+ eCtlWeight = pWeightItem->GetValue();
+
+ const SvxPostureItem* pPostureItem = pCondSet->GetItemIfSet( ATTR_FONT_POSTURE );
+ if ( !pPostureItem )
+ pPostureItem = &rSrcSet.Get( ATTR_FONT_POSTURE );
+ eItalic = pPostureItem->GetValue();
+ pPostureItem = pCondSet->GetItemIfSet( ATTR_CJK_FONT_POSTURE );
+ if ( !pPostureItem )
+ pPostureItem = &rSrcSet.Get( ATTR_CJK_FONT_POSTURE );
+ eCjkItalic = pPostureItem->GetValue();
+ pPostureItem = pCondSet->GetItemIfSet( ATTR_CTL_FONT_POSTURE );
+ if ( !pPostureItem )
+ pPostureItem = &rSrcSet.Get( ATTR_CTL_FONT_POSTURE );
+ eCtlItalic = pPostureItem->GetValue();
+
+ lcl_populate(aUnderlineItem, ATTR_FONT_UNDERLINE, rSrcSet, pCondSet);
+ lcl_populate(aOverlineItem, ATTR_FONT_OVERLINE, rSrcSet, pCondSet);
+
+ const SvxWordLineModeItem* pWordLineModeItem = pCondSet->GetItemIfSet( ATTR_FONT_WORDLINE );
+ if ( !pWordLineModeItem )
+ pWordLineModeItem = &rSrcSet.Get( ATTR_FONT_WORDLINE );
+ bWordLine = pWordLineModeItem->GetValue();
+
+ const SvxCrossedOutItem* pCrossedOutItem = pCondSet->GetItemIfSet( ATTR_FONT_CROSSEDOUT );
+ if ( !pCrossedOutItem )
+ pCrossedOutItem = &rSrcSet.Get( ATTR_FONT_CROSSEDOUT );
+ eStrike = pCrossedOutItem->GetValue();
+
+ const SvxContourItem* pContourItem = pCondSet->GetItemIfSet( ATTR_FONT_CONTOUR );
+ if ( !pContourItem )
+ pContourItem = &rSrcSet.Get( ATTR_FONT_CONTOUR );
+ bOutline = pContourItem->GetValue();
+
+ const SvxShadowedItem* pShadowedItem = pCondSet->GetItemIfSet( ATTR_FONT_SHADOWED );
+ if ( !pShadowedItem )
+ pShadowedItem = &rSrcSet.Get( ATTR_FONT_SHADOWED );
+ bShadow = pShadowedItem->GetValue();
+
+ const SvxForbiddenRuleItem* pForbiddenRuleItem = pCondSet->GetItemIfSet( ATTR_FORBIDDEN_RULES );
+ if ( !pForbiddenRuleItem )
+ pForbiddenRuleItem = &rSrcSet.Get( ATTR_FORBIDDEN_RULES );
+ bForbidden = pForbiddenRuleItem->GetValue();
+
+ const SvxEmphasisMarkItem* pEmphasisMarkItem = pCondSet->GetItemIfSet( ATTR_FONT_EMPHASISMARK );
+ if ( !pEmphasisMarkItem )
+ pEmphasisMarkItem = &rSrcSet.Get( ATTR_FONT_EMPHASISMARK );
+ eEmphasis = pEmphasisMarkItem->GetEmphasisMark();
+ const SvxCharReliefItem* pCharReliefItem = pCondSet->GetItemIfSet( ATTR_FONT_RELIEF );
+ if ( !pCharReliefItem )
+ pCharReliefItem = &rSrcSet.Get( ATTR_FONT_RELIEF );
+ eRelief = pCharReliefItem->GetValue();
+
+ const SvxLanguageItem* pLanguageItem = pCondSet->GetItemIfSet( ATTR_FONT_LANGUAGE );
+ if ( !pLanguageItem )
+ pLanguageItem = &rSrcSet.Get( ATTR_FONT_LANGUAGE );
+ eLang = pLanguageItem->GetLanguage();
+ pLanguageItem = pCondSet->GetItemIfSet( ATTR_CJK_FONT_LANGUAGE );
+ if ( !pLanguageItem )
+ pLanguageItem = &rSrcSet.Get( ATTR_CJK_FONT_LANGUAGE );
+ eCjkLang = pLanguageItem->GetLanguage();
+ pLanguageItem = pCondSet->GetItemIfSet( ATTR_CTL_FONT_LANGUAGE );
+ if ( !pLanguageItem )
+ pLanguageItem = &rSrcSet.Get( ATTR_CTL_FONT_LANGUAGE );
+ eCtlLang = pLanguageItem->GetLanguage();
+
+ const ScHyphenateCell* pHyphenateCell = pCondSet->GetItemIfSet( ATTR_HYPHENATE );
+ if ( !pHyphenateCell )
+ pHyphenateCell = &rSrcSet.Get( ATTR_HYPHENATE );
+ bHyphenate = pHyphenateCell->GetValue();
+
+ const SvxFrameDirectionItem* pFrameDirectionItem = pCondSet->GetItemIfSet( ATTR_WRITINGDIR );
+ if ( !pFrameDirectionItem )
+ pFrameDirectionItem = &rSrcSet.Get( ATTR_WRITINGDIR );
+ eDirection = pFrameDirectionItem->GetValue();
+ }
+ else // Everything directly from Pattern
+ {
+ aColorItem.reset(rSrcSet.Get(ATTR_FONT_COLOR).Clone());
+ aFontItem.reset(rSrcSet.Get(ATTR_FONT).Clone());
+ aCjkFontItem.reset(rSrcSet.Get(ATTR_CJK_FONT).Clone());
+ aCtlFontItem.reset(rSrcSet.Get(ATTR_CTL_FONT).Clone());
+ nTHeight = rSrcSet.Get( ATTR_FONT_HEIGHT ).GetHeight();
+ nCjkTHeight = rSrcSet.Get( ATTR_CJK_FONT_HEIGHT ).GetHeight();
+ nCtlTHeight = rSrcSet.Get( ATTR_CTL_FONT_HEIGHT ).GetHeight();
+ eWeight = rSrcSet.Get( ATTR_FONT_WEIGHT ).GetValue();
+ eCjkWeight = rSrcSet.Get( ATTR_CJK_FONT_WEIGHT ).GetValue();
+ eCtlWeight = rSrcSet.Get( ATTR_CTL_FONT_WEIGHT ).GetValue();
+ eItalic = rSrcSet.Get( ATTR_FONT_POSTURE ).GetValue();
+ eCjkItalic = rSrcSet.Get( ATTR_CJK_FONT_POSTURE ).GetValue();
+ eCtlItalic = rSrcSet.Get( ATTR_CTL_FONT_POSTURE ).GetValue();
+ aUnderlineItem.reset(rSrcSet.Get(ATTR_FONT_UNDERLINE).Clone());
+ aOverlineItem.reset(rSrcSet.Get(ATTR_FONT_OVERLINE).Clone());
+ bWordLine = rSrcSet.Get( ATTR_FONT_WORDLINE ).GetValue();
+ eStrike = rSrcSet.Get( ATTR_FONT_CROSSEDOUT ).GetValue();
+ bOutline = rSrcSet.Get( ATTR_FONT_CONTOUR ).GetValue();
+ bShadow = rSrcSet.Get( ATTR_FONT_SHADOWED ).GetValue();
+ bForbidden = rSrcSet.Get( ATTR_FORBIDDEN_RULES ).GetValue();
+ eEmphasis = rSrcSet.Get( ATTR_FONT_EMPHASISMARK ).GetEmphasisMark();
+ eRelief = rSrcSet.Get( ATTR_FONT_RELIEF ).GetValue();
+ eLang = rSrcSet.Get( ATTR_FONT_LANGUAGE ).GetLanguage();
+ eCjkLang = rSrcSet.Get( ATTR_CJK_FONT_LANGUAGE ).GetLanguage();
+ eCtlLang = rSrcSet.Get( ATTR_CTL_FONT_LANGUAGE ).GetLanguage();
+ bHyphenate = rSrcSet.Get( ATTR_HYPHENATE ).GetValue();
+ eDirection = rSrcSet.Get( ATTR_WRITINGDIR ).GetValue();
+ }
+
+ // Expect to be compatible to LogicToLogic, ie. 2540/1440 = 127/72, and round
+
+ tools::Long nHeight = convertTwipToMm100(nTHeight);
+ tools::Long nCjkHeight = convertTwipToMm100(nCjkTHeight);
+ tools::Long nCtlHeight = convertTwipToMm100(nCtlTHeight);
+
+ // put items into EditEngine ItemSet
+
+ if ( aColorItem->GetValue() == COL_AUTO )
+ {
+ // When cell attributes are converted to EditEngine paragraph attributes,
+ // don't create a hard item for automatic color, because that would be converted
+ // to black when the item's Store method is used in CreateTransferable/WriteBin.
+ // COL_AUTO is the EditEngine's pool default, so ClearItem will result in automatic
+ // color, too, without having to store the item.
+ rEditSet.ClearItem( EE_CHAR_COLOR );
+ }
+ else
+ {
+ // tdf#125054 adapt WhichID
+ rEditSet.Put( std::move(aColorItem), EE_CHAR_COLOR );
+ }
+
+ // tdf#125054 adapt WhichID
+ rEditSet.Put( std::move(aFontItem), EE_CHAR_FONTINFO );
+ rEditSet.Put( std::move(aCjkFontItem), EE_CHAR_FONTINFO_CJK );
+ rEditSet.Put( std::move(aCtlFontItem), EE_CHAR_FONTINFO_CTL );
+
+ rEditSet.Put( SvxFontHeightItem( nHeight, 100, EE_CHAR_FONTHEIGHT ) );
+ rEditSet.Put( SvxFontHeightItem( nCjkHeight, 100, EE_CHAR_FONTHEIGHT_CJK ) );
+ rEditSet.Put( SvxFontHeightItem( nCtlHeight, 100, EE_CHAR_FONTHEIGHT_CTL ) );
+ rEditSet.Put( SvxWeightItem ( eWeight, EE_CHAR_WEIGHT ) );
+ rEditSet.Put( SvxWeightItem ( eCjkWeight, EE_CHAR_WEIGHT_CJK ) );
+ rEditSet.Put( SvxWeightItem ( eCtlWeight, EE_CHAR_WEIGHT_CTL ) );
+
+ // tdf#125054 adapt WhichID
+ rEditSet.Put( std::move(aUnderlineItem), EE_CHAR_UNDERLINE );
+ rEditSet.Put( std::move(aOverlineItem), EE_CHAR_OVERLINE );
+
+ rEditSet.Put( SvxWordLineModeItem( bWordLine, EE_CHAR_WLM ) );
+ rEditSet.Put( SvxCrossedOutItem( eStrike, EE_CHAR_STRIKEOUT ) );
+ rEditSet.Put( SvxPostureItem ( eItalic, EE_CHAR_ITALIC ) );
+ rEditSet.Put( SvxPostureItem ( eCjkItalic, EE_CHAR_ITALIC_CJK ) );
+ rEditSet.Put( SvxPostureItem ( eCtlItalic, EE_CHAR_ITALIC_CTL ) );
+ rEditSet.Put( SvxContourItem ( bOutline, EE_CHAR_OUTLINE ) );
+ rEditSet.Put( SvxShadowedItem ( bShadow, EE_CHAR_SHADOW ) );
+ rEditSet.Put( SvxForbiddenRuleItem(bForbidden, EE_PARA_FORBIDDENRULES) );
+ rEditSet.Put( SvxEmphasisMarkItem( eEmphasis, EE_CHAR_EMPHASISMARK ) );
+ rEditSet.Put( SvxCharReliefItem( eRelief, EE_CHAR_RELIEF ) );
+ rEditSet.Put( SvxLanguageItem ( eLang, EE_CHAR_LANGUAGE ) );
+ rEditSet.Put( SvxLanguageItem ( eCjkLang, EE_CHAR_LANGUAGE_CJK ) );
+ rEditSet.Put( SvxLanguageItem ( eCtlLang, EE_CHAR_LANGUAGE_CTL ) );
+ rEditSet.Put( SfxBoolItem ( EE_PARA_HYPHENATE, bHyphenate ) );
+ rEditSet.Put( SvxFrameDirectionItem( eDirection, EE_PARA_WRITINGDIR ) );
+
+ // Script spacing is always off.
+ // The cell attribute isn't used here as long as there is no UI to set it
+ // (don't evaluate attributes that can't be changed).
+ // If a locale-dependent default is needed, it has to go into the cell
+ // style, like the fonts.
+ rEditSet.Put( SvxScriptSpaceItem( false, EE_PARA_ASIANCJKSPACING ) );
+}
+
+void ScPatternAttr::FillEditItemSet( SfxItemSet* pEditSet, const SfxItemSet* pCondSet ) const
+{
+ if( pEditSet )
+ FillToEditItemSet( *pEditSet, GetItemSet(), pCondSet );
+}
+
+void ScPatternAttr::GetFromEditItemSet( SfxItemSet& rDestSet, const SfxItemSet& rEditSet )
+{
+ if (const SvxColorItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_COLOR))
+ rDestSet.Put( *pItem, ATTR_FONT_COLOR );
+
+ if (const SvxFontItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_FONTINFO))
+ rDestSet.Put( *pItem, ATTR_FONT );
+ if (const SvxFontItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_FONTINFO_CJK))
+ rDestSet.Put( *pItem, ATTR_CJK_FONT );
+ if (const SvxFontItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_FONTINFO_CTL))
+ rDestSet.Put( *pItem, ATTR_CTL_FONT );
+
+ if (const SvxFontHeightItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_FONTHEIGHT))
+ rDestSet.Put( SvxFontHeightItem(o3tl::toTwips(pItem->GetHeight(), o3tl::Length::mm100),
+ 100, ATTR_FONT_HEIGHT ) );
+ if (const SvxFontHeightItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_FONTHEIGHT_CJK))
+ rDestSet.Put( SvxFontHeightItem(o3tl::toTwips(pItem->GetHeight(), o3tl::Length::mm100),
+ 100, ATTR_CJK_FONT_HEIGHT ) );
+ if (const SvxFontHeightItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_FONTHEIGHT_CTL))
+ rDestSet.Put( SvxFontHeightItem(o3tl::toTwips(pItem->GetHeight(), o3tl::Length::mm100),
+ 100, ATTR_CTL_FONT_HEIGHT ) );
+
+ if (const SvxWeightItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_WEIGHT))
+ rDestSet.Put( SvxWeightItem( pItem->GetValue(),
+ ATTR_FONT_WEIGHT) );
+ if (const SvxWeightItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_WEIGHT_CJK))
+ rDestSet.Put( SvxWeightItem( pItem->GetValue(),
+ ATTR_CJK_FONT_WEIGHT) );
+ if (const SvxWeightItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_WEIGHT_CTL))
+ rDestSet.Put( SvxWeightItem( pItem->GetValue(),
+ ATTR_CTL_FONT_WEIGHT) );
+
+ // SvxTextLineItem contains enum and color
+ if (const SvxUnderlineItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_UNDERLINE))
+ rDestSet.Put( *pItem, ATTR_FONT_UNDERLINE );
+ if (const SvxOverlineItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_OVERLINE))
+ rDestSet.Put( *pItem, ATTR_FONT_OVERLINE );
+ if (const SvxWordLineModeItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_WLM))
+ rDestSet.Put( SvxWordLineModeItem( pItem->GetValue(),
+ ATTR_FONT_WORDLINE) );
+
+ if (const SvxCrossedOutItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_STRIKEOUT))
+ rDestSet.Put( SvxCrossedOutItem( pItem->GetValue(),
+ ATTR_FONT_CROSSEDOUT) );
+
+ if (const SvxPostureItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_ITALIC))
+ rDestSet.Put( SvxPostureItem( pItem->GetValue(),
+ ATTR_FONT_POSTURE) );
+ if (const SvxPostureItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_ITALIC_CJK))
+ rDestSet.Put( SvxPostureItem( pItem->GetValue(),
+ ATTR_CJK_FONT_POSTURE) );
+ if (const SvxPostureItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_ITALIC_CTL))
+ rDestSet.Put( SvxPostureItem( pItem->GetValue(),
+ ATTR_CTL_FONT_POSTURE) );
+
+ if (const SvxContourItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_OUTLINE))
+ rDestSet.Put( SvxContourItem( pItem->GetValue(),
+ ATTR_FONT_CONTOUR) );
+ if (const SvxShadowedItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_SHADOW))
+ rDestSet.Put( SvxShadowedItem( pItem->GetValue(),
+ ATTR_FONT_SHADOWED) );
+ if (const SvxEmphasisMarkItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_EMPHASISMARK))
+ rDestSet.Put( SvxEmphasisMarkItem( pItem->GetEmphasisMark(),
+ ATTR_FONT_EMPHASISMARK) );
+ if (const SvxCharReliefItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_RELIEF))
+ rDestSet.Put( SvxCharReliefItem( pItem->GetValue(),
+ ATTR_FONT_RELIEF) );
+
+ if (const SvxLanguageItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_LANGUAGE))
+ rDestSet.Put( SvxLanguageItem(pItem->GetValue(), ATTR_FONT_LANGUAGE) );
+ if (const SvxLanguageItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_LANGUAGE_CJK))
+ rDestSet.Put( SvxLanguageItem(pItem->GetValue(), ATTR_CJK_FONT_LANGUAGE) );
+ if (const SvxLanguageItem* pItem = rEditSet.GetItemIfSet(EE_CHAR_LANGUAGE_CTL))
+ rDestSet.Put( SvxLanguageItem(pItem->GetValue(), ATTR_CTL_FONT_LANGUAGE) );
+
+ const SvxAdjustItem* pAdjustItem = rEditSet.GetItemIfSet(EE_PARA_JUST);
+
+ if (!pAdjustItem)
+ return;
+
+ SvxCellHorJustify eVal;
+ switch ( pAdjustItem->GetAdjust() )
+ {
+ case SvxAdjust::Left:
+ // EditEngine Default is always set in the GetAttribs() ItemSet !
+ // whether left or right, is decided in text / number
+ eVal = SvxCellHorJustify::Standard;
+ break;
+ case SvxAdjust::Right:
+ eVal = SvxCellHorJustify::Right;
+ break;
+ case SvxAdjust::Block:
+ eVal = SvxCellHorJustify::Block;
+ break;
+ case SvxAdjust::Center:
+ eVal = SvxCellHorJustify::Center;
+ break;
+ case SvxAdjust::BlockLine:
+ eVal = SvxCellHorJustify::Block;
+ break;
+ case SvxAdjust::End:
+ eVal = SvxCellHorJustify::Right;
+ break;
+ default:
+ eVal = SvxCellHorJustify::Standard;
+ }
+ if ( eVal != SvxCellHorJustify::Standard )
+ rDestSet.Put( SvxHorJustifyItem( eVal, ATTR_HOR_JUSTIFY) );
+}
+
+void ScPatternAttr::GetFromEditItemSet( const SfxItemSet* pEditSet )
+{
+ if( !pEditSet )
+ return;
+ GetFromEditItemSet( GetItemSet(), *pEditSet );
+ mxHashCode.reset();
+}
+
+void ScPatternAttr::FillEditParaItems( SfxItemSet* pEditSet ) const
+{
+ // already there in GetFromEditItemSet, but not in FillEditItemSet
+ // Default horizontal alignment is always implemented as left
+
+ const SfxItemSet& rMySet = GetItemSet();
+
+ SvxCellHorJustify eHorJust = rMySet.Get(ATTR_HOR_JUSTIFY).GetValue();
+
+ SvxAdjust eSvxAdjust;
+ switch (eHorJust)
+ {
+ case SvxCellHorJustify::Right: eSvxAdjust = SvxAdjust::Right; break;
+ case SvxCellHorJustify::Center: eSvxAdjust = SvxAdjust::Center; break;
+ case SvxCellHorJustify::Block: eSvxAdjust = SvxAdjust::Block; break;
+ default: eSvxAdjust = SvxAdjust::Left; break;
+ }
+ pEditSet->Put( SvxAdjustItem( eSvxAdjust, EE_PARA_JUST ) );
+}
+
+void ScPatternAttr::DeleteUnchanged( const ScPatternAttr* pOldAttrs )
+{
+ SfxItemSet& rThisSet = GetItemSet();
+ const SfxItemSet& rOldSet = pOldAttrs->GetItemSet();
+
+ const SfxPoolItem* pThisItem;
+ const SfxPoolItem* pOldItem;
+
+ for ( sal_uInt16 nSubWhich=ATTR_PATTERN_START; nSubWhich<=ATTR_PATTERN_END; nSubWhich++ )
+ {
+ // only items that are set are interesting
+ if ( rThisSet.GetItemState( nSubWhich, false, &pThisItem ) == SfxItemState::SET )
+ {
+ SfxItemState eOldState = rOldSet.GetItemState( nSubWhich, true, &pOldItem );
+ if ( eOldState == SfxItemState::SET )
+ {
+ // item is set in OldAttrs (or its parent) -> compare pointers
+ if ( pThisItem == pOldItem )
+ {
+ rThisSet.ClearItem( nSubWhich );
+ mxHashCode.reset();
+ }
+ }
+ else if ( eOldState != SfxItemState::DONTCARE )
+ {
+ // not set in OldAttrs -> compare item value to default item
+ if ( *pThisItem == rThisSet.GetPool()->GetDefaultItem( nSubWhich ) )
+ {
+ rThisSet.ClearItem( nSubWhich );
+ mxHashCode.reset();
+ }
+ }
+ }
+ }
+}
+
+bool ScPatternAttr::HasItemsSet( const sal_uInt16* pWhich ) const
+{
+ const SfxItemSet& rSet = GetItemSet();
+ for (sal_uInt16 i=0; pWhich[i]; i++)
+ if ( rSet.GetItemState( pWhich[i], false ) == SfxItemState::SET )
+ return true;
+ return false;
+}
+
+void ScPatternAttr::ClearItems( const sal_uInt16* pWhich )
+{
+ SfxItemSet& rSet = GetItemSet();
+ for (sal_uInt16 i=0; pWhich[i]; i++)
+ rSet.ClearItem(pWhich[i]);
+ mxHashCode.reset();
+}
+
+static SfxStyleSheetBase* lcl_CopyStyleToPool
+ (
+ SfxStyleSheetBase* pSrcStyle,
+ SfxStyleSheetBasePool* pSrcPool,
+ SfxStyleSheetBasePool* pDestPool,
+ const SvNumberFormatterIndexTable* pFormatExchangeList
+ )
+{
+ if ( !pSrcStyle || !pDestPool || !pSrcPool )
+ {
+ OSL_FAIL( "CopyStyleToPool: Invalid Arguments :-/" );
+ return nullptr;
+ }
+
+ const OUString aStrSrcStyle = pSrcStyle->GetName();
+ const SfxStyleFamily eFamily = pSrcStyle->GetFamily();
+ SfxStyleSheetBase* pDestStyle = pDestPool->Find( aStrSrcStyle, eFamily );
+
+ if ( !pDestStyle )
+ {
+ const OUString aStrParent = pSrcStyle->GetParent();
+ const SfxItemSet& rSrcSet = pSrcStyle->GetItemSet();
+
+ pDestStyle = &pDestPool->Make( aStrSrcStyle, eFamily, SfxStyleSearchBits::UserDefined );
+ SfxItemSet& rDestSet = pDestStyle->GetItemSet();
+ rDestSet.Put( rSrcSet );
+
+ // number format exchange list has to be handled here, too
+ // (only called for cell styles)
+
+ const SfxUInt32Item* pSrcItem;
+ if ( pFormatExchangeList &&
+ (pSrcItem = rSrcSet.GetItemIfSet( ATTR_VALUE_FORMAT, false )) )
+ {
+ sal_uLong nOldFormat = pSrcItem->GetValue();
+ SvNumberFormatterIndexTable::const_iterator it = pFormatExchangeList->find(nOldFormat);
+ if (it != pFormatExchangeList->end())
+ {
+ sal_uInt32 nNewFormat = it->second;
+ rDestSet.Put( SfxUInt32Item( ATTR_VALUE_FORMAT, nNewFormat ) );
+ }
+ }
+
+ // if necessary create derivative Styles, if not available:
+
+ if ( ScResId(STR_STYLENAME_STANDARD) != aStrParent &&
+ aStrSrcStyle != aStrParent &&
+ !pDestPool->Find( aStrParent, eFamily ) )
+ {
+ lcl_CopyStyleToPool( pSrcPool->Find( aStrParent, eFamily ),
+ pSrcPool, pDestPool, pFormatExchangeList );
+ }
+
+ pDestStyle->SetParent( aStrParent );
+ }
+
+ return pDestStyle;
+}
+
+ScPatternAttr* ScPatternAttr::PutInPool( ScDocument* pDestDoc, ScDocument* pSrcDoc ) const
+{
+ const SfxItemSet* pSrcSet = &GetItemSet();
+
+ ScPatternAttr aDestPattern( pDestDoc->GetPool() );
+ SfxItemSet* pDestSet = &aDestPattern.GetItemSet();
+
+ // Copy cell pattern style to other document:
+
+ if ( pDestDoc != pSrcDoc )
+ {
+ OSL_ENSURE( pStyle, "Missing Pattern-Style! :-/" );
+
+ // if pattern in DestDoc is available, use this, otherwise copy
+ // parent style to style or create if necessary and attach DestDoc
+
+ SfxStyleSheetBase* pStyleCpy = lcl_CopyStyleToPool( pStyle,
+ pSrcDoc->GetStyleSheetPool(),
+ pDestDoc->GetStyleSheetPool(),
+ pDestDoc->GetFormatExchangeList() );
+
+ aDestPattern.SetStyleSheet( static_cast<ScStyleSheet*>(pStyleCpy) );
+ }
+
+ for ( sal_uInt16 nAttrId = ATTR_PATTERN_START; nAttrId <= ATTR_PATTERN_END; nAttrId++ )
+ {
+ const SfxPoolItem* pSrcItem;
+ SfxItemState eItemState = pSrcSet->GetItemState( nAttrId, false, &pSrcItem );
+ if (eItemState==SfxItemState::SET)
+ {
+ std::unique_ptr<SfxPoolItem> pNewItem;
+
+ if ( nAttrId == ATTR_VALIDDATA )
+ {
+ // Copy validity to the new document
+
+ sal_uLong nNewIndex = 0;
+ ScValidationDataList* pSrcList = pSrcDoc->GetValidationList();
+ if ( pSrcList )
+ {
+ sal_uLong nOldIndex = static_cast<const SfxUInt32Item*>(pSrcItem)->GetValue();
+ const ScValidationData* pOldData = pSrcList->GetData( nOldIndex );
+ if ( pOldData )
+ nNewIndex = pDestDoc->AddValidationEntry( *pOldData );
+ }
+ pNewItem.reset(new SfxUInt32Item( ATTR_VALIDDATA, nNewIndex ));
+ }
+ else if ( nAttrId == ATTR_VALUE_FORMAT && pDestDoc->GetFormatExchangeList() )
+ {
+ // Number format to Exchange List
+
+ sal_uLong nOldFormat = static_cast<const SfxUInt32Item*>(pSrcItem)->GetValue();
+ SvNumberFormatterIndexTable::const_iterator it = pDestDoc->GetFormatExchangeList()->find(nOldFormat);
+ if (it != pDestDoc->GetFormatExchangeList()->end())
+ {
+ sal_uInt32 nNewFormat = it->second;
+ pNewItem.reset(new SfxUInt32Item( ATTR_VALUE_FORMAT, nNewFormat ));
+ }
+ }
+
+ if ( pNewItem )
+ {
+ pDestSet->Put(std::move(pNewItem));
+ }
+ else
+ pDestSet->Put(*pSrcItem);
+ }
+ }
+
+ ScPatternAttr* pPatternAttr = const_cast<ScPatternAttr*>( &pDestDoc->GetPool()->Put(aDestPattern) );
+ return pPatternAttr;
+}
+
+bool ScPatternAttr::IsVisible() const
+{
+ const SfxItemSet& rSet = GetItemSet();
+
+ if ( const SvxBrushItem* pItem = rSet.GetItemIfSet( ATTR_BACKGROUND ) )
+ if ( pItem->GetColor() != COL_TRANSPARENT )
+ return true;
+
+ if ( const SvxBoxItem* pBoxItem = rSet.GetItemIfSet( ATTR_BORDER ) )
+ {
+ if ( pBoxItem->GetTop() || pBoxItem->GetBottom() ||
+ pBoxItem->GetLeft() || pBoxItem->GetRight() )
+ return true;
+ }
+
+ if ( const SvxLineItem* pItem = rSet.GetItemIfSet( ATTR_BORDER_TLBR ) )
+ if( pItem->GetLine() )
+ return true;
+
+ if ( const SvxLineItem* pItem = rSet.GetItemIfSet( ATTR_BORDER_BLTR ) )
+ if( pItem->GetLine() )
+ return true;
+
+ if ( const SvxShadowItem* pItem = rSet.GetItemIfSet( ATTR_SHADOW ) )
+ if ( pItem->GetLocation() != SvxShadowLocation::NONE )
+ return true;
+
+ return false;
+}
+
+static bool OneEqual( const SfxItemSet& rSet1, const SfxItemSet& rSet2, sal_uInt16 nId )
+{
+ const SfxPoolItem* pItem1 = &rSet1.Get(nId);
+ const SfxPoolItem* pItem2 = &rSet2.Get(nId);
+ return ( pItem1 == pItem2 || *pItem1 == *pItem2 );
+}
+
+bool ScPatternAttr::IsVisibleEqual( const ScPatternAttr& rOther ) const
+{
+ const SfxItemSet& rThisSet = GetItemSet();
+ const SfxItemSet& rOtherSet = rOther.GetItemSet();
+
+ return OneEqual( rThisSet, rOtherSet, ATTR_BACKGROUND ) &&
+ OneEqual( rThisSet, rOtherSet, ATTR_BORDER ) &&
+ OneEqual( rThisSet, rOtherSet, ATTR_BORDER_TLBR ) &&
+ OneEqual( rThisSet, rOtherSet, ATTR_BORDER_BLTR ) &&
+ OneEqual( rThisSet, rOtherSet, ATTR_SHADOW );
+
+ //TODO: also here only check really visible values !!!
+}
+
+const OUString* ScPatternAttr::GetStyleName() const
+{
+ return pName ? &*pName : ( pStyle ? &pStyle->GetName() : nullptr );
+}
+
+void ScPatternAttr::SetStyleSheet( ScStyleSheet* pNewStyle, bool bClearDirectFormat )
+{
+ if (pNewStyle)
+ {
+ SfxItemSet& rPatternSet = GetItemSet();
+ const SfxItemSet& rStyleSet = pNewStyle->GetItemSet();
+
+ if (bClearDirectFormat)
+ {
+ for (sal_uInt16 i=ATTR_PATTERN_START; i<=ATTR_PATTERN_END; i++)
+ {
+ if (rStyleSet.GetItemState(i) == SfxItemState::SET)
+ rPatternSet.ClearItem(i);
+ }
+ }
+ rPatternSet.SetParent(&pNewStyle->GetItemSet());
+ pStyle = pNewStyle;
+ pName.reset();
+ }
+ else
+ {
+ OSL_FAIL( "ScPatternAttr::SetStyleSheet( NULL ) :-|" );
+ GetItemSet().SetParent(nullptr);
+ pStyle = nullptr;
+ }
+ mxHashCode.reset();
+}
+
+void ScPatternAttr::UpdateStyleSheet(const ScDocument& rDoc)
+{
+ if (pName)
+ {
+ pStyle = static_cast<ScStyleSheet*>(rDoc.GetStyleSheetPool()->Find(*pName, SfxStyleFamily::Para));
+
+ // use Standard if Style is not found,
+ // to avoid empty display in Toolbox-Controller
+ // Assumes that "Standard" is always the 1st entry!
+ if (!pStyle)
+ {
+ std::unique_ptr<SfxStyleSheetIterator> pIter = rDoc.GetStyleSheetPool()->CreateIterator(SfxStyleFamily::Para);
+ pStyle = dynamic_cast< ScStyleSheet* >(pIter->First());
+ }
+
+ if (pStyle)
+ {
+ GetItemSet().SetParent(&pStyle->GetItemSet());
+ pName.reset();
+ }
+ }
+ else
+ pStyle = nullptr;
+ mxHashCode.reset();
+}
+
+void ScPatternAttr::StyleToName()
+{
+ // Style was deleted, remember name:
+
+ if ( pStyle )
+ {
+ pName = pStyle->GetName();
+ pStyle = nullptr;
+ GetItemSet().SetParent( nullptr );
+ mxHashCode.reset();
+ }
+}
+
+bool ScPatternAttr::IsSymbolFont() const
+{
+ if( const SvxFontItem* pItem = GetItemSet().GetItemIfSet( ATTR_FONT ) )
+ return pItem->GetCharSet() == RTL_TEXTENCODING_SYMBOL;
+ else
+ return false;
+}
+
+namespace {
+
+sal_uInt32 getNumberFormatKey(const SfxItemSet& rSet)
+{
+ return rSet.Get(ATTR_VALUE_FORMAT).GetValue();
+}
+
+LanguageType getLanguageType(const SfxItemSet& rSet)
+{
+ return rSet.Get(ATTR_LANGUAGE_FORMAT).GetLanguage();
+}
+
+}
+
+sal_uInt32 ScPatternAttr::GetNumberFormat( SvNumberFormatter* pFormatter ) const
+{
+ sal_uInt32 nFormat = getNumberFormatKey(GetItemSet());
+ LanguageType eLang = getLanguageType(GetItemSet());
+ if ( nFormat < SV_COUNTRY_LANGUAGE_OFFSET && eLang == LANGUAGE_SYSTEM )
+ ; // it remains as it is
+ else if ( pFormatter )
+ nFormat = pFormatter->GetFormatForLanguageIfBuiltIn( nFormat, eLang );
+ return nFormat;
+}
+
+// the same if conditional formatting is in play:
+
+sal_uInt32 ScPatternAttr::GetNumberFormat( SvNumberFormatter* pFormatter,
+ const SfxItemSet* pCondSet ) const
+{
+ assert(pFormatter);
+ if (!pCondSet)
+ return GetNumberFormat(pFormatter);
+
+ // Conditional format takes precedence over style and even hard format.
+
+ sal_uInt32 nFormat;
+ LanguageType eLang;
+ if (pCondSet->GetItemState(ATTR_VALUE_FORMAT) == SfxItemState::SET )
+ {
+ nFormat = getNumberFormatKey(*pCondSet);
+ if (pCondSet->GetItemState(ATTR_LANGUAGE_FORMAT) == SfxItemState::SET)
+ eLang = getLanguageType(*pCondSet);
+ else
+ eLang = getLanguageType(GetItemSet());
+ }
+ else
+ {
+ nFormat = getNumberFormatKey(GetItemSet());
+ eLang = getLanguageType(GetItemSet());
+ }
+
+ return pFormatter->GetFormatForLanguageIfBuiltIn(nFormat, eLang);
+}
+
+const SfxPoolItem& ScPatternAttr::GetItem( sal_uInt16 nWhich, const SfxItemSet& rItemSet, const SfxItemSet* pCondSet )
+{
+ const SfxPoolItem* pCondItem;
+ if ( pCondSet && pCondSet->GetItemState( nWhich, true, &pCondItem ) == SfxItemState::SET )
+ return *pCondItem;
+ return rItemSet.Get(nWhich);
+}
+
+const SfxPoolItem& ScPatternAttr::GetItem( sal_uInt16 nSubWhich, const SfxItemSet* pCondSet ) const
+{
+ return GetItem( nSubWhich, GetItemSet(), pCondSet );
+}
+
+// GetRotateVal is tested before ATTR_ORIENTATION
+
+Degree100 ScPatternAttr::GetRotateVal( const SfxItemSet* pCondSet ) const
+{
+ Degree100 nAttrRotate(0);
+ if ( GetCellOrientation() == SvxCellOrientation::Standard )
+ {
+ bool bRepeat = ( GetItem(ATTR_HOR_JUSTIFY, pCondSet).
+ GetValue() == SvxCellHorJustify::Repeat );
+ // ignore orientation/rotation if "repeat" is active
+ if ( !bRepeat )
+ nAttrRotate = GetItem( ATTR_ROTATE_VALUE, pCondSet ).GetValue();
+ }
+ return nAttrRotate;
+}
+
+ScRotateDir ScPatternAttr::GetRotateDir( const SfxItemSet* pCondSet ) const
+{
+ ScRotateDir nRet = ScRotateDir::NONE;
+
+ Degree100 nAttrRotate = GetRotateVal( pCondSet );
+ if ( nAttrRotate )
+ {
+ SvxRotateMode eRotMode = GetItem(ATTR_ROTATE_MODE, pCondSet).GetValue();
+
+ if ( eRotMode == SVX_ROTATE_MODE_STANDARD || nAttrRotate == 18000_deg100 )
+ nRet = ScRotateDir::Standard;
+ else if ( eRotMode == SVX_ROTATE_MODE_CENTER )
+ nRet = ScRotateDir::Center;
+ else if ( eRotMode == SVX_ROTATE_MODE_TOP || eRotMode == SVX_ROTATE_MODE_BOTTOM )
+ {
+ Degree100 nRot180 = nAttrRotate % 18000_deg100; // 1/100 degrees
+ if ( nRot180 == 9000_deg100 )
+ nRet = ScRotateDir::Center;
+ else if ( ( eRotMode == SVX_ROTATE_MODE_TOP && nRot180 < 9000_deg100 ) ||
+ ( eRotMode == SVX_ROTATE_MODE_BOTTOM && nRot180 > 9000_deg100 ) )
+ nRet = ScRotateDir::Left;
+ else
+ nRet = ScRotateDir::Right;
+ }
+ }
+
+ return nRet;
+}
+
+void ScPatternAttr::SetKey(sal_uInt64 nKey)
+{
+ mnKey = nKey;
+}
+
+sal_uInt64 ScPatternAttr::GetKey() const
+{
+ return mnKey;
+}
+
+void ScPatternAttr::CalcHashCode() const
+{
+ auto const & rSet = GetItemSet();
+ if( rSet.TotalCount() != compareSize ) // see EqualPatternSets()
+ {
+ mxHashCode = 0; // invalid
+ return;
+ }
+ mxHashCode = 1; // Set up seed so that an empty pattern does not have an (invalid) hash of 0.
+ boost::hash_range(*mxHashCode, rSet.GetItems_Impl(), rSet.GetItems_Impl() + compareSize);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/pivot2.cxx b/sc/source/core/data/pivot2.cxx
new file mode 100644
index 000000000..7544e6e95
--- /dev/null
+++ b/sc/source/core/data/pivot2.cxx
@@ -0,0 +1,161 @@
+/* -*- 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 <pivot.hxx>
+
+#if DEBUG_PIVOT_TABLE
+using std::cout;
+using std::endl;
+#endif
+
+
+
+// ScDPName
+
+ScDPName::ScDPName() : mnDupCount(0)
+{}
+
+ScDPName::ScDPName(const OUString& rName, const OUString& rLayoutName, sal_uInt8 nDupCount) :
+ maName(rName), maLayoutName(rLayoutName), mnDupCount(nDupCount)
+{}
+
+// ScDPLabelData
+
+ScDPLabelData::Member::Member() :
+ mbVisible(true),
+ mbShowDetails(true)
+{}
+
+OUString const & ScDPLabelData::Member::getDisplayName() const
+{
+ if (!maLayoutName.isEmpty())
+ return maLayoutName;
+
+ return maName;
+}
+
+ScDPLabelData::ScDPLabelData() :
+ mnCol(-1),
+ mnOriginalDim(-1),
+ mnFuncMask(PivotFunc::NONE),
+ mnUsedHier(0),
+ mnFlags(0),
+ mnDupCount(0),
+ mbShowAll(false),
+ mbIsValue(false),
+ mbDataLayout(false),
+ mbRepeatItemLabels(false)
+{}
+
+OUString const & ScDPLabelData::getDisplayName() const
+{
+ if (!maLayoutName.isEmpty())
+ return maLayoutName;
+
+ return maName;
+}
+
+// ScPivotField
+
+ScPivotField::ScPivotField(SCCOL nNewCol) :
+ mnOriginalDim(-1),
+ nFuncMask(PivotFunc::NONE),
+ nCol(nNewCol),
+ mnDupCount(0)
+{}
+
+tools::Long ScPivotField::getOriginalDim() const
+{
+ return mnOriginalDim >= 0 ? mnOriginalDim : static_cast<tools::Long>(nCol);
+}
+
+// ScPivotParam
+
+ScPivotParam::ScPivotParam() :
+ nCol(0), nRow(0), nTab(0),
+ bIgnoreEmptyRows(false), bDetectCategories(false),
+ bMakeTotalCol(true), bMakeTotalRow(true)
+{}
+
+ScPivotParam::ScPivotParam( const ScPivotParam& r )
+ : nCol( r.nCol ), nRow( r.nRow ), nTab( r.nTab ),
+ maPageFields(r.maPageFields),
+ maColFields(r.maColFields),
+ maRowFields(r.maRowFields),
+ maDataFields(r.maDataFields),
+ bIgnoreEmptyRows(r.bIgnoreEmptyRows),
+ bDetectCategories(r.bDetectCategories),
+ bMakeTotalCol(r.bMakeTotalCol),
+ bMakeTotalRow(r.bMakeTotalRow)
+{
+ SetLabelData(r.maLabelArray);
+}
+
+ScPivotParam::~ScPivotParam()
+{}
+
+void ScPivotParam::SetLabelData(const ScDPLabelDataVector& rVector)
+{
+ ScDPLabelDataVector aNewArray;
+ aNewArray.reserve(rVector.size());
+ for (const auto& rxData : rVector)
+ {
+ aNewArray.push_back(std::make_unique<ScDPLabelData>(*rxData));
+ }
+ maLabelArray.swap(aNewArray);
+}
+
+ScPivotParam& ScPivotParam::operator=( const ScPivotParam& rPivotParam )
+{
+ nCol = rPivotParam.nCol;
+ nRow = rPivotParam.nRow;
+ nTab = rPivotParam.nTab;
+ bIgnoreEmptyRows = rPivotParam.bIgnoreEmptyRows;
+ bDetectCategories = rPivotParam.bDetectCategories;
+ bMakeTotalCol = rPivotParam.bMakeTotalCol;
+ bMakeTotalRow = rPivotParam.bMakeTotalRow;
+
+ maPageFields = rPivotParam.maPageFields;
+ maColFields = rPivotParam.maColFields;
+ maRowFields = rPivotParam.maRowFields;
+ maDataFields = rPivotParam.maDataFields;
+
+ SetLabelData(rPivotParam.maLabelArray);
+ return *this;
+}
+
+// ScPivotFuncData
+
+ScPivotFuncData::ScPivotFuncData( SCCOL nCol, PivotFunc nFuncMask ) :
+ mnOriginalDim(-1),
+ mnFuncMask(nFuncMask),
+ mnCol( nCol ),
+ mnDupCount(0)
+{}
+
+#if DEBUG_PIVOT_TABLE
+void ScPivotFuncData::Dump() const
+{
+ cout << "ScPivotFuncData: (col=" << mnCol << ", original dim=" << mnOriginalDim
+ << ", func mask=" << static_cast<int>(mnFuncMask) << ", duplicate count=" << static_cast<int>(mnDupCount)
+ << ")" << endl;
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/poolhelp.cxx b/sc/source/core/data/poolhelp.cxx
new file mode 100644
index 000000000..6ed855fc7
--- /dev/null
+++ b/sc/source/core/data/poolhelp.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 <comphelper/processfactory.hxx>
+#include <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+#include <editeng/editeng.hxx>
+
+#include <poolhelp.hxx>
+#include <document.hxx>
+#include <docpool.hxx>
+#include <stlpool.hxx>
+
+ScPoolHelper::ScPoolHelper( ScDocument& rSourceDoc )
+ : pDocPool(new ScDocumentPool)
+ , m_rSourceDoc(rSourceDoc)
+{
+ pDocPool->FreezeIdRanges();
+
+ mxStylePool = new ScStyleSheetPool( *pDocPool, &rSourceDoc );
+}
+
+ScPoolHelper::~ScPoolHelper()
+{
+ pEnginePool.clear();
+ pEditPool.clear();
+ pFormTable.reset();
+ mxStylePool.clear();
+ pDocPool.clear();
+}
+
+SfxItemPool* ScPoolHelper::GetEditPool() const
+{
+ if ( !pEditPool )
+ {
+ pEditPool = EditEngine::CreatePool();
+ pEditPool->SetDefaultMetric( MapUnit::Map100thMM );
+ pEditPool->FreezeIdRanges();
+ }
+ return pEditPool.get();
+}
+
+SfxItemPool* ScPoolHelper::GetEnginePool() const
+{
+ if ( !pEnginePool )
+ {
+ pEnginePool = EditEngine::CreatePool();
+ pEnginePool->SetDefaultMetric( MapUnit::Map100thMM );
+ pEnginePool->FreezeIdRanges();
+ } // ifg ( pEnginePool )
+ return pEnginePool.get();
+}
+SvNumberFormatter* ScPoolHelper::GetFormTable() const
+{
+ if (!pFormTable)
+ pFormTable = CreateNumberFormatter();
+ return pFormTable.get();
+}
+
+void ScPoolHelper::SetFormTableOpt(const ScDocOptions& rOpt)
+{
+ aOpt = rOpt;
+ // #i105512# if the number formatter exists, update its settings
+ if (pFormTable)
+ {
+ sal_uInt16 d,m;
+ sal_Int16 y;
+ aOpt.GetDate( d,m,y );
+ pFormTable->ChangeNullDate( d,m,y );
+ pFormTable->ChangeStandardPrec( aOpt.GetStdPrecision() );
+ pFormTable->SetYear2000( aOpt.GetYear2000() );
+ }
+}
+
+std::unique_ptr<SvNumberFormatter> ScPoolHelper::CreateNumberFormatter() const
+{
+ std::unique_ptr<SvNumberFormatter> p;
+ {
+ std::scoped_lock aGuard(maMtxCreateNumFormatter);
+ p.reset(new SvNumberFormatter(comphelper::getProcessComponentContext(), LANGUAGE_SYSTEM));
+ }
+ p->SetColorLink( LINK(&m_rSourceDoc, ScDocument, GetUserDefinedColor) );
+ p->SetEvalDateFormat(NF_EVALDATEFORMAT_INTL_FORMAT);
+
+ sal_uInt16 d,m;
+ sal_Int16 y;
+ aOpt.GetDate(d, m, y);
+ p->ChangeNullDate(d, m, y);
+ p->ChangeStandardPrec(aOpt.GetStdPrecision());
+ p->SetYear2000(aOpt.GetYear2000());
+ return p;
+}
+
+void ScPoolHelper::SourceDocumentGone()
+{
+ // reset all pointers to the source document
+ mxStylePool->SetDocument( nullptr );
+ if ( pFormTable )
+ pFormTable->SetColorLink( Link<sal_uInt16,Color*>() );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/postit.cxx b/sc/source/core/data/postit.cxx
new file mode 100644
index 000000000..cd8acfdce
--- /dev/null
+++ b/sc/source/core/data/postit.cxx
@@ -0,0 +1,1306 @@
+/* -*- 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 <postit.hxx>
+
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <unotools/useroptions.hxx>
+#include <svx/svdpage.hxx>
+#include <svx/svdocapt.hxx>
+#include <editeng/outlobj.hxx>
+#include <editeng/editobj.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <osl/diagnose.h>
+#include <comphelper/lok.hxx>
+
+#include <scitems.hxx>
+#include <svx/xfillit0.hxx>
+#include <svx/xlnstit.hxx>
+#include <svx/xlnstwit.hxx>
+#include <svx/xlnstcit.hxx>
+#include <svx/sxcecitm.hxx>
+#include <svx/xflclit.hxx>
+#include <svx/sdshitm.hxx>
+#include <svx/sdsxyitm.hxx>
+#include <svx/sdtditm.hxx>
+#include <svx/sdtagitm.hxx>
+#include <svx/sdtmfitm.hxx>
+#include <tools/gen.hxx>
+
+#include <document.hxx>
+#include <docpool.hxx>
+#include <patattr.hxx>
+#include <drwlayer.hxx>
+#include <userdat.hxx>
+#include <detfunc.hxx>
+#include <editutil.hxx>
+
+using namespace com::sun::star;
+
+namespace {
+
+const tools::Long SC_NOTECAPTION_WIDTH = 2900; /// Default width of note caption textbox.
+const tools::Long SC_NOTECAPTION_MAXWIDTH_TEMP = 12000; /// Maximum width of temporary note caption textbox.
+const tools::Long SC_NOTECAPTION_HEIGHT = 1800; /// Default height of note caption textbox.
+const tools::Long SC_NOTECAPTION_CELLDIST = 600; /// Default distance of note captions to border of anchor cell.
+const tools::Long SC_NOTECAPTION_OFFSET_Y = -1500; /// Default Y offset of note captions to top border of anchor cell.
+const tools::Long SC_NOTECAPTION_OFFSET_X = 1500; /// Default X offset of note captions to left border of anchor cell.
+const tools::Long SC_NOTECAPTION_BORDERDIST_TEMP = 100; /// Distance of temporary note captions to visible sheet area.
+
+/** Static helper functions for caption objects. */
+class ScCaptionUtil
+{
+public:
+ /** Moves the caption object to the correct layer according to passed visibility. */
+ static void SetCaptionLayer( SdrCaptionObj& rCaption, bool bShown );
+ /** Sets basic caption settings required for note caption objects. */
+ static void SetBasicCaptionSettings( SdrCaptionObj& rCaption, bool bShown );
+ /** Stores the cell position of the note in the user data area of the caption. */
+ static void SetCaptionUserData( SdrCaptionObj& rCaption, const ScAddress& rPos );
+ /** Sets all default formatting attributes to the caption object. */
+ static void SetDefaultItems( SdrCaptionObj& rCaption, ScDocument& rDoc, const SfxItemSet* pExtraItemSet );
+};
+
+void ScCaptionUtil::SetCaptionLayer( SdrCaptionObj& rCaption, bool bShown )
+{
+ SdrLayerID nLayer = bShown ? SC_LAYER_INTERN : SC_LAYER_HIDDEN;
+ if( nLayer != rCaption.GetLayer() )
+ rCaption.SetLayer( nLayer );
+}
+
+void ScCaptionUtil::SetBasicCaptionSettings( SdrCaptionObj& rCaption, bool bShown )
+{
+ SetCaptionLayer( rCaption, bShown );
+ rCaption.SetFixedTail();
+ rCaption.SetSpecialTextBoxShadow();
+}
+
+void ScCaptionUtil::SetCaptionUserData( SdrCaptionObj& rCaption, const ScAddress& rPos )
+{
+ // pass true to ScDrawLayer::GetObjData() to create the object data entry
+ ScDrawObjData* pObjData = ScDrawLayer::GetObjData( &rCaption, true );
+ OSL_ENSURE( pObjData, "ScCaptionUtil::SetCaptionUserData - missing drawing object user data" );
+ pObjData->maStart = rPos;
+ pObjData->meType = ScDrawObjData::CellNote;
+}
+
+void ScCaptionUtil::SetDefaultItems( SdrCaptionObj& rCaption, ScDocument& rDoc, const SfxItemSet* pExtraItemSet )
+{
+ SfxItemSet aItemSet = rCaption.GetMergedItemSet();
+
+ // caption tail arrow
+ ::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 );
+ /* Line ends are now created with an empty name. The
+ checkForUniqueItem() method then finds a unique name for the item's
+ value. */
+ aItemSet.Put( XLineStartItem( OUString(), ::basegfx::B2DPolyPolygon( aTriangle ) ) );
+ aItemSet.Put( XLineStartWidthItem( 200 ) );
+ aItemSet.Put( XLineStartCenterItem( false ) );
+ aItemSet.Put( XFillStyleItem( drawing::FillStyle_SOLID ) );
+ aItemSet.Put( XFillColorItem( OUString(), ScDetectiveFunc::GetCommentColor() ) );
+ aItemSet.Put( SdrCaptionEscDirItem( SdrCaptionEscDir::BestFit ) );
+
+ // shadow
+ /* SdrShadowItem has sal_False, instead the shadow is set for the
+ rectangle only with SetSpecialTextBoxShadow() when the object is
+ created (item must be set to adjust objects from older files). */
+ aItemSet.Put( makeSdrShadowItem( false ) );
+ aItemSet.Put( makeSdrShadowXDistItem( 100 ) );
+ aItemSet.Put( makeSdrShadowYDistItem( 100 ) );
+
+ // text attributes
+ aItemSet.Put( makeSdrTextLeftDistItem( 100 ) );
+ aItemSet.Put( makeSdrTextRightDistItem( 100 ) );
+ aItemSet.Put( makeSdrTextUpperDistItem( 100 ) );
+ aItemSet.Put( makeSdrTextLowerDistItem( 100 ) );
+ aItemSet.Put( makeSdrTextAutoGrowWidthItem( false ) );
+ aItemSet.Put( makeSdrTextAutoGrowHeightItem( true ) );
+ // use the default cell style to be able to modify the caption font
+ const ScPatternAttr& rDefPattern = rDoc.GetPool()->GetDefaultItem( ATTR_PATTERN );
+ rDefPattern.FillEditItemSet( &aItemSet );
+
+ if (pExtraItemSet)
+ {
+ /* Updates caption item set according to the passed item set while removing shadow items. */
+
+ aItemSet.Put(*pExtraItemSet);
+ // reset shadow items
+ aItemSet.Put( makeSdrShadowItem( false ) );
+ aItemSet.Put( makeSdrShadowXDistItem( 100 ) );
+ aItemSet.Put( makeSdrShadowYDistItem( 100 ) );
+ }
+
+ rCaption.SetMergedItemSet( aItemSet );
+
+ if (pExtraItemSet)
+ rCaption.SetSpecialTextBoxShadow();
+}
+
+/** Helper for creation and manipulation of caption drawing objects independent
+ from cell annotations. */
+class ScCaptionCreator
+{
+public:
+ /** Create a new caption. The caption will not be inserted into the document. */
+ explicit ScCaptionCreator( ScDocument& rDoc, const ScAddress& rPos, bool bTailFront );
+ /** Manipulate an existing caption. */
+ explicit ScCaptionCreator( ScDocument& rDoc, const ScAddress& rPos, const ScCaptionPtr& xCaption );
+
+ /** Returns the drawing layer page of the sheet contained in maPos. */
+ SdrPage* GetDrawPage();
+ /** Returns the caption drawing object. */
+ ScCaptionPtr & GetCaption() { return mxCaption; }
+
+ /** Moves the caption inside the passed rectangle. Uses page area if 0 is passed. */
+ void FitCaptionToRect( const tools::Rectangle* pVisRect = nullptr );
+ /** Places the caption inside the passed rectangle, tries to keep the cell rectangle uncovered. Uses page area if 0 is passed. */
+ void AutoPlaceCaption( const tools::Rectangle* pVisRect = nullptr );
+ /** Updates caption tail and textbox according to current cell position. Uses page area if 0 is passed. */
+ void UpdateCaptionPos();
+
+protected:
+ /** Helper constructor for derived classes. */
+ explicit ScCaptionCreator( ScDocument& rDoc, const ScAddress& rPos );
+
+ /** Calculates the caption tail position according to current cell position. */
+ Point CalcTailPos( bool bTailFront );
+ /** Implements creation of the caption object. The caption will not be inserted into the document. */
+ void CreateCaption( bool bShown, bool bTailFront );
+
+private:
+ /** Initializes all members. */
+ void Initialize();
+ /** Returns the passed rectangle if existing, page rectangle otherwise. */
+ const tools::Rectangle& GetVisRect( const tools::Rectangle* pVisRect ) const { return pVisRect ? *pVisRect : maPageRect; }
+
+private:
+ ScDocument& mrDoc;
+ ScAddress maPos;
+ ScCaptionPtr mxCaption;
+ tools::Rectangle maPageRect;
+ tools::Rectangle maCellRect;
+ bool mbNegPage;
+};
+
+ScCaptionCreator::ScCaptionCreator( ScDocument& rDoc, const ScAddress& rPos, bool bTailFront ) :
+ mrDoc( rDoc ),
+ maPos( rPos )
+{
+ Initialize();
+ CreateCaption( true/*bShown*/, bTailFront );
+}
+
+ScCaptionCreator::ScCaptionCreator( ScDocument& rDoc, const ScAddress& rPos, const ScCaptionPtr& xCaption ) :
+ mrDoc( rDoc ),
+ maPos( rPos ),
+ mxCaption( xCaption )
+{
+ Initialize();
+}
+
+ScCaptionCreator::ScCaptionCreator( ScDocument& rDoc, const ScAddress& rPos ) :
+ mrDoc( rDoc ),
+ maPos( rPos )
+{
+ Initialize();
+}
+
+SdrPage* ScCaptionCreator::GetDrawPage()
+{
+ ScDrawLayer* pDrawLayer = mrDoc.GetDrawLayer();
+ return pDrawLayer ? pDrawLayer->GetPage( static_cast< sal_uInt16 >( maPos.Tab() ) ) : nullptr;
+}
+
+void ScCaptionCreator::FitCaptionToRect( const tools::Rectangle* pVisRect )
+{
+ const tools::Rectangle& rVisRect = GetVisRect( pVisRect );
+
+ // tail position
+ Point aTailPos = mxCaption->GetTailPos();
+ aTailPos.setX( ::std::clamp( aTailPos.X(), rVisRect.Left(), rVisRect.Right() ) );
+ aTailPos.setY( ::std::clamp( aTailPos.Y(), rVisRect.Top(), rVisRect.Bottom() ) );
+ mxCaption->SetTailPos( aTailPos );
+
+ // caption rectangle
+ tools::Rectangle aCaptRect = mxCaption->GetLogicRect();
+ Point aCaptPos = aCaptRect.TopLeft();
+ // move textbox inside right border of visible area
+ aCaptPos.setX( ::std::min< tools::Long >( aCaptPos.X(), rVisRect.Right() - aCaptRect.GetWidth() ) );
+ // move textbox inside left border of visible area (this may move it outside on right side again)
+ aCaptPos.setX( ::std::max< tools::Long >( aCaptPos.X(), rVisRect.Left() ) );
+ // move textbox inside bottom border of visible area
+ aCaptPos.setY( ::std::min< tools::Long >( aCaptPos.Y(), rVisRect.Bottom() - aCaptRect.GetHeight() ) );
+ // move textbox inside top border of visible area (this may move it outside on bottom side again)
+ aCaptPos.setY( ::std::max< tools::Long >( aCaptPos.Y(), rVisRect.Top() ) );
+ // update caption
+ aCaptRect.SetPos( aCaptPos );
+ mxCaption->SetLogicRect( aCaptRect );
+}
+
+void ScCaptionCreator::AutoPlaceCaption( const tools::Rectangle* pVisRect )
+{
+ const tools::Rectangle& rVisRect = GetVisRect( pVisRect );
+
+ // caption rectangle
+ tools::Rectangle aCaptRect = mxCaption->GetLogicRect();
+ tools::Long nWidth = aCaptRect.GetWidth();
+ tools::Long nHeight = aCaptRect.GetHeight();
+
+ // n***Space contains available space between border of visible area and cell
+ tools::Long nLeftSpace = maCellRect.Left() - rVisRect.Left() + 1;
+ tools::Long nRightSpace = rVisRect.Right() - maCellRect.Right() + 1;
+ tools::Long nTopSpace = maCellRect.Top() - rVisRect.Top() + 1;
+ tools::Long nBottomSpace = rVisRect.Bottom() - maCellRect.Bottom() + 1;
+
+ // nNeeded*** contains textbox dimensions plus needed distances to cell or border of visible area
+ tools::Long nNeededSpaceX = nWidth + SC_NOTECAPTION_CELLDIST;
+ tools::Long nNeededSpaceY = nHeight + SC_NOTECAPTION_CELLDIST;
+
+ // bFitsWidth*** == true means width of textbox fits into horizontal free space of visible area
+ bool bFitsWidthLeft = nNeededSpaceX <= nLeftSpace; // text box width fits into the width left of cell
+ bool bFitsWidthRight = nNeededSpaceX <= nRightSpace; // text box width fits into the width right of cell
+ bool bFitsWidth = nWidth <= rVisRect.GetWidth(); // text box width fits into width of visible area
+
+ // bFitsHeight*** == true means height of textbox fits into vertical free space of visible area
+ bool bFitsHeightTop = nNeededSpaceY <= nTopSpace; // text box height fits into the height above cell
+ bool bFitsHeightBottom = nNeededSpaceY <= nBottomSpace; // text box height fits into the height below cell
+ bool bFitsHeight = nHeight <= rVisRect.GetHeight(); // text box height fits into height of visible area
+
+ // bFits*** == true means the textbox fits completely into free space of visible area
+ bool bFitsLeft = bFitsWidthLeft && bFitsHeight;
+ bool bFitsRight = bFitsWidthRight && bFitsHeight;
+ bool bFitsTop = bFitsWidth && bFitsHeightTop;
+ bool bFitsBottom = bFitsWidth && bFitsHeightBottom;
+
+ Point aCaptPos;
+ // use left/right placement if possible, or if top/bottom placement not possible
+ if( bFitsLeft || bFitsRight || (!bFitsTop && !bFitsBottom) )
+ {
+ // prefer left in RTL sheet and right in LTR sheets
+ bool bPreferLeft = bFitsLeft && (mbNegPage || !bFitsRight);
+ bool bPreferRight = bFitsRight && (!mbNegPage || !bFitsLeft);
+ // move to left, if left is preferred, or if neither left nor right fit and there is more space to the left
+ if( bPreferLeft || (!bPreferRight && (nLeftSpace > nRightSpace)) )
+ aCaptPos.setX( maCellRect.Left() - SC_NOTECAPTION_CELLDIST - nWidth );
+ else // to right
+ aCaptPos.setX( maCellRect.Right() + SC_NOTECAPTION_CELLDIST );
+ // Y position according to top cell border
+ aCaptPos.setY( maCellRect.Top() + SC_NOTECAPTION_OFFSET_Y );
+ }
+ else // top or bottom placement
+ {
+ // X position
+ aCaptPos.setX( maCellRect.Left() + SC_NOTECAPTION_OFFSET_X );
+ // top placement, if possible
+ if( bFitsTop )
+ aCaptPos.setY( maCellRect.Top() - SC_NOTECAPTION_CELLDIST - nHeight );
+ else // bottom placement
+ aCaptPos.setY( maCellRect.Bottom() + SC_NOTECAPTION_CELLDIST );
+ }
+
+ // update textbox position in note caption object
+ aCaptRect.SetPos( aCaptPos );
+ mxCaption->SetLogicRect( aCaptRect );
+ FitCaptionToRect( pVisRect );
+}
+
+void ScCaptionCreator::UpdateCaptionPos()
+{
+ ScDrawLayer* pDrawLayer = mrDoc.GetDrawLayer();
+
+ // update caption position
+ const Point& rOldTailPos = mxCaption->GetTailPos();
+ Point aTailPos = CalcTailPos( false );
+ if( rOldTailPos != aTailPos )
+ {
+ // create drawing undo action
+ if( pDrawLayer && pDrawLayer->IsRecording() )
+ pDrawLayer->AddCalcUndo( std::make_unique<SdrUndoGeoObj>( *mxCaption ) );
+ // calculate new caption rectangle (#i98141# handle LTR<->RTL switch correctly)
+ tools::Rectangle aCaptRect = mxCaption->GetLogicRect();
+ tools::Long nDiffX = (rOldTailPos.X() >= 0) ? (aCaptRect.Left() - rOldTailPos.X()) : (rOldTailPos.X() - aCaptRect.Right());
+ if( mbNegPage ) nDiffX = -nDiffX - aCaptRect.GetWidth();
+ tools::Long nDiffY = aCaptRect.Top() - rOldTailPos.Y();
+ aCaptRect.SetPos( aTailPos + Point( nDiffX, nDiffY ) );
+ // set new tail position and caption rectangle
+ mxCaption->SetTailPos( aTailPos );
+ mxCaption->SetLogicRect( aCaptRect );
+ // fit caption into draw page
+ FitCaptionToRect();
+ }
+
+ // update cell position in caption user data
+ ScDrawObjData* pCaptData = ScDrawLayer::GetNoteCaptionData( mxCaption.get(), maPos.Tab() );
+ if( pCaptData && (maPos != pCaptData->maStart) )
+ {
+ // create drawing undo action
+ if( pDrawLayer && pDrawLayer->IsRecording() )
+ pDrawLayer->AddCalcUndo( std::make_unique<ScUndoObjData>( mxCaption.get(), pCaptData->maStart, pCaptData->maEnd, maPos, pCaptData->maEnd ) );
+ // set new position
+ pCaptData->maStart = maPos;
+ }
+}
+
+Point ScCaptionCreator::CalcTailPos( bool bTailFront )
+{
+ // tail position
+ bool bTailLeft = bTailFront != mbNegPage;
+ Point aTailPos = bTailLeft ? maCellRect.TopLeft() : maCellRect.TopRight();
+ // move caption point 1/10 mm inside cell
+ if( bTailLeft ) aTailPos.AdjustX(10 ); else aTailPos.AdjustX( -10 );
+ aTailPos.AdjustY(10);
+ return aTailPos;
+}
+
+void ScCaptionCreator::CreateCaption( bool bShown, bool bTailFront )
+{
+ // create the caption drawing object
+ tools::Rectangle aTextRect( Point( 0 , 0 ), Size( SC_NOTECAPTION_WIDTH, SC_NOTECAPTION_HEIGHT ) );
+ Point aTailPos = CalcTailPos( bTailFront );
+ mxCaption.reset(
+ new SdrCaptionObj(
+ *mrDoc.GetDrawLayer(), // TTTT should ret a ref?
+ aTextRect,
+ aTailPos));
+ // basic caption settings
+ ScCaptionUtil::SetBasicCaptionSettings( *mxCaption, bShown );
+}
+
+void ScCaptionCreator::Initialize()
+{
+ maCellRect = ScDrawLayer::GetCellRect( mrDoc, maPos, true );
+ mbNegPage = mrDoc.IsNegativePage( maPos.Tab() );
+ if( SdrPage* pDrawPage = GetDrawPage() )
+ {
+ maPageRect = tools::Rectangle( Point( 0, 0 ), pDrawPage->GetSize() );
+ /* #i98141# SdrPage::GetSize() returns negative width in RTL mode.
+ The call to Rectangle::Adjust() orders left/right coordinate
+ accordingly. */
+ maPageRect.Justify();
+ }
+}
+
+/** Helper for creation of permanent caption drawing objects for cell notes. */
+class ScNoteCaptionCreator : public ScCaptionCreator
+{
+public:
+ /** Create a new caption object and inserts it into the document. */
+ explicit ScNoteCaptionCreator( ScDocument& rDoc, const ScAddress& rPos, ScNoteData& rNoteData );
+ /** Manipulate an existing caption. */
+ explicit ScNoteCaptionCreator( ScDocument& rDoc, const ScAddress& rPos, ScCaptionPtr& xCaption, bool bShown );
+};
+
+ScNoteCaptionCreator::ScNoteCaptionCreator( ScDocument& rDoc, const ScAddress& rPos, ScNoteData& rNoteData ) :
+ ScCaptionCreator( rDoc, rPos ) // use helper c'tor that does not create the caption yet
+{
+ SdrPage* pDrawPage = GetDrawPage();
+ OSL_ENSURE( pDrawPage, "ScNoteCaptionCreator::ScNoteCaptionCreator - no drawing page" );
+ if( !pDrawPage )
+ return;
+
+ // create the caption drawing object
+ CreateCaption( rNoteData.mbShown, false );
+ rNoteData.mxCaption = GetCaption();
+ OSL_ENSURE( rNoteData.mxCaption, "ScNoteCaptionCreator::ScNoteCaptionCreator - missing caption object" );
+ if( rNoteData.mxCaption )
+ {
+ // store note position in user data of caption object
+ ScCaptionUtil::SetCaptionUserData( *rNoteData.mxCaption, rPos );
+ // insert object into draw page
+ pDrawPage->InsertObject( rNoteData.mxCaption.get() );
+ }
+}
+
+ScNoteCaptionCreator::ScNoteCaptionCreator( ScDocument& rDoc, const ScAddress& rPos, ScCaptionPtr& xCaption, bool bShown ) :
+ ScCaptionCreator( rDoc, rPos, xCaption )
+{
+ SdrPage* pDrawPage = GetDrawPage();
+ OSL_ENSURE( pDrawPage, "ScNoteCaptionCreator::ScNoteCaptionCreator - no drawing page" );
+ OSL_ENSURE( xCaption->getSdrPageFromSdrObject() == pDrawPage, "ScNoteCaptionCreator::ScNoteCaptionCreator - wrong drawing page in caption" );
+ if( pDrawPage && (xCaption->getSdrPageFromSdrObject() == pDrawPage) )
+ {
+ // store note position in user data of caption object
+ ScCaptionUtil::SetCaptionUserData( *xCaption, rPos );
+ // basic caption settings
+ ScCaptionUtil::SetBasicCaptionSettings( *xCaption, bShown );
+ // set correct tail position
+ xCaption->SetTailPos( CalcTailPos( false ) );
+ }
+}
+
+} // namespace
+
+ScCaptionPtr::ScCaptionPtr() :
+ mpHead(nullptr), mpNext(nullptr), mpCaption(nullptr), mbNotOwner(false)
+{
+}
+
+ScCaptionPtr::ScCaptionPtr( SdrCaptionObj* p ) :
+ mpHead(nullptr), mpNext(nullptr), mpCaption(p), mbNotOwner(false)
+{
+ if (p)
+ {
+ newHead();
+ }
+}
+
+ScCaptionPtr::ScCaptionPtr( const ScCaptionPtr& r ) :
+ mpHead(r.mpHead), mpCaption(r.mpCaption), mbNotOwner(false)
+{
+ if (r.mpCaption)
+ {
+ assert(r.mpHead);
+ r.incRef();
+ // Insert into list.
+ mpNext = r.mpNext;
+ r.mpNext = this;
+ }
+ else
+ {
+ assert(!r.mpHead);
+ mpNext = nullptr;
+ }
+}
+
+ScCaptionPtr::ScCaptionPtr(ScCaptionPtr&& r) noexcept
+ : mpHead(r.mpHead), mpNext(r.mpNext), mpCaption(r.mpCaption), mbNotOwner(false)
+{
+ r.replaceInList( this );
+ r.mpCaption = nullptr;
+ r.mbNotOwner = false;
+}
+
+ScCaptionPtr& ScCaptionPtr::operator=(ScCaptionPtr&& r) noexcept
+{
+ assert(this != &r);
+
+ mpHead = r.mpHead;
+ mpNext = r.mpNext;
+ mpCaption = r.mpCaption;
+ mbNotOwner = r.mbNotOwner;
+
+ r.replaceInList( this );
+ r.mpCaption = nullptr;
+ r.mbNotOwner = false;
+
+ return *this;
+}
+
+ScCaptionPtr& ScCaptionPtr::operator=( const ScCaptionPtr& r )
+{
+ if (this == &r)
+ return *this;
+
+ if (mpCaption == r.mpCaption)
+ {
+ // Two lists for the same caption is bad.
+ assert(!mpCaption || mpHead == r.mpHead);
+ assert(!mpCaption); // assigning same caption pointer within same list is weird
+ // Nullptr captions are not inserted to the list, so nothing to do here
+ // if both are.
+ return *this;
+ }
+
+ // Let's find some weird usage.
+ // Assigning without head doesn't make sense unless it is a nullptr caption.
+ assert(r.mpHead || !r.mpCaption);
+ // A nullptr caption must not be in a list and thus not have a head.
+ assert(!r.mpHead || r.mpCaption);
+ // Same captions were caught above, so here different heads must be present.
+ assert(r.mpHead != mpHead);
+
+ r.incRef();
+ decRefAndDestroy();
+ removeFromList();
+
+ mpCaption = r.mpCaption;
+ mbNotOwner = r.mbNotOwner;
+ // That head is this' master.
+ mpHead = r.mpHead;
+ // Insert into list.
+ mpNext = r.mpNext;
+ r.mpNext = this;
+
+ return *this;
+}
+
+void ScCaptionPtr::setNotOwner()
+{
+ mbNotOwner = true;
+}
+
+ScCaptionPtr::Head::Head( ScCaptionPtr* p ) :
+ mpFirst(p), mnRefs(1)
+{
+}
+
+void ScCaptionPtr::newHead()
+{
+ assert(!mpHead);
+ mpHead = new Head(this);
+}
+
+void ScCaptionPtr::replaceInList(ScCaptionPtr* pNew) noexcept
+{
+ if (!mpHead && !mpNext)
+ return;
+
+ assert(mpHead);
+ assert(mpCaption == pNew->mpCaption);
+
+ ScCaptionPtr* pThat = mpHead->mpFirst;
+ while (pThat && pThat != this && pThat->mpNext != this)
+ {
+ pThat = pThat->mpNext;
+ }
+ if (pThat && pThat != this)
+ {
+ assert(pThat->mpNext == this);
+ pThat->mpNext = pNew;
+ }
+ pNew->mpNext = mpNext;
+ if (mpHead->mpFirst == this)
+ mpHead->mpFirst = pNew;
+
+ mpHead = nullptr;
+ mpNext = nullptr;
+}
+
+void ScCaptionPtr::removeFromList()
+{
+ if (!mpHead && !mpNext && !mpCaption)
+ return;
+
+#if OSL_DEBUG_LEVEL > 0
+ oslInterlockedCount nCount = 0;
+#endif
+ ScCaptionPtr* pThat = (mpHead ? mpHead->mpFirst : nullptr);
+ while (pThat && pThat != this && pThat->mpNext != this)
+ {
+ // Use the walk to check consistency on the fly.
+ assert(pThat->mpHead == mpHead); // all belong to the same
+ assert(pThat->mpHead || !pThat->mpNext); // next without head is bad
+ assert(pThat->mpCaption == mpCaption);
+ pThat = pThat->mpNext;
+#if OSL_DEBUG_LEVEL > 0
+ ++nCount;
+#endif
+ }
+ assert(pThat || !mpHead); // not found only if this was standalone
+ if (pThat)
+ {
+ if (pThat != this)
+ {
+#if OSL_DEBUG_LEVEL > 0
+ // The while loop above was not executed, and for this
+ // (pThat->mpNext) the loop below won't either.
+ ++nCount;
+#endif
+ pThat->mpNext = mpNext;
+ }
+#if OSL_DEBUG_LEVEL > 0
+ do
+ {
+ assert(pThat->mpHead == mpHead); // all belong to the same
+ assert(pThat->mpHead || !pThat->mpNext); // next without head is bad
+ assert(pThat->mpCaption == mpCaption);
+ ++nCount;
+ }
+ while ((pThat = pThat->mpNext) != nullptr);
+#endif
+ }
+#if OSL_DEBUG_LEVEL > 0
+ // If part of a list then refs were already decremented.
+ assert(nCount == (mpHead ? mpHead->mnRefs + 1 : 0));
+#endif
+ if (mpHead && mpHead->mpFirst == this)
+ {
+ if (mpNext)
+ mpHead->mpFirst = mpNext;
+ else
+ {
+ // The only one destroys also head.
+ assert(mpHead->mnRefs == 0); // cough
+ delete mpHead; // DEAD now
+ }
+ }
+ mpHead = nullptr;
+ mpNext = nullptr;
+}
+
+void ScCaptionPtr::reset( SdrCaptionObj* p )
+{
+ assert(!p || p != mpCaption);
+#if OSL_DEBUG_LEVEL > 0
+ if (p)
+ {
+ // Check if we end up with a duplicated management in this list.
+ ScCaptionPtr* pThat = (mpHead ? mpHead->mpFirst : nullptr);
+ while (pThat)
+ {
+ assert(pThat->mpCaption != p);
+ pThat = pThat->mpNext;
+ }
+ }
+#endif
+ decRefAndDestroy();
+ removeFromList();
+ mpCaption = p;
+ mbNotOwner = false;
+ if (p)
+ {
+ newHead();
+ }
+}
+
+ScCaptionPtr::~ScCaptionPtr()
+{
+ decRefAndDestroy();
+ removeFromList();
+}
+
+oslInterlockedCount ScCaptionPtr::getRefs() const
+{
+ return mpHead ? mpHead->mnRefs : 0;
+}
+
+void ScCaptionPtr::incRef() const
+{
+ if (mpHead)
+ osl_atomic_increment(&mpHead->mnRefs);
+}
+
+bool ScCaptionPtr::decRef() const
+{
+ return mpHead && mpHead->mnRefs > 0 && !osl_atomic_decrement(&mpHead->mnRefs);
+}
+
+void ScCaptionPtr::decRefAndDestroy()
+{
+ if (!decRef())
+ return;
+
+ assert(mpHead->mpFirst == this); // this must be one and only one
+ assert(!mpNext); // this must be one and only one
+ assert(mpCaption);
+
+#if 0
+ // Quick workaround for when there are still cases where the caption
+ // pointer is dangling
+ mpCaption = nullptr;
+ mbNotOwner = false;
+#else
+ // Destroying Draw Undo and some other delete the SdrObject, don't
+ // attempt that twice.
+ if (mbNotOwner)
+ {
+ mpCaption = nullptr;
+ mbNotOwner = false;
+ }
+ else
+ {
+ removeFromDrawPageAndFree( true ); // ignoring Undo
+ if (mpCaption)
+ {
+ // There's no draw page associated so removeFromDrawPageAndFree()
+ // didn't do anything, but still we want to delete the caption
+ // object. release()/dissolve() also resets mpCaption.
+ SdrObject* pObj = release();
+ SdrObject::Free( pObj );
+ }
+ }
+#endif
+ delete mpHead;
+ mpHead = nullptr;
+}
+
+void ScCaptionPtr::insertToDrawPage( SdrPage& rDrawPage )
+{
+ assert(mpHead && mpCaption);
+
+ rDrawPage.InsertObject( mpCaption );
+}
+
+void ScCaptionPtr::removeFromDrawPage( SdrPage& rDrawPage )
+{
+ assert(mpHead && mpCaption);
+ SdrObject* pObj = rDrawPage.RemoveObject( mpCaption->GetOrdNum() );
+ assert(pObj == mpCaption); (void)pObj;
+}
+
+void ScCaptionPtr::removeFromDrawPageAndFree( bool bIgnoreUndo )
+{
+ assert(mpHead && mpCaption);
+ SdrPage* pDrawPage(mpCaption->getSdrPageFromSdrObject());
+ SAL_WARN_IF( !pDrawPage, "sc.core", "ScCaptionPtr::removeFromDrawPageAndFree - object without drawing page");
+ if (!pDrawPage)
+ return;
+
+ pDrawPage->RecalcObjOrdNums();
+ bool bRecording = false;
+ if(!bIgnoreUndo)
+ {
+ ScDrawLayer* pDrawLayer(dynamic_cast< ScDrawLayer* >(&mpCaption->getSdrModelFromSdrObject()));
+ SAL_WARN_IF( !pDrawLayer, "sc.core", "ScCaptionPtr::removeFromDrawPageAndFree - object without drawing layer");
+ // create drawing undo action (before removing the object to have valid draw page in undo action)
+ bRecording = (pDrawLayer && pDrawLayer->IsRecording());
+ if (bRecording)
+ pDrawLayer->AddCalcUndo( std::make_unique<SdrUndoDelObj>( *mpCaption ));
+ }
+ // remove the object from the drawing page, delete if undo is disabled
+ removeFromDrawPage( *pDrawPage );
+ // If called from outside mnRefs must be 1 to delete. If called from
+ // decRefAndDestroy() mnRefs is already 0.
+ if (!bRecording && getRefs() <= 1)
+ {
+ SdrObject* pObj = release();
+ SdrObject::Free( pObj );
+ }
+}
+
+SdrCaptionObj* ScCaptionPtr::release()
+{
+ SdrCaptionObj* pTmp = mpCaption;
+ dissolve();
+ return pTmp;
+}
+
+void ScCaptionPtr::forget()
+{
+ decRef();
+ removeFromList();
+ mpCaption = nullptr;
+ mbNotOwner = false;
+}
+
+void ScCaptionPtr::dissolve()
+{
+ ScCaptionPtr::Head* pHead = mpHead;
+ ScCaptionPtr* pThat = (mpHead ? mpHead->mpFirst : this);
+ while (pThat)
+ {
+ assert(!pThat->mpNext || pThat->mpHead); // next without head is bad
+ assert(pThat->mpHead == pHead); // same head required within one list
+ ScCaptionPtr* p = pThat->mpNext;
+ pThat->clear();
+ pThat = p;
+ }
+ assert(!mpHead && !mpNext && !mpCaption); // should had been cleared during list walk
+ delete pHead;
+}
+
+void ScCaptionPtr::clear()
+{
+ mpHead = nullptr;
+ mpNext = nullptr;
+ mpCaption = nullptr;
+ mbNotOwner = false;
+}
+
+struct ScCaptionInitData
+{
+ std::optional< SfxItemSet > moItemSet; /// Caption object formatting.
+ std::optional< OutlinerParaObject > mxOutlinerObj; /// Text object with all text portion formatting.
+ OUString maSimpleText; /// Simple text without formatting.
+ Point maCaptionOffset; /// Caption position relative to cell corner.
+ Size maCaptionSize; /// Size of the caption object.
+ bool mbDefaultPosSize; /// True = use default position and size for caption.
+
+ explicit ScCaptionInitData();
+};
+
+ScCaptionInitData::ScCaptionInitData() :
+ mbDefaultPosSize( true )
+{
+}
+
+ScNoteData::ScNoteData( bool bShown ) :
+ mbShown( bShown )
+{
+}
+
+sal_uInt32 ScPostIt::mnLastPostItId = 1;
+
+ScPostIt::ScPostIt( ScDocument& rDoc, const ScAddress& rPos, sal_uInt32 nPostItId ) :
+ mrDoc( rDoc ),
+ maNoteData( false )
+{
+ mnPostItId = nPostItId == 0 ? mnLastPostItId++ : nPostItId;
+ AutoStamp();
+ CreateCaption( rPos );
+}
+
+ScPostIt::ScPostIt( ScDocument& rDoc, const ScAddress& rPos, const ScPostIt& rNote, sal_uInt32 nPostItId ) :
+ mrDoc( rDoc ),
+ maNoteData( rNote.maNoteData )
+{
+ mnPostItId = nPostItId == 0 ? mnLastPostItId++ : nPostItId;
+ maNoteData.mxCaption.reset(nullptr);
+ CreateCaption( rPos, rNote.maNoteData.mxCaption.get() );
+}
+
+ScPostIt::ScPostIt( ScDocument& rDoc, const ScAddress& rPos, const ScNoteData& rNoteData, bool bAlwaysCreateCaption, sal_uInt32 nPostItId ) :
+ mrDoc( rDoc ),
+ maNoteData( rNoteData )
+{
+ mnPostItId = nPostItId == 0 ? mnLastPostItId++ : nPostItId;
+ if( bAlwaysCreateCaption || maNoteData.mbShown )
+ CreateCaptionFromInitData( rPos );
+}
+
+ScPostIt::~ScPostIt()
+{
+ RemoveCaption();
+}
+
+std::unique_ptr<ScPostIt> ScPostIt::Clone( const ScAddress& rOwnPos, ScDocument& rDestDoc, const ScAddress& rDestPos, bool bCloneCaption ) const
+{
+ CreateCaptionFromInitData( rOwnPos );
+ sal_uInt32 nPostItId = comphelper::LibreOfficeKit::isActive() ? 0 : mnPostItId;
+ return bCloneCaption ? std::make_unique<ScPostIt>( rDestDoc, rDestPos, *this, nPostItId ) : std::make_unique<ScPostIt>( rDestDoc, rDestPos, maNoteData, false, mnPostItId );
+}
+
+void ScPostIt::SetDate( const OUString& rDate )
+{
+ maNoteData.maDate = rDate;
+}
+
+void ScPostIt::SetAuthor( const OUString& rAuthor )
+{
+ maNoteData.maAuthor = rAuthor;
+}
+
+void ScPostIt::AutoStamp()
+{
+ maNoteData.maDate = ScGlobal::getLocaleData().getDate( Date( Date::SYSTEM ) );
+ maNoteData.maAuthor = SvtUserOptions().GetID();
+}
+
+const OutlinerParaObject* ScPostIt::GetOutlinerObject() const
+{
+ if( maNoteData.mxCaption )
+ return maNoteData.mxCaption->GetOutlinerParaObject();
+ if( maNoteData.mxInitData && maNoteData.mxInitData->mxOutlinerObj )
+ return &*maNoteData.mxInitData->mxOutlinerObj;
+ return nullptr;
+}
+
+const EditTextObject* ScPostIt::GetEditTextObject() const
+{
+ const OutlinerParaObject* pOPO = GetOutlinerObject();
+ return pOPO ? &pOPO->GetTextObject() : nullptr;
+}
+
+OUString ScPostIt::GetText() const
+{
+ if( const EditTextObject* pEditObj = GetEditTextObject() )
+ {
+ OUStringBuffer aBuffer;
+ ScNoteEditEngine& rEngine = mrDoc.GetNoteEngine();
+ rEngine.SetTextCurrentDefaults(*pEditObj);
+ sal_Int32 nParaCount = rEngine.GetParagraphCount();
+ for( sal_Int32 nPara = 0; nPara < nParaCount; ++nPara )
+ {
+ if( nPara > 0 )
+ aBuffer.append( '\n' );
+ aBuffer.append(rEngine.GetText(nPara));
+ }
+ return aBuffer.makeStringAndClear();
+ }
+ if( maNoteData.mxInitData )
+ return maNoteData.mxInitData->maSimpleText;
+ return OUString();
+}
+
+bool ScPostIt::HasMultiLineText() const
+{
+ if( const EditTextObject* pEditObj = GetEditTextObject() )
+ return pEditObj->GetParagraphCount() > 1;
+ if( maNoteData.mxInitData )
+ return maNoteData.mxInitData->maSimpleText.indexOf( '\n' ) >= 0;
+ return false;
+}
+
+void ScPostIt::SetText( const ScAddress& rPos, const OUString& rText )
+{
+ CreateCaptionFromInitData( rPos );
+ if( maNoteData.mxCaption )
+ maNoteData.mxCaption->SetText( rText );
+}
+
+SdrCaptionObj* ScPostIt::GetOrCreateCaption( const ScAddress& rPos ) const
+{
+ CreateCaptionFromInitData( rPos );
+ return maNoteData.mxCaption.get();
+}
+
+void ScPostIt::ForgetCaption( bool bPreserveData )
+{
+ if (bPreserveData)
+ {
+ // Used in clipboard when the originating document is destructed to be
+ // able to paste into another document. Caption size and relative
+ // position are not preserved but default created when pasted. Also the
+ // MergedItemSet can not be carried over or it had to be adapted to
+ // defaults and pool. At least preserve the text and outline object if
+ // possible.
+ ScCaptionInitData* pInitData = new ScCaptionInitData;
+ const OutlinerParaObject* pOPO = GetOutlinerObject();
+ if (pOPO)
+ pInitData->mxOutlinerObj = *pOPO;
+ pInitData->maSimpleText = GetText();
+
+ maNoteData.mxInitData.reset(pInitData);
+ maNoteData.mxCaption.forget();
+ }
+ else
+ {
+ /* This function is used in undo actions to give up the responsibility for
+ the caption object which is handled by separate drawing undo actions. */
+ maNoteData.mxCaption.forget();
+ maNoteData.mxInitData.reset();
+ }
+}
+
+void ScPostIt::ShowCaption( const ScAddress& rPos, bool bShow )
+{
+ CreateCaptionFromInitData( rPos );
+ // no separate drawing undo needed, handled completely inside ScUndoShowHideNote
+ maNoteData.mbShown = bShow;
+ if( maNoteData.mxCaption )
+ ScCaptionUtil::SetCaptionLayer( *maNoteData.mxCaption, bShow );
+}
+
+void ScPostIt::ShowCaptionTemp( const ScAddress& rPos, bool bShow )
+{
+ CreateCaptionFromInitData( rPos );
+ if( maNoteData.mxCaption )
+ ScCaptionUtil::SetCaptionLayer( *maNoteData.mxCaption, maNoteData.mbShown || bShow );
+}
+
+void ScPostIt::UpdateCaptionPos( const ScAddress& rPos )
+{
+ CreateCaptionFromInitData( rPos );
+ if( maNoteData.mxCaption )
+ {
+ ScCaptionCreator aCreator( mrDoc, rPos, maNoteData.mxCaption );
+ aCreator.UpdateCaptionPos();
+ }
+}
+
+// private --------------------------------------------------------------------
+
+void ScPostIt::CreateCaptionFromInitData( const ScAddress& rPos ) const
+{
+ // Captions are not created in Undo documents and only rarely in Clipboard,
+ // but otherwise we need caption or initial data.
+ assert((maNoteData.mxCaption || maNoteData.mxInitData) || mrDoc.IsUndo() || mrDoc.IsClipboard());
+ if( !maNoteData.mxInitData )
+ return;
+
+
+ /* This function is called from ScPostIt::Clone() when copying cells
+ to the clipboard/undo document, and when copying cells from the
+ clipboard/undo document. The former should always be called first,
+ so if called in a clipboard/undo document, the caption should have
+ been created already. However, for clipboard in case the
+ originating document was destructed a new caption has to be
+ created. */
+ OSL_ENSURE( !mrDoc.IsUndo() && (!mrDoc.IsClipboard() || !maNoteData.mxCaption),
+ "ScPostIt::CreateCaptionFromInitData - note caption should not be created in undo/clip documents" );
+
+ // going to forget the initial caption data struct when this method returns
+ auto xInitData = std::move(maNoteData.mxInitData);
+
+ /* #i104915# Never try to create notes in Undo document, leads to
+ crash due to missing document members (e.g. row height array). */
+ if( maNoteData.mxCaption || mrDoc.IsUndo() )
+ return;
+
+ if (mrDoc.IsClipboard())
+ mrDoc.InitDrawLayer(); // ensure there is a drawing layer
+
+ // ScNoteCaptionCreator c'tor creates the caption and inserts it into the document and maNoteData
+ ScNoteCaptionCreator aCreator( mrDoc, rPos, maNoteData );
+ if( !maNoteData.mxCaption )
+ return;
+
+ // Prevent triple change broadcasts of the same object.
+ bool bWasLocked = maNoteData.mxCaption->getSdrModelFromSdrObject().isLocked();
+ maNoteData.mxCaption->getSdrModelFromSdrObject().setLock(true);
+
+ // transfer ownership of outliner object to caption, or set simple text
+ OSL_ENSURE( xInitData->mxOutlinerObj || !xInitData->maSimpleText.isEmpty(),
+ "ScPostIt::CreateCaptionFromInitData - need either outliner para object or simple text" );
+ if (xInitData->mxOutlinerObj)
+ maNoteData.mxCaption->SetOutlinerParaObject( std::move(xInitData->mxOutlinerObj) );
+ else
+ maNoteData.mxCaption->SetText( xInitData->maSimpleText );
+
+ // copy all items or set default items; reset shadow items
+ ScCaptionUtil::SetDefaultItems( *maNoteData.mxCaption, mrDoc, xInitData->moItemSet ? &*xInitData->moItemSet : nullptr );
+
+ // set position and size of the caption object
+ if( xInitData->mbDefaultPosSize )
+ {
+ // set other items and fit caption size to text
+ maNoteData.mxCaption->SetMergedItem( makeSdrTextMinFrameWidthItem( SC_NOTECAPTION_WIDTH ) );
+ maNoteData.mxCaption->SetMergedItem( makeSdrTextMaxFrameWidthItem( SC_NOTECAPTION_MAXWIDTH_TEMP ) );
+ maNoteData.mxCaption->AdjustTextFrameWidthAndHeight();
+ aCreator.AutoPlaceCaption();
+ }
+ else
+ {
+ tools::Rectangle aCellRect = ScDrawLayer::GetCellRect( mrDoc, rPos, true );
+ bool bNegPage = mrDoc.IsNegativePage( rPos.Tab() );
+ tools::Long nPosX = bNegPage ? (aCellRect.Left() - xInitData->maCaptionOffset.X()) : (aCellRect.Right() + xInitData->maCaptionOffset.X());
+ tools::Long nPosY = aCellRect.Top() + xInitData->maCaptionOffset.Y();
+ tools::Rectangle aCaptRect( Point( nPosX, nPosY ), xInitData->maCaptionSize );
+ maNoteData.mxCaption->SetLogicRect( aCaptRect );
+ aCreator.FitCaptionToRect();
+ }
+
+ // End prevent triple change broadcasts of the same object.
+ maNoteData.mxCaption->getSdrModelFromSdrObject().setLock(bWasLocked);
+ maNoteData.mxCaption->BroadcastObjectChange();
+}
+
+void ScPostIt::CreateCaption( const ScAddress& rPos, const SdrCaptionObj* pCaption )
+{
+ OSL_ENSURE( !maNoteData.mxCaption, "ScPostIt::CreateCaption - unexpected caption object found" );
+ maNoteData.mxCaption.reset(nullptr);
+
+ /* #i104915# Never try to create notes in Undo document, leads to
+ crash due to missing document members (e.g. row height array). */
+ OSL_ENSURE( !mrDoc.IsUndo(), "ScPostIt::CreateCaption - note caption should not be created in undo documents" );
+ if( mrDoc.IsUndo() )
+ return;
+
+ // drawing layer may be missing, if a note is copied into a clipboard document
+ if( mrDoc.IsClipboard() )
+ mrDoc.InitDrawLayer();
+
+ // ScNoteCaptionCreator c'tor creates the caption and inserts it into the document and maNoteData
+ ScNoteCaptionCreator aCreator( mrDoc, rPos, maNoteData );
+ if( !maNoteData.mxCaption )
+ return;
+
+ // clone settings of passed caption
+ if( pCaption )
+ {
+ // copy edit text object (object must be inserted into page already)
+ if( OutlinerParaObject* pOPO = pCaption->GetOutlinerParaObject() )
+ maNoteData.mxCaption->SetOutlinerParaObject( *pOPO );
+ // copy formatting items (after text has been copied to apply font formatting)
+ maNoteData.mxCaption->SetMergedItemSetAndBroadcast( pCaption->GetMergedItemSet() );
+ // move textbox position relative to new cell, copy textbox size
+ tools::Rectangle aCaptRect = pCaption->GetLogicRect();
+ Point aDist = maNoteData.mxCaption->GetTailPos() - pCaption->GetTailPos();
+ aCaptRect.Move( aDist.X(), aDist.Y() );
+ maNoteData.mxCaption->SetLogicRect( aCaptRect );
+ aCreator.FitCaptionToRect();
+ }
+ else
+ {
+ // set default formatting and default position
+ ScCaptionUtil::SetDefaultItems( *maNoteData.mxCaption, mrDoc, nullptr );
+ aCreator.AutoPlaceCaption();
+ }
+
+ // create undo action
+ if( ScDrawLayer* pDrawLayer = mrDoc.GetDrawLayer() )
+ if( pDrawLayer->IsRecording() )
+ pDrawLayer->AddCalcUndo( std::make_unique<SdrUndoNewObj>( *maNoteData.mxCaption ) );
+}
+
+void ScPostIt::RemoveCaption()
+{
+ if (!maNoteData.mxCaption)
+ return;
+
+ /* Remove caption object only, if this note is its owner (e.g. notes in
+ undo documents refer to captions in original document, do not remove
+ them from drawing layer here). */
+ // TTTT maybe no longer needed - can that still happen?
+ ScDrawLayer* pDrawLayer = mrDoc.GetDrawLayer();
+ if (pDrawLayer == &maNoteData.mxCaption->getSdrModelFromSdrObject())
+ maNoteData.mxCaption.removeFromDrawPageAndFree();
+
+ SAL_INFO("sc.core","ScPostIt::RemoveCaption - refs: " << maNoteData.mxCaption.getRefs() <<
+ " IsUndo: " << mrDoc.IsUndo() << " IsClip: " << mrDoc.IsClipboard() <<
+ " Dtor: " << mrDoc.IsInDtorClear());
+
+ // Forget the caption object if removeFromDrawPageAndFree() did not free it.
+ if (maNoteData.mxCaption)
+ {
+ SAL_INFO("sc.core","ScPostIt::RemoveCaption - forgetting one ref");
+ maNoteData.mxCaption.forget();
+ }
+}
+
+ScCaptionPtr ScNoteUtil::CreateTempCaption(
+ ScDocument& rDoc, const ScAddress& rPos, SdrPage& rDrawPage,
+ std::u16string_view rUserText, const tools::Rectangle& rVisRect, bool bTailFront )
+{
+ OUStringBuffer aBuffer( rUserText );
+ // add plain text of invisible (!) cell note (no formatting etc.)
+ SdrCaptionObj* pNoteCaption = nullptr;
+ const ScPostIt* pNote = rDoc.GetNote( rPos );
+ if( pNote && !pNote->IsCaptionShown() )
+ {
+ if( !aBuffer.isEmpty() )
+ aBuffer.append( "\n--------\n" + pNote->GetText() );
+ pNoteCaption = pNote->GetOrCreateCaption( rPos );
+ }
+
+ // create a caption if any text exists
+ if( !pNoteCaption && aBuffer.isEmpty() )
+ return ScCaptionPtr();
+
+ // prepare visible rectangle (add default distance to all borders)
+ tools::Rectangle aVisRect(
+ rVisRect.Left() + SC_NOTECAPTION_BORDERDIST_TEMP,
+ rVisRect.Top() + SC_NOTECAPTION_BORDERDIST_TEMP,
+ rVisRect.Right() - SC_NOTECAPTION_BORDERDIST_TEMP,
+ rVisRect.Bottom() - SC_NOTECAPTION_BORDERDIST_TEMP );
+
+ // create the caption object
+ ScCaptionCreator aCreator( rDoc, rPos, bTailFront );
+
+ // insert caption into page (needed to set caption text)
+ aCreator.GetCaption().insertToDrawPage( rDrawPage );
+
+ SdrCaptionObj* pCaption = aCreator.GetCaption().get(); // just for ease of use
+
+ // clone the edit text object, unless user text is present, then set this text
+ if( pNoteCaption && rUserText.empty() )
+ {
+ if( OutlinerParaObject* pOPO = pNoteCaption->GetOutlinerParaObject() )
+ pCaption->SetOutlinerParaObject( *pOPO );
+ // set formatting (must be done after setting text) and resize the box to fit the text
+ pCaption->SetMergedItemSetAndBroadcast( pNoteCaption->GetMergedItemSet() );
+ tools::Rectangle aCaptRect( pCaption->GetLogicRect().TopLeft(), pNoteCaption->GetLogicRect().GetSize() );
+ pCaption->SetLogicRect( aCaptRect );
+ }
+ else
+ {
+ // if pNoteCaption is null, then aBuffer contains some text
+ pCaption->SetText( aBuffer.makeStringAndClear() );
+ ScCaptionUtil::SetDefaultItems( *pCaption, rDoc, nullptr );
+ // adjust caption size to text size
+ tools::Long nMaxWidth = ::std::min< tools::Long >( aVisRect.GetWidth() * 2 / 3, SC_NOTECAPTION_MAXWIDTH_TEMP );
+ pCaption->SetMergedItem( makeSdrTextAutoGrowWidthItem( true ) );
+ pCaption->SetMergedItem( makeSdrTextMinFrameWidthItem( SC_NOTECAPTION_WIDTH ) );
+ pCaption->SetMergedItem( makeSdrTextMaxFrameWidthItem( nMaxWidth ) );
+ pCaption->SetMergedItem( makeSdrTextAutoGrowHeightItem( true ) );
+ pCaption->AdjustTextFrameWidthAndHeight();
+ }
+
+ // move caption into visible area
+ aCreator.AutoPlaceCaption( &aVisRect );
+
+ // XXX Note it is already inserted to the draw page.
+ return aCreator.GetCaption();
+}
+
+ScPostIt* ScNoteUtil::CreateNoteFromCaption(
+ ScDocument& rDoc, const ScAddress& rPos, SdrCaptionObj* pCaption )
+{
+ ScNoteData aNoteData( true/*bShown*/ );
+ aNoteData.mxCaption.reset( pCaption );
+ ScPostIt* pNote = new ScPostIt( rDoc, rPos, aNoteData, false );
+ pNote->AutoStamp();
+
+ rDoc.SetNote(rPos, std::unique_ptr<ScPostIt>(pNote));
+
+ // ScNoteCaptionCreator c'tor updates the caption object to be part of a note
+ ScNoteCaptionCreator aCreator( rDoc, rPos, aNoteData.mxCaption, true/*bShown*/ );
+
+ return pNote;
+}
+
+ScPostIt* ScNoteUtil::CreateNoteFromObjectData(
+ ScDocument& rDoc, const ScAddress& rPos, SfxItemSet&& rItemSet,
+ const OutlinerParaObject& rOutlinerObj, const tools::Rectangle& rCaptionRect,
+ bool bShown )
+{
+ ScNoteData aNoteData( bShown );
+ aNoteData.mxInitData = std::make_shared<ScCaptionInitData>();
+ ScCaptionInitData& rInitData = *aNoteData.mxInitData;
+ rInitData.moItemSet.emplace(std::move(rItemSet));
+ rInitData.mxOutlinerObj = rOutlinerObj;
+
+ // convert absolute caption position to relative position
+ rInitData.mbDefaultPosSize = rCaptionRect.IsEmpty();
+ if( !rInitData.mbDefaultPosSize )
+ {
+ tools::Rectangle aCellRect = ScDrawLayer::GetCellRect( rDoc, rPos, true );
+ bool bNegPage = rDoc.IsNegativePage( rPos.Tab() );
+ rInitData.maCaptionOffset.setX( bNegPage ? (aCellRect.Left() - rCaptionRect.Right()) : (rCaptionRect.Left() - aCellRect.Right()) );
+ rInitData.maCaptionOffset.setY( rCaptionRect.Top() - aCellRect.Top() );
+ rInitData.maCaptionSize = rCaptionRect.GetSize();
+ }
+
+ /* Create the note and insert it into the document. If the note is
+ visible, the caption object will be created automatically. */
+ ScPostIt* pNote = new ScPostIt( rDoc, rPos, aNoteData, /*bAlwaysCreateCaption*/false, 0/*nPostItId*/ );
+ pNote->AutoStamp();
+
+ rDoc.SetNote(rPos, std::unique_ptr<ScPostIt>(pNote));
+
+ return pNote;
+}
+
+ScPostIt* ScNoteUtil::CreateNoteFromString(
+ ScDocument& rDoc, const ScAddress& rPos, const OUString& rNoteText,
+ bool bShown, bool bAlwaysCreateCaption, sal_uInt32 nPostItId )
+{
+ ScPostIt* pNote = nullptr;
+ if( !rNoteText.isEmpty() )
+ {
+ ScNoteData aNoteData( bShown );
+ aNoteData.mxInitData = std::make_shared<ScCaptionInitData>();
+ ScCaptionInitData& rInitData = *aNoteData.mxInitData;
+ rInitData.maSimpleText = rNoteText;
+ rInitData.mbDefaultPosSize = true;
+
+ /* Create the note and insert it into the document. If the note is
+ visible, the caption object will be created automatically. */
+ pNote = new ScPostIt( rDoc, rPos, aNoteData, bAlwaysCreateCaption, nPostItId );
+ pNote->AutoStamp();
+ //insert takes ownership
+ rDoc.SetNote(rPos, std::unique_ptr<ScPostIt>(pNote));
+ }
+ return pNote;
+}
+
+namespace sc {
+
+NoteEntry::NoteEntry( const ScAddress& rPos, const ScPostIt* pNote ) :
+ maPos(rPos), mpNote(pNote) {}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/queryevaluator.cxx b/sc/source/core/data/queryevaluator.cxx
new file mode 100644
index 000000000..0cf27a36e
--- /dev/null
+++ b/sc/source/core/data/queryevaluator.cxx
@@ -0,0 +1,961 @@
+/* -*- 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 <queryevaluator.hxx>
+
+#include <cellform.hxx>
+#include <cellvalue.hxx>
+#include <colorscale.hxx>
+#include <document.hxx>
+#include <docoptio.hxx>
+#include <queryparam.hxx>
+#include <scitems.hxx>
+#include <table.hxx>
+
+#include <editeng/brushitem.hxx>
+#include <editeng/colritem.hxx>
+#include <svl/numformat.hxx>
+#include <svl/sharedstringpool.hxx>
+#include <svl/zformat.hxx>
+#include <unotools/collatorwrapper.hxx>
+
+bool ScQueryEvaluator::isPartialTextMatchOp(ScQueryOp eOp)
+{
+ switch (eOp)
+ {
+ // these operators can only be used with textural comparisons.
+ case SC_CONTAINS:
+ case SC_DOES_NOT_CONTAIN:
+ case SC_BEGINS_WITH:
+ case SC_ENDS_WITH:
+ case SC_DOES_NOT_BEGIN_WITH:
+ case SC_DOES_NOT_END_WITH:
+ return true;
+ default:;
+ }
+ return false;
+}
+
+bool ScQueryEvaluator::isTextMatchOp(ScQueryOp eOp)
+{
+ if (isPartialTextMatchOp(eOp))
+ return true;
+
+ switch (eOp)
+ {
+ // these operators can be used for either textural or value comparison.
+ case SC_EQUAL:
+ case SC_NOT_EQUAL:
+ return true;
+ default:;
+ }
+ return false;
+}
+
+bool ScQueryEvaluator::isMatchWholeCellHelper(bool docMatchWholeCell, ScQueryOp eOp)
+{
+ bool bMatchWholeCell = docMatchWholeCell;
+ if (isPartialTextMatchOp(eOp))
+ // may have to do partial textural comparison.
+ bMatchWholeCell = false;
+ return bMatchWholeCell;
+}
+
+bool ScQueryEvaluator::isMatchWholeCell(ScQueryOp eOp) const
+{
+ return isMatchWholeCellHelper(mbMatchWholeCell, eOp);
+}
+
+bool ScQueryEvaluator::isMatchWholeCell(const ScDocument& rDoc, ScQueryOp eOp)
+{
+ return isMatchWholeCellHelper(rDoc.GetDocOptions().IsMatchWholeCell(), eOp);
+}
+
+void ScQueryEvaluator::setupTransliteratorIfNeeded()
+{
+ if (!mpTransliteration)
+ mpTransliteration = &ScGlobal::GetTransliteration(mrParam.bCaseSens);
+}
+
+void ScQueryEvaluator::setupCollatorIfNeeded()
+{
+ if (!mpCollator)
+ mpCollator = &ScGlobal::GetCollator(mrParam.bCaseSens);
+}
+
+ScQueryEvaluator::ScQueryEvaluator(ScDocument& rDoc, const ScTable& rTab,
+ const ScQueryParam& rParam, const ScInterpreterContext* pContext,
+ bool* pTestEqualCondition)
+ : mrDoc(rDoc)
+ , mrStrPool(rDoc.GetSharedStringPool())
+ , mrTab(rTab)
+ , mrParam(rParam)
+ , mpTestEqualCondition(pTestEqualCondition)
+ , mpTransliteration(nullptr)
+ , mpCollator(nullptr)
+ , mbMatchWholeCell(rDoc.GetDocOptions().IsMatchWholeCell())
+ , mbCaseSensitive(rParam.bCaseSens)
+ , mpContext(pContext)
+ , mnEntryCount(mrParam.GetEntryCount())
+{
+ if (mnEntryCount <= nFixedBools)
+ {
+ mpPasst = &maBool[0];
+ mpTest = &maTest[0];
+ }
+ else
+ {
+ mpBoolDynamic.reset(new bool[mnEntryCount]);
+ mpTestDynamic.reset(new bool[mnEntryCount]);
+ mpPasst = mpBoolDynamic.get();
+ mpTest = mpTestDynamic.get();
+ }
+}
+
+bool ScQueryEvaluator::isRealWildOrRegExp(const ScQueryEntry& rEntry) const
+{
+ if (mrParam.eSearchType == utl::SearchParam::SearchType::Normal)
+ return false;
+
+ return isTextMatchOp(rEntry.eOp);
+}
+
+bool ScQueryEvaluator::isTestWildOrRegExp(const ScQueryEntry& rEntry) const
+{
+ if (!mpTestEqualCondition)
+ return false;
+
+ if (mrParam.eSearchType == utl::SearchParam::SearchType::Normal)
+ return false;
+
+ return (rEntry.eOp == SC_LESS_EQUAL || rEntry.eOp == SC_GREATER_EQUAL);
+}
+
+bool ScQueryEvaluator::isQueryByValue(ScQueryOp eOp, ScQueryEntry::QueryType eType,
+ const ScRefCellValue& rCell)
+{
+ if (eType == ScQueryEntry::ByString || isPartialTextMatchOp(eOp))
+ return false;
+
+ return isQueryByValueForCell(rCell);
+}
+
+bool ScQueryEvaluator::isQueryByValueForCell(const ScRefCellValue& rCell)
+{
+ if (rCell.meType == CELLTYPE_FORMULA && rCell.mpFormula->GetErrCode() != FormulaError::NONE)
+ // Error values are compared as string.
+ return false;
+
+ return rCell.hasNumeric();
+}
+
+bool ScQueryEvaluator::isQueryByString(ScQueryOp eOp, ScQueryEntry::QueryType eType,
+ const ScRefCellValue& rCell)
+{
+ if (isTextMatchOp(eOp))
+ return true;
+
+ if (eType != ScQueryEntry::ByString)
+ return false;
+
+ return rCell.hasString();
+}
+
+sal_uInt32 ScQueryEvaluator::getNumFmt(SCCOL nCol, SCROW nRow)
+{
+ sal_uInt32 nNumFmt
+ = (mpContext ? mrTab.GetNumberFormat(*mpContext, ScAddress(nCol, nRow, mrTab.GetTab()))
+ : mrTab.GetNumberFormat(nCol, nRow));
+ if (nNumFmt && (nNumFmt % SV_COUNTRY_LANGUAGE_OFFSET) == 0)
+ // Any General of any locale is irrelevant for rounding.
+ nNumFmt = 0;
+ return nNumFmt;
+}
+
+std::pair<bool, bool> ScQueryEvaluator::compareByValue(const ScRefCellValue& rCell, SCCOL nCol,
+ SCROW nRow, const ScQueryEntry& rEntry,
+ const ScQueryEntry::Item& rItem)
+{
+ bool bOk = false;
+ bool bTestEqual = false;
+ double nCellVal;
+ double fQueryVal = rItem.mfVal;
+ // Defer all number format detection to as late as possible as it's a
+ // bottle neck, even if that complicates the code. Also do not
+ // unnecessarily call ScDocument::RoundValueAsShown() for the same
+ // reason.
+ sal_uInt32 nNumFmt = NUMBERFORMAT_ENTRY_NOT_FOUND;
+
+ switch (rCell.meType)
+ {
+ case CELLTYPE_VALUE:
+ nCellVal = rCell.mfValue;
+ break;
+ case CELLTYPE_FORMULA:
+ nCellVal = rCell.mpFormula->GetValue();
+ break;
+ default:
+ nCellVal = 0.0;
+ }
+ if (rItem.mbRoundForFilter && nCellVal != 0.0)
+ {
+ nNumFmt = getNumFmt(nCol, nRow);
+ if (nNumFmt)
+ {
+ switch (rCell.meType)
+ {
+ case CELLTYPE_VALUE:
+ case CELLTYPE_FORMULA:
+ nCellVal = mrDoc.RoundValueAsShown(nCellVal, nNumFmt, mpContext);
+ break;
+ default:
+ assert(!"can't be");
+ }
+ }
+ }
+
+ /* NOTE: lcl_PrepareQuery() prepares a filter query such that if a
+ * date+time format was queried rEntry.bQueryByDate is not set. In
+ * case other queries wanted to use this mechanism they should do
+ * the same, in other words only if rEntry.nVal is an integer value
+ * rEntry.bQueryByDate should be true and the time fraction be
+ * stripped here. */
+
+ if (rItem.meType == ScQueryEntry::ByDate)
+ {
+ if (nNumFmt == NUMBERFORMAT_ENTRY_NOT_FOUND)
+ nNumFmt = getNumFmt(nCol, nRow);
+ if (nNumFmt)
+ {
+ SvNumberFormatter* pFormatter
+ = mpContext ? mpContext->GetFormatTable() : mrDoc.GetFormatTable();
+ const SvNumberformat* pEntry = pFormatter->GetEntry(nNumFmt);
+ if (pEntry)
+ {
+ SvNumFormatType nNumFmtType = pEntry->GetType();
+ /* NOTE: Omitting the check for absence of
+ * css::util::NumberFormat::TIME would include also date+time formatted
+ * values of the same day. That may be desired in some
+ * cases, querying all time values of a day, but confusing
+ * in other cases. A user can always setup a standard
+ * filter query for x >= date AND x < date+1 */
+ if ((nNumFmtType & SvNumFormatType::DATE) && !(nNumFmtType & SvNumFormatType::TIME))
+ {
+ // The format is of date type. Strip off the time
+ // element.
+ nCellVal = ::rtl::math::approxFloor(nCellVal);
+ }
+ }
+ }
+ }
+
+ switch (rEntry.eOp)
+ {
+ case SC_EQUAL:
+ bOk = ::rtl::math::approxEqual(nCellVal, fQueryVal);
+ break;
+ case SC_LESS:
+ bOk = (nCellVal < fQueryVal) && !::rtl::math::approxEqual(nCellVal, fQueryVal);
+ break;
+ case SC_GREATER:
+ bOk = (nCellVal > fQueryVal) && !::rtl::math::approxEqual(nCellVal, fQueryVal);
+ break;
+ case SC_LESS_EQUAL:
+ bOk = (nCellVal < fQueryVal) || ::rtl::math::approxEqual(nCellVal, fQueryVal);
+ if (bOk && mpTestEqualCondition)
+ bTestEqual = ::rtl::math::approxEqual(nCellVal, fQueryVal);
+ break;
+ case SC_GREATER_EQUAL:
+ bOk = (nCellVal > fQueryVal) || ::rtl::math::approxEqual(nCellVal, fQueryVal);
+ if (bOk && mpTestEqualCondition)
+ bTestEqual = ::rtl::math::approxEqual(nCellVal, fQueryVal);
+ break;
+ case SC_NOT_EQUAL:
+ bOk = !::rtl::math::approxEqual(nCellVal, fQueryVal);
+ break;
+ default:
+ assert(false);
+ break;
+ }
+
+ return std::pair<bool, bool>(bOk, bTestEqual);
+}
+
+OUString ScQueryEvaluator::getCellString(const ScRefCellValue& rCell, SCROW nRow, SCCOL nCol,
+ const svl::SharedString** sharedString)
+{
+ if (rCell.meType == CELLTYPE_FORMULA && rCell.mpFormula->GetErrCode() != FormulaError::NONE)
+ {
+ // Error cell is evaluated as string (for now).
+ const FormulaError error = rCell.mpFormula->GetErrCode();
+ auto it = mCachedSharedErrorStrings.find(error);
+ if (it == mCachedSharedErrorStrings.end())
+ {
+ svl::SharedString str = mrStrPool.intern(ScGlobal::GetErrorString(error));
+ auto pos = mCachedSharedErrorStrings.insert({ error, str });
+ assert(pos.second); // inserted
+ it = pos.first;
+ }
+ *sharedString = &it->second;
+ return OUString();
+ }
+ else if (rCell.meType == CELLTYPE_STRING)
+ {
+ *sharedString = rCell.mpString;
+ return OUString();
+ }
+ else
+ {
+ sal_uInt32 nFormat
+ = mpContext ? mrTab.GetNumberFormat(*mpContext, ScAddress(nCol, nRow, mrTab.GetTab()))
+ : mrTab.GetNumberFormat(nCol, nRow);
+ SvNumberFormatter* pFormatter
+ = mpContext ? mpContext->GetFormatTable() : mrDoc.GetFormatTable();
+ return ScCellFormat::GetInputString(rCell, nFormat, *pFormatter, mrDoc, sharedString, true);
+ }
+}
+
+bool ScQueryEvaluator::isFastCompareByString(const ScQueryEntry& rEntry) const
+{
+ // If this is true, then there's a fast path in compareByString() which
+ // can be selected using the template argument to get fast code
+ // that will not check the same conditions every time. This makes a difference
+ // in fast lookups that search for an exact value (case sensitive or not).
+ const bool bRealWildOrRegExp = isRealWildOrRegExp(rEntry);
+ const bool bTestWildOrRegExp = isTestWildOrRegExp(rEntry);
+ // SC_EQUAL is part of isTextMatchOp(rEntry)
+ return rEntry.eOp == SC_EQUAL && !bRealWildOrRegExp && !bTestWildOrRegExp
+ && isMatchWholeCell(rEntry.eOp);
+}
+
+// The value is placed inside one parameter: [pValueSource1] or [pValueSource2] but never in both.
+// For the template argument see isFastCompareByString().
+template <bool bFast>
+std::pair<bool, bool> ScQueryEvaluator::compareByString(const ScQueryEntry& rEntry,
+ const ScQueryEntry::Item& rItem,
+ const svl::SharedString* pValueSource1,
+ const OUString* pValueSource2)
+{
+ bool bOk = false;
+ bool bTestEqual = false;
+ bool bMatchWholeCell;
+ if (bFast)
+ bMatchWholeCell = true;
+ else
+ bMatchWholeCell = isMatchWholeCell(rEntry.eOp);
+ const bool bRealWildOrRegExp = !bFast && isRealWildOrRegExp(rEntry);
+ const bool bTestWildOrRegExp = !bFast && isTestWildOrRegExp(rEntry);
+
+ assert(!bFast || pValueSource1 != nullptr); // shared string for fast path
+ // [pValueSource1] or [pValueSource2] but never both of them or none of them
+ assert((pValueSource1 != nullptr) != (pValueSource2 != nullptr));
+
+ if (!bFast && (bRealWildOrRegExp || bTestWildOrRegExp))
+ {
+ const OUString& rValue = pValueSource1 ? pValueSource1->getString() : *pValueSource2;
+
+ sal_Int32 nStart = 0;
+ sal_Int32 nEnd = rValue.getLength();
+
+ // from 614 on, nEnd is behind the found text
+ bool bMatch = false;
+ if (rEntry.eOp == SC_ENDS_WITH || rEntry.eOp == SC_DOES_NOT_END_WITH)
+ {
+ nEnd = 0;
+ nStart = rValue.getLength();
+ bMatch
+ = rEntry.GetSearchTextPtr(mrParam.eSearchType, mrParam.bCaseSens, bMatchWholeCell)
+ ->SearchBackward(rValue, &nStart, &nEnd);
+ }
+ else
+ {
+ bMatch
+ = rEntry.GetSearchTextPtr(mrParam.eSearchType, mrParam.bCaseSens, bMatchWholeCell)
+ ->SearchForward(rValue, &nStart, &nEnd);
+ }
+ if (bMatch && bMatchWholeCell && (nStart != 0 || nEnd != rValue.getLength()))
+ bMatch = false; // RegExp must match entire cell string
+ if (bRealWildOrRegExp)
+ {
+ switch (rEntry.eOp)
+ {
+ case SC_EQUAL:
+ case SC_CONTAINS:
+ bOk = bMatch;
+ break;
+ case SC_NOT_EQUAL:
+ case SC_DOES_NOT_CONTAIN:
+ bOk = !bMatch;
+ break;
+ case SC_BEGINS_WITH:
+ bOk = (bMatch && (nStart == 0));
+ break;
+ case SC_DOES_NOT_BEGIN_WITH:
+ bOk = !(bMatch && (nStart == 0));
+ break;
+ case SC_ENDS_WITH:
+ bOk = (bMatch && (nEnd == rValue.getLength()));
+ break;
+ case SC_DOES_NOT_END_WITH:
+ bOk = !(bMatch && (nEnd == rValue.getLength()));
+ break;
+ default:
+ assert(false);
+ break;
+ }
+ }
+ else
+ bTestEqual = bMatch;
+ }
+ if (bFast || !bRealWildOrRegExp)
+ {
+ // Simple string matching i.e. no regexp match.
+ if (bFast || isTextMatchOp(rEntry.eOp))
+ {
+ // Check this even with bFast.
+ if (rItem.meType != ScQueryEntry::ByString && rItem.maString.isEmpty())
+ {
+ // #i18374# When used from functions (match, countif, sumif, vlookup, hlookup, lookup),
+ // the query value is assigned directly, and the string is empty. In that case,
+ // don't find any string (isEqual would find empty string results in formula cells).
+ bOk = false;
+ if (rEntry.eOp == SC_NOT_EQUAL)
+ bOk = !bOk;
+ }
+ else if (bFast || bMatchWholeCell)
+ {
+ if (bFast || pValueSource1)
+ {
+ // Fast string equality check by comparing string identifiers.
+ // This is the bFast path, all conditions should lead here on bFast == true.
+ if (mrParam.bCaseSens)
+ {
+ bOk = pValueSource1->getData() == rItem.maString.getData();
+ }
+ else
+ {
+ bOk = pValueSource1->getDataIgnoreCase()
+ == rItem.maString.getDataIgnoreCase();
+ }
+ }
+ else // if (pValueSource2)
+ {
+ if (mrParam.bCaseSens)
+ {
+ bOk = (*pValueSource2 == rItem.maString.getString());
+ }
+ else
+ {
+ // fallback
+ const svl::SharedString rSource2(mrStrPool.intern(*pValueSource2));
+ // Fast string equality check by comparing string identifiers.
+ bOk = rSource2.getDataIgnoreCase() == rItem.maString.getDataIgnoreCase();
+ }
+ }
+
+ if (!bFast && rEntry.eOp == SC_NOT_EQUAL)
+ bOk = !bOk;
+ }
+ else
+ {
+ // Where do we find a match (if at all)
+ sal_Int32 nStrPos;
+
+ if (!mbCaseSensitive)
+ { // Common case for vlookup etc.
+ const svl::SharedString rSource(
+ pValueSource1 ? *pValueSource1 : mrStrPool.intern(*pValueSource2));
+
+ const rtl_uString* pQuer = rItem.maString.getDataIgnoreCase();
+ const rtl_uString* pCellStr = rSource.getDataIgnoreCase();
+
+ assert(pCellStr != nullptr);
+ if (pQuer == nullptr)
+ pQuer = svl::SharedString::getEmptyString().getDataIgnoreCase();
+
+ const sal_Int32 nIndex
+ = (rEntry.eOp == SC_ENDS_WITH || rEntry.eOp == SC_DOES_NOT_END_WITH)
+ ? (pCellStr->length - pQuer->length)
+ : 0;
+
+ if (nIndex < 0)
+ nStrPos = -1;
+ else
+ { // OUString::indexOf
+ nStrPos = rtl_ustr_indexOfStr_WithLength(pCellStr->buffer + nIndex,
+ pCellStr->length - nIndex,
+ pQuer->buffer, pQuer->length);
+
+ if (nStrPos >= 0)
+ nStrPos += nIndex;
+ }
+ }
+ else
+ {
+ const OUString& rValue
+ = pValueSource1 ? pValueSource1->getString() : *pValueSource2;
+ const OUString aQueryStr = rItem.maString.getString();
+ const LanguageType nLang
+ = ScGlobal::oSysLocale->GetLanguageTag().getLanguageType();
+ setupTransliteratorIfNeeded();
+ const OUString aCell(mpTransliteration->transliterate(
+ rValue, nLang, 0, rValue.getLength(), nullptr));
+
+ const OUString aQuer(mpTransliteration->transliterate(
+ aQueryStr, nLang, 0, aQueryStr.getLength(), nullptr));
+
+ const sal_Int32 nIndex
+ = (rEntry.eOp == SC_ENDS_WITH || rEntry.eOp == SC_DOES_NOT_END_WITH)
+ ? (aCell.getLength() - aQuer.getLength())
+ : 0;
+ nStrPos = ((nIndex < 0) ? -1 : aCell.indexOf(aQuer, nIndex));
+ }
+ switch (rEntry.eOp)
+ {
+ case SC_EQUAL:
+ case SC_CONTAINS:
+ bOk = (nStrPos != -1);
+ break;
+ case SC_NOT_EQUAL:
+ case SC_DOES_NOT_CONTAIN:
+ bOk = (nStrPos == -1);
+ break;
+ case SC_BEGINS_WITH:
+ bOk = (nStrPos == 0);
+ break;
+ case SC_DOES_NOT_BEGIN_WITH:
+ bOk = (nStrPos != 0);
+ break;
+ case SC_ENDS_WITH:
+ bOk = (nStrPos >= 0);
+ break;
+ case SC_DOES_NOT_END_WITH:
+ bOk = (nStrPos < 0);
+ break;
+ default:
+ assert(false);
+ break;
+ }
+ }
+ }
+ else
+ { // use collator here because data was probably sorted
+ const OUString& rValue = pValueSource1 ? pValueSource1->getString() : *pValueSource2;
+ setupCollatorIfNeeded();
+ sal_Int32 nCompare = mpCollator->compareString(rValue, rItem.maString.getString());
+ switch (rEntry.eOp)
+ {
+ case SC_LESS:
+ bOk = (nCompare < 0);
+ break;
+ case SC_GREATER:
+ bOk = (nCompare > 0);
+ break;
+ case SC_LESS_EQUAL:
+ bOk = (nCompare <= 0);
+ if (bOk && mpTestEqualCondition && !bTestEqual)
+ bTestEqual = (nCompare == 0);
+ break;
+ case SC_GREATER_EQUAL:
+ bOk = (nCompare >= 0);
+ if (bOk && mpTestEqualCondition && !bTestEqual)
+ bTestEqual = (nCompare == 0);
+ break;
+ default:
+ assert(false);
+ break;
+ }
+ }
+ }
+
+ return std::pair<bool, bool>(bOk, bTestEqual);
+}
+
+std::pair<bool, bool> ScQueryEvaluator::compareByTextColor(SCCOL nCol, SCROW nRow,
+ const ScQueryEntry::Item& rItem)
+{
+ ScAddress aPos(nCol, nRow, mrTab.GetTab());
+ Color color;
+ bool bHasConditionalColor = false;
+ // Text color can be set via conditional formatting - check that first
+ const ScPatternAttr* pPattern = mrDoc.GetPattern(nCol, nRow, mrTab.GetTab());
+ if (pPattern)
+ {
+ if (!pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData().empty())
+ {
+ const SfxItemSet* pCondSet = mrDoc.GetCondResult(nCol, nRow, mrTab.GetTab());
+ const SvxColorItem* pColor = &pPattern->GetItem(ATTR_FONT_COLOR, pCondSet);
+ color = pColor->GetValue();
+ bHasConditionalColor = true;
+ }
+ }
+
+ if (!bHasConditionalColor)
+ {
+ const SvxColorItem* pColor = mrDoc.GetAttr(aPos, ATTR_FONT_COLOR);
+ color = pColor->GetValue();
+ }
+
+ bool bMatch = rItem.maColor == color;
+ return std::pair<bool, bool>(bMatch, false);
+}
+
+std::pair<bool, bool> ScQueryEvaluator::compareByBackgroundColor(SCCOL nCol, SCROW nRow,
+ const ScQueryEntry::Item& rItem)
+{
+ ScAddress aPos(nCol, nRow, mrTab.GetTab());
+ Color color;
+
+ // Background color can be set via conditional formatting - check that first
+ bool bHasConditionalColor = false;
+ const ScPatternAttr* pPattern = mrDoc.GetPattern(nCol, nRow, mrTab.GetTab());
+ if (pPattern)
+ {
+ if (!pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData().empty())
+ {
+ const SfxItemSet* pCondSet = mrDoc.GetCondResult(nCol, nRow, mrTab.GetTab());
+ const SvxBrushItem* pBackgroundColor = &pPattern->GetItem(ATTR_BACKGROUND, pCondSet);
+ color = pBackgroundColor->GetColor();
+ bHasConditionalColor = true;
+ }
+ }
+
+ ScConditionalFormat* pCondFormat = mrDoc.GetCondFormat(nCol, nRow, mrTab.GetTab());
+ if (pCondFormat)
+ {
+ for (size_t i = 0; i < pCondFormat->size(); i++)
+ {
+ auto aEntry = pCondFormat->GetEntry(i);
+ if (aEntry->GetType() == ScFormatEntry::Type::Colorscale)
+ {
+ const ScColorScaleFormat* pColFormat
+ = static_cast<const ScColorScaleFormat*>(aEntry);
+ color = *(pColFormat->GetColor(aPos));
+ bHasConditionalColor = true;
+ }
+ }
+ }
+
+ if (!bHasConditionalColor)
+ {
+ const SvxBrushItem* pBrush = mrDoc.GetAttr(aPos, ATTR_BACKGROUND);
+ color = pBrush->GetColor();
+ }
+
+ bool bMatch = rItem.maColor == color;
+ return std::pair<bool, bool>(bMatch, false);
+}
+
+// To be called only if both isQueryByValue() and isQueryByString()
+// returned false and range lookup is wanted! In range lookup comparison
+// numbers are less than strings. Nothing else is compared.
+std::pair<bool, bool> ScQueryEvaluator::compareByRangeLookup(const ScRefCellValue& rCell,
+ const ScQueryEntry& rEntry,
+ const ScQueryEntry::Item& rItem)
+{
+ bool bTestEqual = false;
+
+ if (rItem.meType == ScQueryEntry::ByString && rEntry.eOp != SC_LESS
+ && rEntry.eOp != SC_LESS_EQUAL)
+ return std::pair<bool, bool>(false, bTestEqual);
+
+ if (rItem.meType != ScQueryEntry::ByString && rEntry.eOp != SC_GREATER
+ && rEntry.eOp != SC_GREATER_EQUAL)
+ return std::pair<bool, bool>(false, bTestEqual);
+
+ if (rItem.meType == ScQueryEntry::ByString)
+ {
+ if (rCell.meType == CELLTYPE_FORMULA && rCell.mpFormula->GetErrCode() != FormulaError::NONE)
+ // Error values are compared as string.
+ return std::pair<bool, bool>(false, bTestEqual);
+
+ return std::pair<bool, bool>(rCell.hasNumeric(), bTestEqual);
+ }
+
+ return std::pair<bool, bool>(!rCell.hasNumeric(), bTestEqual);
+}
+
+std::pair<bool, bool> ScQueryEvaluator::processEntry(SCROW nRow, SCCOL nCol, ScRefCellValue& aCell,
+ const ScQueryEntry& rEntry, size_t nEntryIndex)
+{
+ std::pair<bool, bool> aRes(false, false);
+ const ScQueryEntry::QueryItemsType& rItems = rEntry.GetQueryItems();
+ if (rItems.size() == 1 && rItems.front().meType == ScQueryEntry::ByEmpty)
+ {
+ if (rEntry.IsQueryByEmpty())
+ aRes.first = aCell.isEmpty();
+ else
+ {
+ assert(rEntry.IsQueryByNonEmpty());
+ aRes.first = !aCell.isEmpty();
+ }
+ return aRes;
+ }
+ if (rEntry.eOp == SC_EQUAL && rItems.size() >= 10)
+ {
+ // If there are many items to query for (autofilter does this), then try to search
+ // efficiently in those items. So first search all the items of the relevant type,
+ // If that does not find anything, fall back to the generic code.
+ double value = 0;
+ bool valid = true;
+ // For ScQueryEntry::ByValue check that the cell either is a value or is a formula
+ // that has a value and is not an error (those are compared as strings). This
+ // is basically simplified isQueryByValue().
+ if (aCell.meType == CELLTYPE_VALUE)
+ value = aCell.mfValue;
+ else if (aCell.meType == CELLTYPE_FORMULA
+ && aCell.mpFormula->GetErrCode() != FormulaError::NONE
+ && aCell.mpFormula->IsValue())
+ {
+ value = aCell.mpFormula->GetValue();
+ }
+ else
+ valid = false;
+ if (valid)
+ {
+ if (rItems.size() >= 100)
+ {
+ // Sort, cache and binary search for the value in items.
+ // Don't bother comparing approximately.
+ if (mCachedSortedItemValues.size() <= nEntryIndex)
+ {
+ mCachedSortedItemValues.resize(nEntryIndex + 1);
+ auto& values = mCachedSortedItemValues[nEntryIndex];
+ values.reserve(rItems.size());
+ for (const auto& rItem : rItems)
+ if (rItem.meType == ScQueryEntry::ByValue)
+ values.push_back(rItem.mfVal);
+ std::sort(values.begin(), values.end());
+ }
+ auto& values = mCachedSortedItemValues[nEntryIndex];
+ auto it = std::lower_bound(values.begin(), values.end(), value);
+ if (it != values.end() && *it == value)
+ return std::make_pair(true, true);
+ }
+ else
+ {
+ for (const auto& rItem : rItems)
+ {
+ // For speed don't bother comparing approximately here, usually there either
+ // will be an exact match or it wouldn't match anyway.
+ if (rItem.meType == ScQueryEntry::ByValue && value == rItem.mfVal)
+ {
+ return std::make_pair(true, true);
+ }
+ }
+ }
+ }
+ }
+ const svl::SharedString* cellSharedString = nullptr;
+ OUString cellString;
+ bool cellStringSet = false;
+ const bool bFastCompareByString = isFastCompareByString(rEntry);
+ if (rEntry.eOp == SC_EQUAL && rItems.size() >= 10 && bFastCompareByString)
+ {
+ // The same as above but for strings. Try to optimize the case when
+ // it's a svl::SharedString comparison. That happens when SC_EQUAL is used
+ // and simple matching is used, see compareByString()
+ if (!cellStringSet)
+ {
+ cellString = getCellString(aCell, nRow, rEntry.nField, &cellSharedString);
+ cellStringSet = true;
+ }
+ // Allow also checking ScQueryEntry::ByValue if the cell is not numeric,
+ // as in that case isQueryByNumeric() would be false and isQueryByString() would
+ // be true because of SC_EQUAL making isTextMatchOp() true.
+ bool compareByValue = !isQueryByValueForCell(aCell);
+ // For ScQueryEntry::ByString check that the cell is represented by a shared string,
+ // which means it's either a string cell or a formula error. This is not as
+ // generous as isQueryByString() but it should be enough and better be safe.
+ if (cellSharedString != nullptr)
+ {
+ if (rItems.size() >= 100)
+ {
+ // Sort, cache and binary search for the string in items.
+ // Since each SharedString is identified by pointer value,
+ // sorting by pointer value is enough.
+ if (mCachedSortedItemStrings.size() <= nEntryIndex)
+ {
+ mCachedSortedItemStrings.resize(nEntryIndex + 1);
+ auto& values = mCachedSortedItemStrings[nEntryIndex];
+ values.reserve(rItems.size());
+ for (const auto& rItem : rItems)
+ {
+ if (rItem.meType == ScQueryEntry::ByString
+ || (compareByValue && rItem.meType == ScQueryEntry::ByValue))
+ {
+ values.push_back(mrParam.bCaseSens
+ ? rItem.maString.getData()
+ : rItem.maString.getDataIgnoreCase());
+ }
+ }
+ std::sort(values.begin(), values.end());
+ }
+ auto& values = mCachedSortedItemStrings[nEntryIndex];
+ const rtl_uString* string = mrParam.bCaseSens
+ ? cellSharedString->getData()
+ : cellSharedString->getDataIgnoreCase();
+ auto it = std::lower_bound(values.begin(), values.end(), string);
+ if (it != values.end() && *it == string)
+ return std::make_pair(true, true);
+ }
+ else
+ {
+ for (const auto& rItem : rItems)
+ {
+ if ((rItem.meType == ScQueryEntry::ByString
+ || (compareByValue && rItem.meType == ScQueryEntry::ByValue))
+ && (mrParam.bCaseSens
+ ? cellSharedString->getData() == rItem.maString.getData()
+ : cellSharedString->getDataIgnoreCase()
+ == rItem.maString.getDataIgnoreCase()))
+ {
+ return std::make_pair(true, true);
+ }
+ }
+ }
+ }
+ }
+ // Generic handling.
+ for (const auto& rItem : rItems)
+ {
+ if (rItem.meType == ScQueryEntry::ByTextColor)
+ {
+ std::pair<bool, bool> aThisRes = compareByTextColor(nCol, nRow, rItem);
+ aRes.first |= aThisRes.first;
+ aRes.second |= aThisRes.second;
+ }
+ else if (rItem.meType == ScQueryEntry::ByBackgroundColor)
+ {
+ std::pair<bool, bool> aThisRes = compareByBackgroundColor(nCol, nRow, rItem);
+ aRes.first |= aThisRes.first;
+ aRes.second |= aThisRes.second;
+ }
+ else if (isQueryByValue(rEntry.eOp, rItem.meType, aCell))
+ {
+ std::pair<bool, bool> aThisRes = compareByValue(aCell, nCol, nRow, rEntry, rItem);
+ aRes.first |= aThisRes.first;
+ aRes.second |= aThisRes.second;
+ }
+ else if (isQueryByString(rEntry.eOp, rItem.meType, aCell))
+ {
+ if (!cellStringSet)
+ {
+ cellString = getCellString(aCell, nRow, rEntry.nField, &cellSharedString);
+ cellStringSet = true;
+ }
+ std::pair<bool, bool> aThisRes;
+ if (cellSharedString && bFastCompareByString) // fast
+ aThisRes = compareByString<true>(rEntry, rItem, cellSharedString, nullptr);
+ else if (cellSharedString)
+ aThisRes = compareByString(rEntry, rItem, cellSharedString, nullptr);
+ else
+ aThisRes = compareByString(rEntry, rItem, nullptr, &cellString);
+ aRes.first |= aThisRes.first;
+ aRes.second |= aThisRes.second;
+ }
+ else if (mrParam.mbRangeLookup)
+ {
+ std::pair<bool, bool> aThisRes = compareByRangeLookup(aCell, rEntry, rItem);
+ aRes.first |= aThisRes.first;
+ aRes.second |= aThisRes.second;
+ }
+
+ if (aRes.first && (aRes.second || mpTestEqualCondition == nullptr))
+ break;
+ }
+ return aRes;
+}
+
+bool ScQueryEvaluator::ValidQuery(SCROW nRow, const ScRefCellValue* pCell,
+ sc::TableColumnBlockPositionSet* pBlockPos)
+{
+ if (!mrParam.GetEntry(0).bDoQuery)
+ return true;
+
+ tools::Long nPos = -1;
+ ScQueryParam::const_iterator it, itBeg = mrParam.begin(), itEnd = mrParam.end();
+ for (it = itBeg; it != itEnd && it->bDoQuery; ++it)
+ {
+ const ScQueryEntry& rEntry = *it;
+
+ // Short-circuit the test at the end of the loop - if this is SC_AND
+ // and the previous value is false, this value will not be needed.
+ // Disable this if pbTestEqualCondition is present as that one may get set
+ // even if the result is false (that also means pTest doesn't need to be
+ // handled here).
+ if (rEntry.eConnect == SC_AND && mpTestEqualCondition == nullptr && nPos != -1
+ && !mpPasst[nPos])
+ {
+ continue;
+ }
+
+ SCCOL nCol = static_cast<SCCOL>(rEntry.nField);
+
+ // We can only handle one single direct query passed as a known pCell,
+ // subsequent queries have to obtain the cell.
+ ScRefCellValue aCell;
+ if (pCell && it == itBeg)
+ aCell = *pCell;
+ else if (pBlockPos)
+ { // hinted mdds access
+ aCell = const_cast<ScTable&>(mrTab).GetCellValue(
+ nCol, *pBlockPos->getBlockPosition(nCol), nRow);
+ }
+ else
+ aCell = mrTab.GetCellValue(nCol, nRow);
+
+ std::pair<bool, bool> aRes = processEntry(nRow, nCol, aCell, rEntry, it - itBeg);
+
+ if (nPos == -1)
+ {
+ nPos++;
+ mpPasst[nPos] = aRes.first;
+ mpTest[nPos] = aRes.second;
+ }
+ else
+ {
+ if (rEntry.eConnect == SC_AND)
+ {
+ mpPasst[nPos] = mpPasst[nPos] && aRes.first;
+ mpTest[nPos] = mpTest[nPos] && aRes.second;
+ }
+ else
+ {
+ nPos++;
+ mpPasst[nPos] = aRes.first;
+ mpTest[nPos] = aRes.second;
+ }
+ }
+ }
+
+ for (tools::Long j = 1; j <= nPos; j++)
+ {
+ mpPasst[0] = mpPasst[0] || mpPasst[j];
+ mpTest[0] = mpTest[0] || mpTest[j];
+ }
+
+ bool bRet = mpPasst[0];
+ if (mpTestEqualCondition)
+ *mpTestEqualCondition = mpTest[0];
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/queryiter.cxx b/sc/source/core/data/queryiter.cxx
new file mode 100644
index 000000000..6718c1870
--- /dev/null
+++ b/sc/source/core/data/queryiter.cxx
@@ -0,0 +1,1476 @@
+/* -*- 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 <queryiter.hxx>
+
+#include <comphelper/flagguard.hxx>
+#include <o3tl/safeint.hxx>
+#include <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+
+#include <global.hxx>
+#include <dociter.hxx>
+#include <document.hxx>
+#include <table.hxx>
+#include <column.hxx>
+#include <formulacell.hxx>
+#include <attarray.hxx>
+#include <patattr.hxx>
+#include <docoptio.hxx>
+#include <cellform.hxx>
+#include <segmenttree.hxx>
+#include <progress.hxx>
+#include <queryparam.hxx>
+#include <queryentry.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <cellvalue.hxx>
+#include <scmatrix.hxx>
+#include <rowheightcontext.hxx>
+#include <queryevaluator.hxx>
+#include <rangecache.hxx>
+#include <refdata.hxx>
+
+#include <o3tl/safeint.hxx>
+#include <tools/fract.hxx>
+#include <editeng/editobj.hxx>
+#include <svl/sharedstring.hxx>
+#include <unotools/collatorwrapper.hxx>
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+
+#include <algorithm>
+#include <limits>
+#include <vector>
+
+template< ScQueryCellIteratorAccess accessType, ScQueryCellIteratorType queryType >
+ScQueryCellIteratorBase< accessType, queryType >::ScQueryCellIteratorBase(ScDocument& rDocument,
+ ScInterpreterContext& rContext, SCTAB nTable, const ScQueryParam& rParam, bool bMod )
+ : AccessBase( rDocument, rContext, rParam )
+ , nStopOnMismatch( nStopOnMismatchDisabled )
+ , nTestEqualCondition( nTestEqualConditionDisabled )
+ , bAdvanceQuery( false )
+ , bIgnoreMismatchOnLeadingStrings( false )
+{
+ nTab = nTable;
+ nCol = maParam.nCol1;
+ nRow = maParam.nRow1;
+ SCSIZE i;
+ if (!bMod) // Or else it's already inserted
+ return;
+
+ SCSIZE nCount = maParam.GetEntryCount();
+ for (i = 0; (i < nCount) && (maParam.GetEntry(i).bDoQuery); ++i)
+ {
+ ScQueryEntry& rEntry = maParam.GetEntry(i);
+ ScQueryEntry::Item& rItem = rEntry.GetQueryItem();
+ sal_uInt32 nIndex = 0;
+ bool bNumber = mrContext.GetFormatTable()->IsNumberFormat(
+ rItem.maString.getString(), nIndex, rItem.mfVal);
+ rItem.meType = bNumber ? ScQueryEntry::ByValue : ScQueryEntry::ByString;
+ }
+}
+
+template< ScQueryCellIteratorAccess accessType, ScQueryCellIteratorType queryType >
+void ScQueryCellIteratorBase< accessType, queryType >::PerformQuery()
+{
+ assert(nTab < rDoc.GetTableCount() && "index out of bounds, FIX IT");
+ const ScQueryEntry& rEntry = maParam.GetEntry(0);
+ const ScQueryEntry::Item& rItem = rEntry.GetQueryItem();
+
+ const bool bSingleQueryItem = rEntry.GetQueryItems().size() == 1;
+ SCCOLROW nFirstQueryField = rEntry.nField;
+ bool bAllStringIgnore = bIgnoreMismatchOnLeadingStrings &&
+ rItem.meType != ScQueryEntry::ByString;
+ bool bFirstStringIgnore = bIgnoreMismatchOnLeadingStrings &&
+ !maParam.bHasHeader && rItem.meType == ScQueryEntry::ByString &&
+ ((maParam.bByRow && nRow == maParam.nRow1) ||
+ (!maParam.bByRow && nCol == maParam.nCol1));
+ bool bTestEqualCondition = false;
+ ScQueryEvaluator queryEvaluator(rDoc, *rDoc.maTabs[nTab], maParam, &mrContext,
+ (nTestEqualCondition ? &bTestEqualCondition : nullptr));
+ if( queryType == ScQueryCellIteratorType::CountIf )
+ {
+ // These are not used for COUNTIF, so should not be set, make the compiler
+ // explicitly aware of it so that the relevant parts are optimized away.
+ assert( !bAllStringIgnore );
+ assert( !bIgnoreMismatchOnLeadingStrings );
+ assert( nStopOnMismatch == nStopOnMismatchDisabled );
+ assert( nTestEqualCondition == nTestEqualConditionDisabled );
+ bAllStringIgnore = false;
+ bIgnoreMismatchOnLeadingStrings = false;
+ nStopOnMismatch = nStopOnMismatchDisabled;
+ nTestEqualCondition = nTestEqualConditionDisabled;
+ // This one is always set.
+ assert( bAdvanceQuery );
+ bAdvanceQuery = true;
+ }
+
+ ScColumn* pCol = &(rDoc.maTabs[nTab])->aCol[nCol];
+ while (true)
+ {
+ bool bNextColumn = maCurPos.first == pCol->maCells.end();
+ if (!bNextColumn)
+ {
+ if (nRow > maParam.nRow2)
+ bNextColumn = true;
+ }
+
+ if (bNextColumn)
+ {
+ do
+ {
+ ++nCol;
+ if (nCol > maParam.nCol2 || nCol >= rDoc.maTabs[nTab]->GetAllocatedColumnsCount())
+ return;
+ if ( bAdvanceQuery )
+ {
+ AdvanceQueryParamEntryField();
+ nFirstQueryField = rEntry.nField;
+ }
+ pCol = &(rDoc.maTabs[nTab])->aCol[nCol];
+ }
+ while (!rItem.mbMatchEmpty && pCol->IsEmptyData());
+
+ InitPos();
+
+ bFirstStringIgnore = bIgnoreMismatchOnLeadingStrings &&
+ !maParam.bHasHeader && rItem.meType == ScQueryEntry::ByString &&
+ maParam.bByRow;
+ }
+
+ if (maCurPos.first->type == sc::element_type_empty)
+ {
+ if (rItem.mbMatchEmpty && bSingleQueryItem)
+ {
+ // This shortcut, instead of determining if any SC_OR query
+ // exists or this query is SC_AND'ed (which wouldn't make
+ // sense, but..) and evaluating them in ValidQuery(), is
+ // possible only because the interpreter is the only caller
+ // that sets mbMatchEmpty and there is only one item in those
+ // cases.
+ // XXX this would have to be reworked if other filters used it
+ // in different manners and evaluation would have to be done in
+ // ValidQuery().
+ if(HandleItemFound())
+ return;
+ IncPos();
+ continue;
+ }
+ else
+ {
+ IncBlock();
+ continue;
+ }
+ }
+
+ ScRefCellValue aCell = sc::toRefCell(maCurPos.first, maCurPos.second);
+
+ if (bAllStringIgnore && aCell.hasString())
+ IncPos();
+ else
+ {
+ if ( queryEvaluator.ValidQuery( nRow,
+ (nCol == static_cast<SCCOL>(nFirstQueryField) ? &aCell : nullptr)))
+ {
+ if ( nTestEqualCondition && bTestEqualCondition )
+ nTestEqualCondition |= nTestEqualConditionMatched;
+ if ( aCell.isEmpty())
+ return;
+ if( HandleItemFound())
+ return;
+ IncPos();
+ continue;
+ }
+ else if ( nStopOnMismatch )
+ {
+ // Yes, even a mismatch may have a fulfilled equal
+ // condition if regular expressions were involved and
+ // SC_LESS_EQUAL or SC_GREATER_EQUAL were queried.
+ if ( nTestEqualCondition && bTestEqualCondition )
+ {
+ nTestEqualCondition |= nTestEqualConditionMatched;
+ nStopOnMismatch |= nStopOnMismatchOccurred;
+ return;
+ }
+ bool bStop;
+ if (bFirstStringIgnore)
+ {
+ if (aCell.hasString())
+ {
+ IncPos();
+ bStop = false;
+ }
+ else
+ bStop = true;
+ }
+ else
+ bStop = true;
+ if (bStop)
+ {
+ nStopOnMismatch |= nStopOnMismatchOccurred;
+ return;
+ }
+ }
+ else
+ IncPos();
+ }
+ bFirstStringIgnore = false;
+ }
+}
+
+template< ScQueryCellIteratorAccess accessType, ScQueryCellIteratorType queryType >
+void ScQueryCellIteratorBase< accessType, queryType >::InitPos()
+{
+ if constexpr( accessType != ScQueryCellIteratorAccess::SortedCache )
+ AccessBase::InitPos();
+ else
+ {
+ // This should be all in AccessBase::InitPos(), but that one can't call
+ // BinarySearch(), so do it this way instead.
+ AccessBase::InitPosStart();
+ ScQueryOp& op = maParam.GetEntry(0).eOp;
+ SCROW beforeRow = -1;
+ SCROW lastRow = -1;
+ if( op == SC_EQUAL )
+ {
+ if( BinarySearch( nCol ))
+ {
+ // BinarySearch() searches for the last item that matches. Now we
+ // also need to find the first item where to start. Find the last
+ // non-matching position using SC_LESS and the start position
+ // is the one after it.
+ lastRow = nRow;
+ ScQueryOp saveOp = op;
+ op = SC_LESS;
+ if( BinarySearch( nCol, true ))
+ beforeRow = nRow;
+ // If BinarySearch() returns false, there was no match, which means
+ // there's no value smaller. In that case BinarySearch() has set
+ // the position to the first row in the range.
+ op = saveOp; // back to SC_EQUAL
+ }
+ else if( maParam.GetEntry(0).GetQueryItem().mbMatchEmpty
+ && rDoc.IsEmptyData(nCol, maParam.nRow1, nCol, maParam.nRow2, nTab))
+ {
+ // BinarySearch() returns false in case it's all empty data,
+ // handle that specially.
+ beforeRow = -1;
+ lastRow = maParam.nRow2;
+ }
+ }
+ else
+ { // The range is from the start up to and including the last matching.
+ if( BinarySearch( nCol ))
+ lastRow = nRow;
+ }
+ AccessBase::InitPosFinish( beforeRow, lastRow );
+ }
+}
+
+template< ScQueryCellIteratorAccess accessType, ScQueryCellIteratorType queryType >
+void ScQueryCellIteratorBase< accessType, queryType >::AdvanceQueryParamEntryField()
+{
+ SCSIZE nEntries = maParam.GetEntryCount();
+ for ( SCSIZE j = 0; j < nEntries; j++ )
+ {
+ ScQueryEntry& rEntry = maParam.GetEntry( j );
+ if ( rEntry.bDoQuery )
+ {
+ if ( rEntry.nField < rDoc.MaxCol() )
+ rEntry.nField++;
+ else
+ {
+ assert(!"AdvanceQueryParamEntryField: ++rEntry.nField > MAXCOL");
+ }
+ }
+ else
+ break; // for
+ }
+}
+
+namespace {
+
+template<typename Iter>
+void incBlock(std::pair<Iter, size_t>& rPos)
+{
+ // Move to the next block.
+ ++rPos.first;
+ rPos.second = 0;
+}
+
+template<typename Iter>
+void decBlock(std::pair<Iter, size_t>& rPos)
+{
+ // Move to the last element of the previous block.
+ --rPos.first;
+ rPos.second = rPos.first->size - 1;
+}
+
+}
+
+template< ScQueryCellIteratorAccess accessType, ScQueryCellIteratorType queryType >
+bool ScQueryCellIteratorBase< accessType, queryType >::BinarySearch( SCCOL col, bool forEqual )
+{
+ assert(maParam.GetEntry(0).bDoQuery && !maParam.GetEntry(1).bDoQuery
+ && maParam.GetEntry(0).GetQueryItems().size() == 1 );
+ assert(maParam.eSearchType == utl::SearchParam::SearchType::Normal);
+ assert(maParam.GetEntry(0).GetQueryItem().meType == ScQueryEntry::ByString
+ || maParam.GetEntry(0).GetQueryItem().meType == ScQueryEntry::ByValue);
+ assert(maParam.bByRow);
+ assert(maParam.GetEntry(0).eOp == SC_LESS || maParam.GetEntry(0).eOp == SC_LESS_EQUAL
+ || maParam.GetEntry(0).eOp == SC_GREATER || maParam.GetEntry(0).eOp == SC_GREATER_EQUAL
+ || maParam.GetEntry(0).eOp == SC_EQUAL);
+
+ // TODO: This will be extremely slow with mdds::multi_type_vector.
+
+ assert(nTab < rDoc.GetTableCount() && "index out of bounds, FIX IT");
+ nCol = col;
+ nRow = maParam.nRow1;
+
+ if (nCol >= rDoc.maTabs[nTab]->GetAllocatedColumnsCount())
+ return false;
+
+ ScColumn* pCol = &(rDoc.maTabs[nTab])->aCol[nCol];
+ if (pCol->IsEmptyData())
+ return false;
+
+ CollatorWrapper& rCollator = ScGlobal::GetCollator(maParam.bCaseSens);
+ SvNumberFormatter& rFormatter = *(mrContext.GetFormatTable());
+ const ScQueryEntry& rEntry = maParam.GetEntry(0);
+ const ScQueryEntry::Item& rItem = rEntry.GetQueryItem();
+ bool bAscending = rEntry.eOp == SC_LESS || rEntry.eOp == SC_LESS_EQUAL || rEntry.eOp == SC_EQUAL;
+ bool bByString = rItem.meType == ScQueryEntry::ByString;
+ bool bForceStr = bByString && ( rEntry.eOp == SC_EQUAL || forEqual );
+ bool bAllStringIgnore = bIgnoreMismatchOnLeadingStrings && !bByString;
+ bool bFirstStringIgnore = bIgnoreMismatchOnLeadingStrings &&
+ !maParam.bHasHeader && bByString;
+
+ if (maParam.bHasHeader)
+ ++nRow;
+
+ if (bFirstStringIgnore)
+ {
+ sc::CellStoreType::const_position_type aPos = pCol->maCells.position(nRow);
+ if (aPos.first->type == sc::element_type_string || aPos.first->type == sc::element_type_edittext)
+ {
+ ScRefCellValue aCell = sc::toRefCell(aPos.first, aPos.second);
+ sal_uInt32 nFormat = pCol->GetNumberFormat(mrContext, nRow);
+ OUString aCellStr = ScCellFormat::GetInputString(aCell, nFormat, rFormatter, rDoc);
+ sal_Int32 nTmp = rCollator.compareString(aCellStr, rEntry.GetQueryItem().maString.getString());
+ if ((rEntry.eOp == SC_LESS_EQUAL && nTmp > 0) ||
+ (rEntry.eOp == SC_GREATER_EQUAL && nTmp < 0) ||
+ (rEntry.eOp == SC_EQUAL && nTmp != 0) ||
+ (rEntry.eOp == SC_LESS && nTmp >= 0) ||
+ (rEntry.eOp == SC_GREATER && nTmp <= 0))
+ ++nRow;
+ }
+ }
+
+ // Skip leading empty block, if any.
+ sc::CellStoreType::const_position_type startPos = pCol->maCells.position(nRow);
+ if (startPos.first->type == sc::element_type_empty)
+ incBlock(startPos);
+ if(bAllStringIgnore)
+ {
+ // Skip all leading string or empty blocks.
+ while (startPos.first != pCol->maCells.end()
+ && (startPos.first->type == sc::element_type_string ||
+ startPos.first->type == sc::element_type_edittext ||
+ startPos.first->type == sc::element_type_empty))
+ {
+ incBlock(startPos);
+ }
+ }
+ if(startPos.first == pCol->maCells.end())
+ return false;
+ nRow = startPos.first->position + startPos.second;
+ if (nRow > maParam.nRow2)
+ return false;
+
+ auto aIndexer = MakeBinarySearchIndexer(pCol->maCells, nRow, maParam.nRow2);
+ if (!aIndexer.isValid())
+ return false;
+
+ size_t nLo = aIndexer.getLowIndex();
+ size_t nHi = aIndexer.getHighIndex();
+ BinarySearchCellType aCellData;
+
+ // Bookkeeping values for breaking up the binary search in case the data
+ // range isn't strictly sorted.
+ size_t nLastInRange = nLo;
+ double fLastInRangeValue = bAscending ?
+ -(::std::numeric_limits<double>::max()) :
+ ::std::numeric_limits<double>::max();
+ OUString aLastInRangeString;
+ if (!bAscending)
+ aLastInRangeString = OUString(u'\xFFFF');
+
+ aCellData = aIndexer.getCell(nLastInRange);
+ ScRefCellValue aCell = aCellData.first;
+ if (bForceStr || aCell.hasString())
+ {
+ sal_uInt32 nFormat = pCol->GetNumberFormat(mrContext, aCellData.second);
+ OUString aStr = ScCellFormat::GetInputString(aCell, nFormat, rFormatter, rDoc);
+ aLastInRangeString = aStr;
+ }
+ else
+ {
+ switch (aCell.meType)
+ {
+ case CELLTYPE_VALUE :
+ fLastInRangeValue = aCell.mfValue;
+ break;
+ case CELLTYPE_FORMULA :
+ fLastInRangeValue = aCell.mpFormula->GetValue();
+ break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+ }
+
+ sal_Int32 nRes = 0;
+ std::optional<size_t> found;
+ bool bDone = false;
+ bool orderBroken = false;
+ while (nLo <= nHi && !bDone)
+ {
+ size_t nMid = (nLo+nHi)/2;
+ size_t i = nMid;
+
+ aCellData = aIndexer.getCell(i);
+ aCell = aCellData.first;
+ bool bStr = bForceStr || aCell.hasString();
+ nRes = 0;
+
+ // compares are content<query:-1, content>query:1
+ // Cell value comparison similar to ScTable::ValidQuery()
+ if (!bStr && !bByString)
+ {
+ double nCellVal;
+ switch (aCell.meType)
+ {
+ case CELLTYPE_VALUE :
+ case CELLTYPE_FORMULA :
+ nCellVal = aCell.getValue();
+ break;
+ default:
+ nCellVal = 0.0;
+ }
+ if ((nCellVal < rItem.mfVal) && !::rtl::math::approxEqual(
+ nCellVal, rItem.mfVal))
+ {
+ nRes = -1;
+ if (bAscending)
+ {
+ if (fLastInRangeValue <= nCellVal)
+ {
+ fLastInRangeValue = nCellVal;
+ nLastInRange = i;
+ }
+ else if (fLastInRangeValue >= nCellVal)
+ {
+ // not strictly sorted, continue with GetThis()
+ orderBroken = true;
+ bDone = true;
+ }
+ }
+ }
+ else if ((nCellVal > rItem.mfVal) && !::rtl::math::approxEqual(
+ nCellVal, rItem.mfVal))
+ {
+ nRes = 1;
+ if (!bAscending)
+ {
+ if (fLastInRangeValue >= nCellVal)
+ {
+ fLastInRangeValue = nCellVal;
+ nLastInRange = i;
+ }
+ else if (fLastInRangeValue <= nCellVal)
+ {
+ // not strictly sorted, continue with GetThis()
+ orderBroken = true;
+ bDone = true;
+ }
+ }
+ }
+ }
+ else if (bStr && bByString)
+ {
+ sal_uInt32 nFormat = pCol->GetNumberFormat(mrContext, aCellData.second);
+ OUString aCellStr = ScCellFormat::GetInputString(aCell, nFormat, rFormatter, rDoc);
+
+ nRes = rCollator.compareString(aCellStr, rEntry.GetQueryItem().maString.getString());
+ if (nRes < 0 && bAscending)
+ {
+ sal_Int32 nTmp = rCollator.compareString( aLastInRangeString,
+ aCellStr);
+ if (nTmp <= 0)
+ {
+ aLastInRangeString = aCellStr;
+ nLastInRange = i;
+ }
+ else if (nTmp > 0)
+ {
+ // not strictly sorted, continue with GetThis()
+ orderBroken = true;
+ bDone = true;
+ }
+ }
+ else if (nRes > 0 && !bAscending)
+ {
+ sal_Int32 nTmp = rCollator.compareString( aLastInRangeString,
+ aCellStr);
+ if (nTmp >= 0)
+ {
+ aLastInRangeString = aCellStr;
+ nLastInRange = i;
+ }
+ else if (nTmp < 0)
+ {
+ // not strictly sorted, continue with GetThis()
+ orderBroken = true;
+ bDone = true;
+ }
+ }
+ }
+ else if (!bStr && bByString)
+ {
+ nRes = -1; // numeric < string
+ if (bAscending)
+ nLastInRange = i;
+ }
+ else // if (bStr && !bByString)
+ {
+ nRes = 1; // string > numeric
+ if (!bAscending)
+ nLastInRange = i;
+ }
+ if (nRes < 0)
+ {
+ if (bAscending)
+ nLo = nMid + 1;
+ else // assumed to be SC_GREATER_EQUAL
+ {
+ if (nMid > 0)
+ nHi = nMid - 1;
+ else
+ bDone = true;
+ }
+ }
+ else if (nRes > 0)
+ {
+ if (bAscending)
+ {
+ if (nMid > 0)
+ nHi = nMid - 1;
+ else
+ bDone = true;
+ }
+ else // assumed to be SC_GREATER_EQUAL
+ nLo = nMid + 1;
+ }
+ else
+ {
+ if(rEntry.eOp == SC_LESS_EQUAL || rEntry.eOp == SC_GREATER_EQUAL || rEntry.eOp == SC_EQUAL)
+ {
+ found = i;
+ nLastInRange = i;
+ // But keep searching to find the last matching one.
+ nLo = nMid + 1;
+ }
+ else if (bAscending)
+ {
+ if (nMid > 0)
+ nHi = nMid - 1;
+ else
+ bDone = true;
+ }
+ else
+ {
+ if (nMid > 0)
+ nHi = nMid - 1;
+ else
+ bDone = true;
+ }
+ }
+ }
+
+ bool isInRange;
+ if (orderBroken)
+ {
+ // Reset position to the first row in range and force caller
+ // to search from start.
+ nLo = aIndexer.getLowIndex();
+ isInRange = false;
+ }
+ else if (found)
+ {
+ nLo = *found;
+ isInRange = true;
+ }
+ else
+ {
+ // Not nothing was found and the search position is at the start,
+ // then the possible match would need to be before the data range.
+ // In that case return false to force the caller to search from the start
+ // and detect this.
+ isInRange = nLo != aIndexer.getLowIndex();
+ // If nothing was found, that is either because there is no value
+ // that would match exactly, or the data range is not properly sorted
+ // and we failed to detect (doing so reliably would require a linear scan).
+ // Set the position to the last one that was in matching range (i.e. before
+ // where the exact match would be), and leave sorting it out to GetThis()
+ // or whatever the caller uses.
+ nLo = nLastInRange;
+ }
+
+ aCellData = aIndexer.getCell(nLo);
+ if (nLo <= nHi && aCellData.second <= maParam.nRow2)
+ {
+ nRow = aCellData.second;
+ maCurPos = aIndexer.getPosition(nLo);
+ return isInRange;
+ }
+ else
+ {
+ nRow = maParam.nRow2 + 1;
+ // Set current position to the last possible row.
+ maCurPos.first = pCol->maCells.end();
+ --maCurPos.first;
+ maCurPos.second = maCurPos.first->size - 1;
+ return false;
+ }
+}
+
+
+template< ScQueryCellIteratorAccess accessType >
+bool ScQueryCellIterator< accessType >::FindEqualOrSortedLastInRange( SCCOL& nFoundCol,
+ SCROW& nFoundRow )
+{
+ // Set and automatically reset mpParam->mbRangeLookup when returning.
+ comphelper::FlagRestorationGuard aRangeLookupResetter( maParam.mbRangeLookup, true );
+
+ nFoundCol = rDoc.MaxCol()+1;
+ nFoundRow = rDoc.MaxRow()+1;
+ SetStopOnMismatch( true ); // assume sorted keys
+ SetTestEqualCondition( true );
+ bIgnoreMismatchOnLeadingStrings = true;
+ bool bLiteral = maParam.eSearchType == utl::SearchParam::SearchType::Normal &&
+ maParam.GetEntry(0).GetQueryItem().meType == ScQueryEntry::ByString;
+ bool bBinary = maParam.bByRow &&
+ (bLiteral || maParam.GetEntry(0).GetQueryItem().meType == ScQueryEntry::ByValue) &&
+ (maParam.GetEntry(0).eOp == SC_LESS_EQUAL || maParam.GetEntry(0).eOp == SC_GREATER_EQUAL);
+ bool bFound = false;
+ if (bBinary)
+ {
+ if (BinarySearch( maParam.nCol1 ))
+ {
+ // BinarySearch() already positions correctly and only needs real
+ // query comparisons afterwards, skip the verification check below.
+ maParam.mbRangeLookup = false;
+ bFound = GetThis();
+ }
+ else // Not sorted properly, or before the range (in which case GetFirst() will be simple).
+ bFound = GetFirst();
+ }
+ else
+ {
+ bFound = GetFirst();
+ }
+ if (bFound)
+ {
+ // First equal entry or last smaller than (greater than) entry.
+ PositionType aPosSave;
+ bool bNext = false;
+ do
+ {
+ nFoundCol = GetCol();
+ nFoundRow = GetRow();
+ aPosSave = maCurPos;
+ if (IsEqualConditionFulfilled())
+ break;
+ bNext = GetNext();
+ }
+ while (bNext);
+
+ // There may be no pNext but equal condition fulfilled if regular
+ // expressions are involved. Keep the found entry and proceed.
+ if (!bNext && !IsEqualConditionFulfilled())
+ {
+ // Step back to last in range and adjust position markers for
+ // GetNumberFormat() or similar.
+ SCCOL nColDiff = nCol - nFoundCol;
+ nCol = nFoundCol;
+ nRow = nFoundRow;
+ maCurPos = aPosSave;
+ if (maParam.mbRangeLookup)
+ {
+ // Verify that the found entry does not only fulfill the range
+ // lookup but also the real query, i.e. not numeric was found
+ // if query is ByString and vice versa.
+ maParam.mbRangeLookup = false;
+ // Step back the last field advance if GetNext() did one.
+ if (bAdvanceQuery && nColDiff)
+ {
+ SCSIZE nEntries = maParam.GetEntryCount();
+ for (SCSIZE j=0; j < nEntries; ++j)
+ {
+ ScQueryEntry& rEntry = maParam.GetEntry( j );
+ if (rEntry.bDoQuery)
+ {
+ if (rEntry.nField - nColDiff >= 0)
+ rEntry.nField -= nColDiff;
+ else
+ {
+ assert(!"FindEqualOrSortedLastInRange: rEntry.nField -= nColDiff < 0");
+ }
+ }
+ else
+ break; // for
+ }
+ }
+ // Check it.
+ if (!GetThis())
+ {
+ nFoundCol = rDoc.MaxCol()+1;
+ nFoundRow = rDoc.MaxRow()+1;
+ }
+ }
+ }
+ }
+ if ( IsEqualConditionFulfilled() )
+ {
+ // Position on last equal entry.
+ SCSIZE nEntries = maParam.GetEntryCount();
+ for ( SCSIZE j = 0; j < nEntries; j++ )
+ {
+ ScQueryEntry& rEntry = maParam.GetEntry( j );
+ if ( rEntry.bDoQuery )
+ {
+ switch ( rEntry.eOp )
+ {
+ case SC_LESS_EQUAL :
+ case SC_GREATER_EQUAL :
+ rEntry.eOp = SC_EQUAL;
+ break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+ }
+ else
+ break; // for
+ }
+ PositionType aPosSave;
+ bIgnoreMismatchOnLeadingStrings = false;
+ SetTestEqualCondition( false );
+ do
+ {
+ nFoundCol = GetCol();
+ nFoundRow = GetRow();
+ aPosSave = maCurPos;
+ } while (GetNext());
+
+ // Step back conditions are the same as above
+ nCol = nFoundCol;
+ nRow = nFoundRow;
+ maCurPos = aPosSave;
+ return true;
+ }
+ if ( (maParam.eSearchType != utl::SearchParam::SearchType::Normal) &&
+ StoppedOnMismatch() )
+ {
+ // Assume found entry to be the last value less than respectively
+ // greater than the query. But keep on searching for an equal match.
+ SCSIZE nEntries = maParam.GetEntryCount();
+ for ( SCSIZE j = 0; j < nEntries; j++ )
+ {
+ ScQueryEntry& rEntry = maParam.GetEntry( j );
+ if ( rEntry.bDoQuery )
+ {
+ switch ( rEntry.eOp )
+ {
+ case SC_LESS_EQUAL :
+ case SC_GREATER_EQUAL :
+ rEntry.eOp = SC_EQUAL;
+ break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+ }
+ else
+ break; // for
+ }
+ SetStopOnMismatch( false );
+ SetTestEqualCondition( false );
+ if (GetNext())
+ {
+ // Last of a consecutive area, avoid searching the entire parameter
+ // range as it is a real performance bottleneck in case of regular
+ // expressions.
+ PositionType aPosSave;
+ do
+ {
+ nFoundCol = GetCol();
+ nFoundRow = GetRow();
+ aPosSave = maCurPos;
+ SetStopOnMismatch( true );
+ } while (GetNext());
+ nCol = nFoundCol;
+ nRow = nFoundRow;
+ maCurPos = aPosSave;
+ }
+ }
+ return (nFoundCol <= rDoc.MaxCol()) && (nFoundRow <= rDoc.MaxRow());
+}
+
+// Direct linear cell access using mdds.
+
+ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::Direct >
+ ::ScQueryCellIteratorAccessSpecific( ScDocument& rDocument,
+ ScInterpreterContext& rContext, const ScQueryParam& rParam )
+ : maParam( rParam )
+ , rDoc( rDocument )
+ , mrContext( rContext )
+{
+ // coverity[uninit_member] - this just contains data, subclass will initialize some of it
+}
+
+void ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::Direct >::InitPos()
+{
+ nRow = maParam.nRow1;
+ if (maParam.bHasHeader && maParam.bByRow)
+ ++nRow;
+ const ScColumn& rCol = rDoc.maTabs[nTab]->CreateColumnIfNotExists(nCol);
+ maCurPos = rCol.maCells.position(nRow);
+}
+
+void ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::Direct >::IncPos()
+{
+ if (maCurPos.second + 1 < maCurPos.first->size)
+ {
+ // Move within the same block.
+ ++maCurPos.second;
+ ++nRow;
+ }
+ else
+ // Move to the next block.
+ IncBlock();
+}
+
+void ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::Direct >::IncBlock()
+{
+ ++maCurPos.first;
+ maCurPos.second = 0;
+
+ nRow = maCurPos.first->position;
+}
+
+/**
+ * This class sequentially indexes non-empty cells in order, from the top of
+ * the block where the start row position is, to the bottom of the block
+ * where the end row position is. It skips all empty blocks that may be
+ * present in between.
+ *
+ * The index value is an offset from the first element of the first block
+ * disregarding all empty cell blocks.
+ */
+class ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::Direct >::NonEmptyCellIndexer
+{
+ typedef std::map<size_t, sc::CellStoreType::const_iterator> BlockMapType;
+
+ BlockMapType maBlockMap;
+
+ const sc::CellStoreType& mrCells;
+
+ size_t mnLowIndex;
+ size_t mnHighIndex;
+
+ bool mbValid;
+
+public:
+ /**
+ * @param rCells cell storage container
+ * @param nStartRow logical start row position
+ * @param nEndRow logical end row position, inclusive.
+ */
+ NonEmptyCellIndexer(
+ const sc::CellStoreType& rCells, SCROW nStartRow, SCROW nEndRow) :
+ mrCells(rCells), mnLowIndex(0), mnHighIndex(0), mbValid(true)
+ {
+ // Find the low position.
+
+ sc::CellStoreType::const_position_type aLoPos = mrCells.position(nStartRow);
+ assert(aLoPos.first->type != sc::element_type_empty);
+ assert(aLoPos.first != rCells.end());
+
+ SCROW nFirstRow = aLoPos.first->position;
+ SCROW nLastRow = aLoPos.first->position + aLoPos.first->size - 1;
+
+ if (nFirstRow > nEndRow)
+ {
+ // Both start and end row positions are within the leading skipped
+ // blocks.
+ mbValid = false;
+ return;
+ }
+
+ // Calculate the index of the low position.
+ if (nFirstRow < nStartRow)
+ mnLowIndex = nStartRow - nFirstRow;
+ else
+ {
+ // Start row is within the skipped block(s). Set it to the first
+ // element of the low block.
+ mnLowIndex = 0;
+ }
+
+ if (nEndRow < nLastRow)
+ {
+ assert(nEndRow >= nFirstRow);
+ mnHighIndex = nEndRow - nFirstRow;
+
+ maBlockMap.emplace(aLoPos.first->size, aLoPos.first);
+ return;
+ }
+
+ // Find the high position.
+
+ sc::CellStoreType::const_position_type aHiPos = mrCells.position(aLoPos.first, nEndRow);
+ if (aHiPos.first->type == sc::element_type_empty)
+ {
+ // Move to the last position of the previous block.
+ decBlock(aHiPos);
+
+ // Check the row position of the end of the previous block, and make sure it's valid.
+ SCROW nBlockEndRow = aHiPos.first->position + aHiPos.first->size - 1;
+ if (nBlockEndRow < nStartRow)
+ {
+ mbValid = false;
+ return;
+ }
+ }
+
+ // Tag the start and end blocks, and all blocks in between in order
+ // but skip all empty blocks.
+
+ size_t nPos = 0;
+ sc::CellStoreType::const_iterator itBlk = aLoPos.first;
+ while (itBlk != aHiPos.first)
+ {
+ if (itBlk->type == sc::element_type_empty)
+ {
+ ++itBlk;
+ continue;
+ }
+
+ nPos += itBlk->size;
+ maBlockMap.emplace(nPos, itBlk);
+ ++itBlk;
+
+ if (itBlk->type == sc::element_type_empty)
+ ++itBlk;
+
+ assert(itBlk != mrCells.end());
+ }
+
+ assert(itBlk == aHiPos.first);
+ nPos += itBlk->size;
+ maBlockMap.emplace(nPos, itBlk);
+
+ // Calculate the high index.
+ BlockMapType::const_reverse_iterator ri = maBlockMap.rbegin();
+ mnHighIndex = ri->first;
+ mnHighIndex -= ri->second->size;
+ mnHighIndex += aHiPos.second;
+ }
+
+ sc::CellStoreType::const_position_type getPosition( size_t nIndex ) const
+ {
+ assert(mbValid);
+ assert(mnLowIndex <= nIndex);
+ assert(nIndex <= mnHighIndex);
+
+ sc::CellStoreType::const_position_type aRet(mrCells.end(), 0);
+
+ BlockMapType::const_iterator it = maBlockMap.upper_bound(nIndex);
+ if (it == maBlockMap.end())
+ return aRet;
+
+ sc::CellStoreType::const_iterator itBlk = it->second;
+ size_t nBlkIndex = it->first - itBlk->size; // index of the first element of the block.
+ assert(nBlkIndex <= nIndex);
+ assert(nIndex < it->first);
+
+ size_t nOffset = nIndex - nBlkIndex;
+ aRet.first = itBlk;
+ aRet.second = nOffset;
+ return aRet;
+ }
+
+ BinarySearchCellType getCell( size_t nIndex ) const
+ {
+ BinarySearchCellType aRet;
+ aRet.second = -1;
+
+ sc::CellStoreType::const_position_type aPos = getPosition(nIndex);
+ if (aPos.first == mrCells.end())
+ return aRet;
+
+ aRet.first = sc::toRefCell(aPos.first, aPos.second);
+ aRet.second = aPos.first->position + aPos.second;
+ return aRet;
+ }
+
+ size_t getLowIndex() const { return mnLowIndex; }
+
+ size_t getHighIndex() const { return mnHighIndex; }
+
+ bool isValid() const { return mbValid; }
+};
+
+ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::Direct >::NonEmptyCellIndexer
+ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::Direct >::MakeBinarySearchIndexer(
+ const sc::CellStoreType& rCells, SCROW nStartRow, SCROW nEndRow )
+{
+ return NonEmptyCellIndexer(rCells, nStartRow, nEndRow);
+}
+
+// Sorted access using ScSortedRangeCache.
+
+ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::SortedCache >
+ ::ScQueryCellIteratorAccessSpecific( ScDocument& rDocument,
+ ScInterpreterContext& rContext, const ScQueryParam& rParam )
+ : maParam( rParam )
+ , rDoc( rDocument )
+ , mrContext( rContext )
+{
+ // coverity[uninit_member] - this just contains data, subclass will initialize some of it
+}
+
+void ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::SortedCache >::SetSortedRangeCache(
+ const ScSortedRangeCache& cache)
+{
+ sortedCache = &cache;
+}
+
+// The idea in iterating using the sorted cache is that the iteration is instead done
+// over indexes of the sorted cache (which is a stable sort of the cell contents) in the range
+// that fits the query condition and then that is mapped to rows. This will result in iterating
+// over only matching rows in their sorted order (and for equal rows in their row order).
+void ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::SortedCache >::InitPosStart()
+{
+ ScRange aSortedRangeRange( nCol, maParam.nRow1, nTab, nCol, maParam.nRow2, nTab );
+ // We want all matching values first in the sort order,
+ SetSortedRangeCache( rDoc.GetSortedRangeCache( aSortedRangeRange, maParam, &mrContext ));
+ // InitPosFinish() needs to be called after this, ScQueryCellIteratorBase::InitPos()
+ // will handle that
+}
+
+void ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::SortedCache >::InitPosFinish(
+ SCROW beforeRow, SCROW lastRow )
+{
+ pColumn = &rDoc.maTabs[nTab]->CreateColumnIfNotExists(nCol);
+ if(lastRow >= 0)
+ {
+ sortedCachePos = beforeRow >= 0 ? sortedCache->indexForRow(beforeRow) + 1 : 0;
+ sortedCachePosLast = sortedCache->indexForRow(lastRow);
+ if(sortedCachePos <= sortedCachePosLast)
+ {
+ nRow = sortedCache->rowForIndex(sortedCachePos);
+ maCurPos = pColumn->maCells.position(nRow);
+ return;
+ }
+ }
+ // No rows, set to end.
+ sortedCachePos = sortedCachePosLast = 0;
+ maCurPos.first = pColumn->maCells.end();
+ maCurPos.second = 0;
+}
+
+template<bool fast>
+bool ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::SortedCache >::IncPosImpl()
+{
+ if(sortedCachePos < sortedCachePosLast)
+ {
+ ++sortedCachePos;
+ nRow = sortedCache->rowForIndex(sortedCachePos);
+#ifndef DBG_UTIL
+ if constexpr (!fast)
+#endif
+ {
+ // Avoid mdds position() call if row is in the same block.
+ if(maCurPos.first != pColumn->maCells.end() && o3tl::make_unsigned(nRow) >= maCurPos.first->position
+ && o3tl::make_unsigned(nRow) < maCurPos.first->position + maCurPos.first->size)
+ maCurPos.second = nRow - maCurPos.first->position;
+ else
+ maCurPos = pColumn->maCells.position(nRow);
+ }
+ return true;
+ }
+ else
+ {
+ // This will make PerformQuery() go to next column.
+ // Necessary even in fast mode, as GetNext() will call GetThis() in this case.
+ maCurPos.first = pColumn->maCells.end();
+ maCurPos.second = 0;
+ return false;
+ }
+}
+
+// Helper that allows binary search of unsorted cells using ScSortedRangeCache.
+// Rows in the given range are kept in a sorted vector and that vector is binary-searched.
+class ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::SortedCache >::SortedCacheIndexer
+{
+ std::vector<SCROW> mSortedRowsCopy;
+ const std::vector<SCROW>& mSortedRows;
+ const sc::CellStoreType& mCells;
+ size_t mLowIndex;
+ size_t mHighIndex;
+ bool mValid;
+
+ const std::vector<SCROW>& makeSortedRows( const ScSortedRangeCache* cache, SCROW startRow, SCROW endRow )
+ {
+ // Keep a reference to rows from the cache if equal, otherwise make a copy.
+ if(startRow == cache->getRange().aStart.Row() && endRow == cache->getRange().aEnd.Row())
+ return cache->sortedRows();
+ else
+ {
+ mSortedRowsCopy.reserve( cache->sortedRows().size());
+ for( SCROW row : cache->sortedRows())
+ if( row >= startRow && row <= endRow )
+ mSortedRowsCopy.emplace_back( row );
+ return mSortedRowsCopy;
+ }
+ }
+
+public:
+ SortedCacheIndexer( const sc::CellStoreType& cells, SCROW startRow, SCROW endRow,
+ const ScSortedRangeCache* cache )
+ : mSortedRows( makeSortedRows( cache, startRow, endRow ))
+ , mCells( cells )
+ , mValid( false )
+ {
+ if(mSortedRows.empty())
+ {
+ // coverity[uninit_member] - these are initialized only if valid
+ return;
+ }
+ mLowIndex = 0;
+ mHighIndex = mSortedRows.size() - 1;
+ mValid = true;
+ }
+
+ sc::CellStoreType::const_position_type getPosition( size_t nIndex ) const
+ {
+ // TODO optimize?
+ SCROW row = mSortedRows[ nIndex ];
+ return mCells.position(row);
+ }
+
+ BinarySearchCellType getCell( size_t nIndex ) const
+ {
+ BinarySearchCellType aRet;
+ aRet.second = -1;
+
+ sc::CellStoreType::const_position_type aPos = getPosition(nIndex);
+ if (aPos.first == mCells.end())
+ return aRet;
+
+ aRet.first = sc::toRefCell(aPos.first, aPos.second);
+ aRet.second = aPos.first->position + aPos.second;
+ return aRet;
+ }
+
+ size_t getLowIndex() const { return mLowIndex; }
+
+ size_t getHighIndex() const { return mHighIndex; }
+
+ bool isValid() const { return mValid; }
+};
+
+ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::SortedCache >::SortedCacheIndexer
+ScQueryCellIteratorAccessSpecific< ScQueryCellIteratorAccess::SortedCache >::MakeBinarySearchIndexer(
+ const sc::CellStoreType& rCells, SCROW nStartRow, SCROW nEndRow)
+{
+ return SortedCacheIndexer(rCells, nStartRow, nEndRow, sortedCache);
+}
+
+static bool CanBeUsedForSorterCache(ScDocument& /*rDoc*/, const ScQueryParam& /*rParam*/,
+ SCTAB /*nTab*/, const ScFormulaCell* /*cell*/, const ScComplexRefData* /*refData*/,
+ ScInterpreterContext& /*context*/)
+{
+#if 1
+ /* TODO: tdf#151958 broken by string query of binary search on sorted
+ * cache, use the direct query instead for releases and fix SortedCache
+ * implementation after. Not only COUNTIF() is broken, but also COUNTIFS(),
+ * and maybe lcl_LookupQuery() for VLOOKUP() etc. as well. Just disable
+ * this for now.
+ * Can't just return false because below would be unreachable code. Can't
+ * just #if/#else/#endif either because parameters would be unused. Crap
+ * this and comment out parameter names. */
+ return false;
+#else
+ if(!rParam.GetEntry(0).bDoQuery || rParam.GetEntry(1).bDoQuery
+ || rParam.GetEntry(0).GetQueryItems().size() != 1 )
+ return false;
+ if(rParam.eSearchType != utl::SearchParam::SearchType::Normal)
+ return false;
+ if(rParam.GetEntry(0).GetQueryItem().meType != ScQueryEntry::ByValue
+ && rParam.GetEntry(0).GetQueryItem().meType != ScQueryEntry::ByString)
+ return false;
+ if(!rParam.bByRow)
+ return false;
+ if(rParam.bHasHeader)
+ return false;
+ if(rParam.mbRangeLookup)
+ return false;
+ if(rParam.GetEntry(0).GetQueryItem().meType == ScQueryEntry::ByString
+ && !ScQueryEvaluator::isMatchWholeCell(rDoc, rParam.GetEntry(0).eOp))
+ return false; // substring matching cannot be sorted
+ if(rParam.GetEntry(0).eOp != SC_LESS && rParam.GetEntry(0).eOp != SC_LESS_EQUAL
+ && rParam.GetEntry(0).eOp != SC_GREATER && rParam.GetEntry(0).eOp != SC_GREATER_EQUAL
+ && rParam.GetEntry(0).eOp != SC_EQUAL)
+ return false;
+ // For unittests allow inefficient caching, in order for the code to be checked.
+ static bool inUnitTest = getenv("LO_TESTNAME") != nullptr;
+ if(refData == nullptr || refData->Ref1.IsRowRel() || refData->Ref2.IsRowRel())
+ {
+ // If this is not a range, then a cache is not worth it. If rows are relative, then each
+ // computation will use a different area, so the cache wouldn't be reused. Tab/cols are
+ // not a problem, because formula group computations are done for the same tab/col.
+ if(!inUnitTest)
+ return false;
+ }
+ if(rParam.nRow2 - rParam.nRow1 < 10)
+ {
+ if(!inUnitTest)
+ return false;
+ }
+ if( !cell )
+ return false;
+ if( !cell->GetCellGroup() || cell->GetCellGroup()->mnLength < 10 )
+ {
+ if(!inUnitTest)
+ return false;
+ }
+ // Check that all the relevant caches would be valid (may not be the case when mixing
+ // numeric and string cells for ByValue lookups).
+ for(SCCOL col : rDoc.GetAllocatedColumnsRange(nTab, rParam.nCol1, rParam.nCol2))
+ {
+ ScRange aSortedRangeRange( col, rParam.nRow1, nTab, col, rParam.nRow2, nTab);
+ if( aSortedRangeRange.Contains( cell->aPos ))
+ return false; // self-referencing, can't create cache
+ ScSortedRangeCache& cache = rDoc.GetSortedRangeCache( aSortedRangeRange, rParam, &context );
+ if(!cache.isValid())
+ return false;
+ }
+ return true;
+#endif
+}
+
+// Generic query implementation.
+
+bool ScQueryCellIteratorTypeSpecific< ScQueryCellIteratorType::Generic >::HandleItemFound()
+{
+ getThisResult = true;
+ return true; // Return from PerformQuery().
+}
+
+template< ScQueryCellIteratorAccess accessType >
+bool ScQueryCellIterator< accessType >::GetThis()
+{
+ getThisResult = false;
+ PerformQuery();
+ return getThisResult;
+}
+
+template< ScQueryCellIteratorAccess accessType >
+bool ScQueryCellIterator< accessType >::GetFirst()
+{
+ assert(nTab < rDoc.GetTableCount() && "index out of bounds, FIX IT");
+ nCol = maParam.nCol1;
+ InitPos();
+ return GetThis();
+}
+
+template< ScQueryCellIteratorAccess accessType >
+bool ScQueryCellIterator< accessType >::GetNext()
+{
+ IncPos();
+ if ( nStopOnMismatch )
+ nStopOnMismatch = nStopOnMismatchEnabled;
+ if ( nTestEqualCondition )
+ nTestEqualCondition = nTestEqualConditionEnabled;
+ return GetThis();
+}
+
+template<>
+bool ScQueryCellIterator< ScQueryCellIteratorAccess::SortedCache >::GetNext()
+{
+ assert( !nStopOnMismatch );
+ assert( !nTestEqualCondition );
+ // When searching using sorted cache, we should always find cells that match,
+ // because InitPos()/IncPos() select only such rows, so skip GetThis() (and thus
+ // the somewhat expensive PerformQuery) as long as we're not at the end
+ // of a column. As an optimization IncPosFast() returns true if not at the end,
+ // in which case in non-DBG_UTIL mode it doesn't even bother to set maCurPos.
+ if( IncPosFast())
+ {
+#ifdef DBG_UTIL
+ assert(GetThis());
+#endif
+ return true;
+ }
+ return GetThis();
+}
+
+bool ScQueryCellIteratorSortedCache::CanBeUsed(ScDocument& rDoc, const ScQueryParam& rParam,
+ SCTAB nTab, const ScFormulaCell* cell, const ScComplexRefData* refData,
+ ScInterpreterContext& context)
+{
+ return CanBeUsedForSorterCache(rDoc, rParam, nTab, cell, refData, context);
+}
+
+// Countifs implementation.
+
+bool ScQueryCellIteratorTypeSpecific< ScQueryCellIteratorType::CountIf >::HandleItemFound()
+{
+ ++countIfCount;
+ return false; // Continue searching.
+}
+
+template< ScQueryCellIteratorAccess accessType >
+sal_uInt64 ScCountIfCellIterator< accessType >::GetCount()
+{
+ // Keep Entry.nField in iterator on column change
+ SetAdvanceQueryParamEntryField( true );
+ assert(nTab < rDoc.GetTableCount() && "try to access index out of bounds, FIX IT");
+ maParam.nCol1 = rDoc.ClampToAllocatedColumns(nTab, maParam.nCol1);
+ maParam.nCol2 = rDoc.ClampToAllocatedColumns(nTab, maParam.nCol2);
+ nCol = maParam.nCol1;
+ InitPos();
+ countIfCount = 0;
+ PerformQuery();
+ return countIfCount;
+}
+
+
+bool ScCountIfCellIteratorSortedCache::CanBeUsed(ScDocument& rDoc, const ScQueryParam& rParam,
+ SCTAB nTab, const ScFormulaCell* cell, const ScComplexRefData* refData,
+ ScInterpreterContext& context)
+{
+ return CanBeUsedForSorterCache(rDoc, rParam, nTab, cell, refData, context);
+}
+
+template<>
+sal_uInt64 ScCountIfCellIterator< ScQueryCellIteratorAccess::SortedCache >::GetCount()
+{
+ // Keep Entry.nField in iterator on column change
+ SetAdvanceQueryParamEntryField( true );
+ assert(nTab < rDoc.GetTableCount() && "try to access index out of bounds, FIX IT");
+ sal_uInt64 count = 0;
+ // Each column must be sorted separately.
+ for(SCCOL col : rDoc.GetAllocatedColumnsRange(nTab, maParam.nCol1, maParam.nCol2))
+ {
+ nCol = col;
+ nRow = maParam.nRow1;
+ ScRange aSortedRangeRange( col, maParam.nRow1, nTab, col, maParam.nRow2, nTab);
+ ScQueryOp& op = maParam.GetEntry(0).eOp;
+ SetSortedRangeCache( rDoc.GetSortedRangeCache( aSortedRangeRange, maParam, &mrContext ));
+ if( op == SC_EQUAL )
+ {
+ // BinarySearch() searches for the last item that matches. Therefore first
+ // find the last non-matching position using SC_LESS and then find the last
+ // matching position using SC_EQUAL.
+ ScQueryOp saveOp = op;
+ op = SC_LESS;
+ if( BinarySearch( nCol, true ))
+ {
+ op = saveOp; // back to SC_EQUAL
+ size_t lastNonMatching = sortedCache->indexForRow(nRow);
+ if( BinarySearch( nCol ))
+ {
+ size_t lastMatching = sortedCache->indexForRow(nRow);
+ assert(lastMatching >= lastNonMatching);
+ count += lastMatching - lastNonMatching;
+ }
+ else
+ {
+ // BinarySearch() should at least find the same result as the SC_LESS
+ // call, so this should not happen.
+ assert(false);
+ }
+ }
+ else
+ {
+ // BinarySearch() returning false means that all values are larger,
+ // so try to find matching ones and count those up to and including
+ // the found one.
+ op = saveOp; // back to SC_EQUAL
+ if( BinarySearch( nCol ))
+ {
+ size_t lastMatching = sortedCache->indexForRow(nRow) + 1;
+ count += lastMatching;
+ }
+ else if( maParam.GetEntry(0).GetQueryItem().mbMatchEmpty
+ && rDoc.IsEmptyData(col, maParam.nRow1, col, maParam.nRow2, nTab))
+ {
+ // BinarySearch() returns false in case it's all empty data,
+ // handle that specially.
+ count += maParam.nRow2 - maParam.nRow1 + 1;
+ }
+ }
+ }
+ else
+ {
+ // BinarySearch() searches for the last item that matches. Therefore everything
+ // up to and including the found row matches the condition.
+ if( BinarySearch( nCol ))
+ count += sortedCache->indexForRow(nRow) + 1;
+ }
+ }
+ if( maParam.GetEntry(0).GetQueryItem().mbMatchEmpty
+ && maParam.nCol2 >= rDoc.GetAllocatedColumnsCount( nTab ))
+ {
+ count += (maParam.nCol2 - rDoc.GetAllocatedColumnsCount( nTab ))
+ * ( maParam.nRow2 - maParam.nRow1 + 1 );
+ }
+ return count;
+}
+
+template class ScQueryCellIterator< ScQueryCellIteratorAccess::Direct >;
+template class ScQueryCellIterator< ScQueryCellIteratorAccess::SortedCache >;
+template class ScCountIfCellIterator< ScQueryCellIteratorAccess::Direct >;
+template class ScCountIfCellIterator< ScQueryCellIteratorAccess::SortedCache >;
+
+// gcc for some reason needs these too
+template class ScQueryCellIteratorBase< ScQueryCellIteratorAccess::Direct, ScQueryCellIteratorType::Generic >;
+template class ScQueryCellIteratorBase< ScQueryCellIteratorAccess::SortedCache, ScQueryCellIteratorType::Generic >;
+template class ScQueryCellIteratorBase< ScQueryCellIteratorAccess::Direct, ScQueryCellIteratorType::CountIf >;
+template class ScQueryCellIteratorBase< ScQueryCellIteratorAccess::SortedCache, ScQueryCellIteratorType::CountIf >;
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/refupdatecontext.cxx b/sc/source/core/data/refupdatecontext.cxx
new file mode 100644
index 000000000..8faf1f105
--- /dev/null
+++ b/sc/source/core/data/refupdatecontext.cxx
@@ -0,0 +1,139 @@
+/* -*- 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 <refupdatecontext.hxx>
+#include <algorithm>
+#include <clipparam.hxx>
+#include <mtvelements.hxx>
+
+namespace sc {
+
+void UpdatedRangeNames::setUpdatedName(SCTAB nTab, sal_uInt16 nIndex)
+{
+ // Map anything <-1 to global names. Unless we really want to come up with
+ // some classification there...
+ if (nTab < -1)
+ nTab = -1;
+
+ UpdatedNamesType::iterator it = maUpdatedNames.find(nTab);
+ if (it == maUpdatedNames.end())
+ {
+ // Insert a new container for this sheet index.
+ NameIndicesType aIndices;
+ std::pair<UpdatedNamesType::iterator,bool> r =
+ maUpdatedNames.emplace( nTab, aIndices);
+
+ if (!r.second)
+ // Insertion failed for whatever reason.
+ return;
+
+ it = r.first;
+ }
+
+ NameIndicesType& rIndices = it->second;
+ rIndices.insert(nIndex);
+}
+
+bool UpdatedRangeNames::isNameUpdated(SCTAB nTab, sal_uInt16 nIndex) const
+{
+ UpdatedNamesType::const_iterator it = maUpdatedNames.find(nTab);
+ if (it == maUpdatedNames.end())
+ return false;
+
+ const NameIndicesType& rIndices = it->second;
+ return rIndices.count(nIndex) > 0;
+}
+
+UpdatedRangeNames::NameIndicesType UpdatedRangeNames::getUpdatedNames(SCTAB nTab) const
+{
+ UpdatedNamesType::const_iterator it = maUpdatedNames.find(nTab);
+ if (it == maUpdatedNames.end())
+ return NameIndicesType();
+ return it->second;
+}
+
+bool UpdatedRangeNames::isEmpty(SCTAB nTab) const
+{
+ UpdatedNamesType::const_iterator it = maUpdatedNames.find(nTab);
+ return it == maUpdatedNames.end();
+}
+
+RefUpdateContext::RefUpdateContext(ScDocument& rDoc, ScDocument* pClipdoc)
+ : mrDoc(rDoc)
+ , meMode(URM_INSDEL)
+ , mbTransposed(pClipdoc != nullptr && pClipdoc->GetClipParam().isTransposed())
+ , mnColDelta(0)
+ , mnRowDelta(0)
+ , mnTabDelta(0)
+ , mpBlockPos(nullptr)
+{
+ assert((pClipdoc == nullptr || pClipdoc->IsClipboard()) && "only nullptr or clipdoc allowed");
+}
+
+bool RefUpdateContext::isInserted() const
+{
+ return (meMode == URM_INSDEL) && (mnColDelta > 0 || mnRowDelta > 0 || mnTabDelta > 0);
+}
+
+bool RefUpdateContext::isDeleted() const
+{
+ return (meMode == URM_INSDEL) && (mnColDelta < 0 || mnRowDelta < 0 || mnTabDelta < 0);
+}
+
+void RefUpdateContext::setBlockPositionReference( ColumnBlockPositionSet* blockPos )
+{
+ mpBlockPos = blockPos;
+}
+
+ColumnBlockPosition* RefUpdateContext::getBlockPosition(SCTAB nTab, SCCOL nCol)
+{
+ return mpBlockPos ? mpBlockPos->getBlockPosition(nTab, nCol) : nullptr;
+}
+
+RefUpdateResult::RefUpdateResult() : mbValueChanged(false), mbReferenceModified(false), mbNameModified(false) {}
+
+RefUpdateInsertTabContext::RefUpdateInsertTabContext(ScDocument& rDoc, SCTAB nInsertPos, SCTAB nSheets) :
+ mrDoc(rDoc), mnInsertPos(nInsertPos), mnSheets(nSheets) {}
+
+RefUpdateDeleteTabContext::RefUpdateDeleteTabContext(ScDocument& rDoc, SCTAB nDeletePos, SCTAB nSheets) :
+ mrDoc(rDoc), mnDeletePos(nDeletePos), mnSheets(nSheets) {}
+
+RefUpdateMoveTabContext::RefUpdateMoveTabContext(ScDocument& rDoc, SCTAB nOldPos, SCTAB nNewPos) :
+ mrDoc(rDoc), mnOldPos(nOldPos), mnNewPos(nNewPos) {}
+
+SCTAB RefUpdateMoveTabContext::getNewTab(SCTAB nOldTab) const
+{
+ // Sheets below the lower bound or above the upper bound will not change.
+ SCTAB nLowerBound = std::min(mnOldPos, mnNewPos);
+ SCTAB nUpperBound = std::max(mnOldPos, mnNewPos);
+
+ if (nOldTab < nLowerBound || nUpperBound < nOldTab)
+ // Outside the boundary. Nothing to adjust.
+ return nOldTab;
+
+ if (nOldTab == mnOldPos)
+ return mnNewPos;
+
+ // It's somewhere in between.
+ if (mnOldPos < mnNewPos)
+ {
+ // Moving a sheet to the right. The rest of the sheets shifts to the left.
+ return nOldTab - 1;
+ }
+
+ // Moving a sheet to the left. The rest of the sheets shifts to the right.
+ return nOldTab + 1;
+}
+
+SetFormulaDirtyContext::SetFormulaDirtyContext() :
+ mnTabDeletedStart(-1), mnTabDeletedEnd(-1), mbClearTabDeletedFlag(false) {}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/rowheightcontext.cxx b/sc/source/core/data/rowheightcontext.cxx
new file mode 100644
index 000000000..c5f615c66
--- /dev/null
+++ b/sc/source/core/data/rowheightcontext.cxx
@@ -0,0 +1,38 @@
+/* -*- 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 <rowheightcontext.hxx>
+
+namespace sc {
+
+RowHeightContext::RowHeightContext(SCROW nMaxRow,
+ double fPPTX, double fPPTY, const Fraction& rZoomX, const Fraction& rZoomY,
+ OutputDevice* pOutDev ) :
+ maHeights(nMaxRow, 0),
+ mfPPTX(fPPTX), mfPPTY(fPPTY),
+ maZoomX(rZoomX), maZoomY(rZoomY),
+ mpOutDev(pOutDev),
+ mnExtraHeight(0),
+ mbForceAutoSize(false) {}
+
+RowHeightContext::~RowHeightContext() {}
+
+void RowHeightContext::setExtraHeight( sal_uInt16 nH )
+{
+ mnExtraHeight = nH;
+}
+
+void RowHeightContext::setForceAutoSize( bool b )
+{
+ mbForceAutoSize = b;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/segmenttree.cxx b/sc/source/core/data/segmenttree.cxx
new file mode 100644
index 000000000..aa10d3254
--- /dev/null
+++ b/sc/source/core/data/segmenttree.cxx
@@ -0,0 +1,711 @@
+/* -*- 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 <segmenttree.hxx>
+#include <o3tl/safeint.hxx>
+#include <mdds/flat_segment_tree.hpp>
+#include <sal/log.hxx>
+#include <algorithm>
+#include <limits>
+#include <string_view>
+#include <global.hxx>
+
+using ::std::numeric_limits;
+
+namespace {
+
+template<typename ValueType_, typename ExtValueType_ = ValueType_>
+class ScFlatSegmentsImpl
+{
+public:
+ typedef ValueType_ ValueType;
+ typedef ExtValueType_ ExtValueType;
+
+ struct RangeData
+ {
+ SCCOLROW mnPos1;
+ SCCOLROW mnPos2;
+ ValueType mnValue;
+ };
+
+ ScFlatSegmentsImpl(SCCOLROW nMax, ValueType nDefault);
+ ScFlatSegmentsImpl(const ScFlatSegmentsImpl& r);
+
+ bool setValue(SCCOLROW nPos1, SCCOLROW nPos2, ValueType nValue);
+ void setValueIf(SCCOLROW nPos1, SCCOLROW nPos2, ValueType nValue, const std::function<bool(ValueType)>& rPredicate);
+ ValueType getValue(SCCOLROW nPos);
+ sal_uInt64 getSumValue(SCCOLROW nPos1, SCCOLROW nPos2);
+ bool getRangeData(SCCOLROW nPos, RangeData& rData);
+ bool getRangeDataLeaf(SCCOLROW nPos, RangeData& rData);
+ void removeSegment(SCCOLROW nPos1, SCCOLROW nPos2);
+ void insertSegment(SCCOLROW nPos, SCCOLROW nSize, bool bSkipStartBoundary);
+
+ SCCOLROW findLastTrue(ValueType nValue) const;
+
+ // range iteration
+ bool getFirst(RangeData& rData);
+ bool getNext(RangeData& rData);
+
+ void enableTreeSearch(bool b)
+ {
+ mbTreeSearchEnabled = b;
+ }
+
+ void makeReady();
+
+private:
+ typedef ::mdds::flat_segment_tree<SCCOLROW, ValueType> fst_type;
+ fst_type maSegments;
+ typename fst_type::const_iterator maItr;
+
+ bool mbTreeSearchEnabled:1;
+};
+
+}
+
+template<typename ValueType_, typename ExtValueType_>
+ScFlatSegmentsImpl<ValueType_, ExtValueType_>::ScFlatSegmentsImpl(SCCOLROW nMax, ValueType nDefault) :
+ maSegments(0, nMax+1, nDefault),
+ mbTreeSearchEnabled(true)
+{
+}
+
+template<typename ValueType_, typename ExtValueType_>
+ScFlatSegmentsImpl<ValueType_, ExtValueType_>::ScFlatSegmentsImpl(const ScFlatSegmentsImpl<ValueType_, ExtValueType_>& r) :
+ maSegments(r.maSegments),
+ mbTreeSearchEnabled(r.mbTreeSearchEnabled)
+{
+}
+
+template<typename ValueType_, typename ExtValueType_>
+bool ScFlatSegmentsImpl<ValueType_, ExtValueType_>::setValue(SCCOLROW nPos1, SCCOLROW nPos2, ValueType nValue)
+{
+ ::std::pair<typename fst_type::const_iterator, bool> ret;
+ ret = maSegments.insert(maItr, nPos1, nPos2+1, nValue);
+ maItr = ret.first;
+ return ret.second;
+}
+
+template<typename ValueType_, typename ExtValueType_>
+void ScFlatSegmentsImpl<ValueType_, ExtValueType_>::setValueIf(SCCOLROW nPos1, SCCOLROW nPos2,
+ ValueType nValue, const std::function<bool(ValueType)>& rPredicate)
+{
+ SCCOLROW nCurrentStartRow = nPos1;
+ while (nCurrentStartRow <= nPos2)
+ {
+ RangeData aRangeData;
+ getRangeData(nCurrentStartRow, aRangeData);
+ if (rPredicate(aRangeData.mnValue))
+ {
+ // set value from current iteration point on, til end of range.
+ // Note that aRangeData may well contain much lower values for nPos1
+ setValue(nCurrentStartRow, std::min<SCCOLROW>(nPos2, aRangeData.mnPos2), nValue);
+ }
+
+ // even if nPos2 is bigger than nPos2 this should terminate the loop
+ nCurrentStartRow = aRangeData.mnPos2 + 1;
+ }
+}
+
+template<typename ValueType_, typename ExtValueType_>
+typename ScFlatSegmentsImpl<ValueType_, ExtValueType_>::ValueType ScFlatSegmentsImpl<ValueType_, ExtValueType_>::getValue(SCCOLROW nPos)
+{
+ ValueType nValue = 0;
+ if (!mbTreeSearchEnabled)
+ {
+ maSegments.search(nPos, nValue);
+ return nValue;
+ }
+
+ if (!maSegments.is_tree_valid())
+ {
+ assert(!ScGlobal::bThreadedGroupCalcInProgress);
+ maSegments.build_tree();
+ }
+
+ maSegments.search_tree(nPos, nValue);
+ return nValue;
+}
+
+template<typename ValueType_, typename ExtValueType_>
+sal_uInt64 ScFlatSegmentsImpl<ValueType_, ExtValueType_>::getSumValue(SCCOLROW nPos1, SCCOLROW nPos2)
+{
+ if (mbTreeSearchEnabled)
+ {
+
+ if (!maSegments.is_tree_valid())
+ {
+ assert(!ScGlobal::bThreadedGroupCalcInProgress);
+ maSegments.build_tree();
+ }
+
+ RangeData aData;
+ auto [it, found] = maSegments.search_tree(nPos1, aData.mnValue, &aData.mnPos1, &aData.mnPos2);
+ if (!found)
+ return 0;
+ aData.mnPos2 = aData.mnPos2-1; // end point is not inclusive.
+
+ sal_uInt64 nValue = 0;
+
+ SCROW nCurPos = nPos1;
+ SCROW nEndPos = aData.mnPos2;
+ while (nEndPos <= nPos2)
+ {
+ sal_uInt64 nRes;
+ if (o3tl::checked_multiply<sal_uInt64>(aData.mnValue, nEndPos - nCurPos + 1, nRes))
+ {
+ SAL_WARN("sc.core", "row height overflow");
+ nRes = SAL_MAX_INT64;
+ }
+ nValue = o3tl::saturating_add(nValue, nRes);
+ nCurPos = nEndPos + 1;
+ auto itPair = maSegments.search(it, nCurPos, aData.mnValue, &aData.mnPos1, &aData.mnPos2);
+ if (!itPair.second)
+ break;
+ it = itPair.first;
+ aData.mnPos2 = aData.mnPos2-1; // end point is not inclusive.
+ nEndPos = aData.mnPos2;
+ }
+ if (nCurPos <= nPos2)
+ {
+ nEndPos = ::std::min(nEndPos, nPos2);
+ sal_uInt64 nRes;
+ if (o3tl::checked_multiply<sal_uInt64>(aData.mnValue, nEndPos - nCurPos + 1, nRes))
+ {
+ SAL_WARN("sc.core", "row height overflow");
+ nRes = SAL_MAX_INT64;
+ }
+ nValue = o3tl::saturating_add(nValue, nRes);
+ }
+ return nValue;
+ }
+ else
+ {
+ RangeData aData;
+ if (!getRangeDataLeaf(nPos1, aData))
+ return 0;
+
+ sal_uInt64 nValue = 0;
+
+ SCROW nCurPos = nPos1;
+ SCROW nEndPos = aData.mnPos2;
+ while (nEndPos <= nPos2)
+ {
+ sal_uInt64 nRes;
+ if (o3tl::checked_multiply<sal_uInt64>(aData.mnValue, nEndPos - nCurPos + 1, nRes))
+ {
+ SAL_WARN("sc.core", "row height overflow");
+ nRes = SAL_MAX_INT64;
+ }
+ nValue = o3tl::saturating_add(nValue, nRes);
+ nCurPos = nEndPos + 1;
+ if (!getRangeDataLeaf(nCurPos, aData))
+ break;
+
+ nEndPos = aData.mnPos2;
+ }
+ if (nCurPos <= nPos2)
+ {
+ nEndPos = ::std::min(nEndPos, nPos2);
+ sal_uInt64 nRes;
+ if (o3tl::checked_multiply<sal_uInt64>(aData.mnValue, nEndPos - nCurPos + 1, nRes))
+ {
+ SAL_WARN("sc.core", "row height overflow");
+ nRes = SAL_MAX_INT64;
+ }
+ nValue = o3tl::saturating_add(nValue, nRes);
+ }
+ return nValue;
+ }
+}
+
+template<typename ValueType_, typename ExtValueType_>
+bool ScFlatSegmentsImpl<ValueType_, ExtValueType_>::getRangeData(SCCOLROW nPos, RangeData& rData)
+{
+ if (!mbTreeSearchEnabled)
+ return getRangeDataLeaf(nPos, rData);
+
+ if (!maSegments.is_tree_valid())
+ {
+ assert(!ScGlobal::bThreadedGroupCalcInProgress);
+ maSegments.build_tree();
+ }
+
+ auto [it,found] = maSegments.search_tree(nPos, rData.mnValue, &rData.mnPos1, &rData.mnPos2);
+ if (!found)
+ return false;
+ maItr = it; // cache the iterator to speed up ForwardIterator.
+ rData.mnPos2 = rData.mnPos2-1; // end point is not inclusive.
+ return true;
+}
+
+template<typename ValueType_, typename ExtValueType_>
+bool ScFlatSegmentsImpl<ValueType_, ExtValueType_>::getRangeDataLeaf(SCCOLROW nPos, RangeData& rData)
+{
+ // Conduct leaf-node only search. Faster when searching between range insertion.
+ const ::std::pair<typename fst_type::const_iterator, bool> &ret =
+ maSegments.search(maItr, nPos, rData.mnValue, &rData.mnPos1, &rData.mnPos2);
+
+ if (!ret.second)
+ return false;
+
+ maItr = ret.first;
+
+ rData.mnPos2 = rData.mnPos2-1; // end point is not inclusive.
+ return true;
+}
+
+template<typename ValueType_, typename ExtValueType_>
+void ScFlatSegmentsImpl<ValueType_, ExtValueType_>::removeSegment(SCCOLROW nPos1, SCCOLROW nPos2)
+{
+ maSegments.shift_left(nPos1, nPos2);
+ maItr = maSegments.begin();
+}
+
+template<typename ValueType_, typename ExtValueType_>
+void ScFlatSegmentsImpl<ValueType_, ExtValueType_>::insertSegment(SCCOLROW nPos, SCCOLROW nSize, bool bSkipStartBoundary)
+{
+ maSegments.shift_right(nPos, nSize, bSkipStartBoundary);
+ maItr = maSegments.begin();
+}
+
+template<typename ValueType_, typename ExtValueType_>
+SCCOLROW ScFlatSegmentsImpl<ValueType_, ExtValueType_>::findLastTrue(ValueType nValue) const
+{
+ SCCOLROW nPos = numeric_limits<SCCOLROW>::max(); // position not found.
+ typename fst_type::const_reverse_iterator itr = maSegments.rbegin(), itrEnd = maSegments.rend();
+ // Note that when searching in reverse direction, we need to skip the first
+ // node, since the right-most leaf node does not store a valid value.
+ for (++itr; itr != itrEnd; ++itr)
+ {
+ if (itr->second != nValue)
+ {
+ nPos = (--itr)->first - 1;
+ break;
+ }
+ }
+ return nPos;
+}
+
+template<typename ValueType_, typename ExtValueType_>
+bool ScFlatSegmentsImpl<ValueType_, ExtValueType_>::getFirst(RangeData& rData)
+{
+ maItr = maSegments.begin();
+ return getNext(rData);
+}
+
+template<typename ValueType_, typename ExtValueType_>
+bool ScFlatSegmentsImpl<ValueType_, ExtValueType_>::getNext(RangeData& rData)
+{
+ typename fst_type::const_iterator itrEnd = maSegments.end();
+ if (maItr == itrEnd)
+ return false;
+
+ rData.mnPos1 = maItr->first;
+ rData.mnValue = maItr->second;
+
+ ++maItr;
+ if (maItr == itrEnd)
+ return false;
+
+ rData.mnPos2 = maItr->first - 1;
+ return true;
+}
+
+template<typename ValueType_, typename ExtValueType_>
+void ScFlatSegmentsImpl<ValueType_, ExtValueType_>::makeReady()
+{
+ assert(!ScGlobal::bThreadedGroupCalcInProgress);
+ if (!maSegments.is_tree_valid())
+ maSegments.build_tree();
+}
+
+class ScFlatUInt16SegmentsImpl : public ScFlatSegmentsImpl<sal_uInt16, sal_uInt32>
+{
+public:
+ explicit ScFlatUInt16SegmentsImpl(SCCOLROW nMax, sal_uInt16 nDefault) :
+ ScFlatSegmentsImpl<sal_uInt16, sal_uInt32>(nMax, nDefault)
+ {
+ }
+};
+
+class ScFlatBoolSegmentsImpl : public ScFlatSegmentsImpl<bool>
+{
+public:
+ explicit ScFlatBoolSegmentsImpl(SCCOLROW nMax) :
+ ScFlatSegmentsImpl<bool>(nMax, false)
+ {
+ }
+
+ bool setTrue(SCCOLROW nPos1, SCCOLROW nPos2);
+ bool setFalse(SCCOLROW nPos1, SCCOLROW nPos2);
+};
+
+bool ScFlatBoolSegmentsImpl::setTrue(SCCOLROW nPos1, SCCOLROW nPos2)
+{
+ return setValue(nPos1, nPos2, true);
+}
+
+bool ScFlatBoolSegmentsImpl::setFalse(SCCOLROW nPos1, SCCOLROW nPos2)
+{
+ return setValue(nPos1, nPos2, false);
+}
+
+ScFlatBoolRowSegments::ForwardIterator::ForwardIterator(ScFlatBoolRowSegments& rSegs) :
+ mrSegs(rSegs), mnCurPos(0), mnLastPos(-1), mbCurValue(false)
+{
+}
+
+bool ScFlatBoolRowSegments::ForwardIterator::getValue(SCROW nPos, bool& rVal)
+{
+ if (nPos >= mnCurPos)
+ // It can only go in a forward direction.
+ mnCurPos = nPos;
+
+ if (mnCurPos > mnLastPos)
+ {
+ // position not in the current segment. Update the current value.
+ ScFlatBoolRowSegments::RangeData aData;
+ if (!mrSegs.getRangeData(mnCurPos, aData))
+ return false;
+
+ mbCurValue = aData.mbValue;
+ mnLastPos = aData.mnRow2;
+ }
+
+ rVal = mbCurValue;
+ return true;
+}
+
+ScFlatBoolRowSegments::RangeIterator::RangeIterator(ScFlatBoolRowSegments const & rSegs) :
+ mrSegs(rSegs)
+{
+}
+
+bool ScFlatBoolRowSegments::RangeIterator::getFirst(RangeData& rRange)
+{
+ ScFlatBoolSegmentsImpl::RangeData aData;
+ if (!mrSegs.mpImpl->getFirst(aData))
+ return false;
+
+ rRange.mnRow1 = static_cast<SCROW>(aData.mnPos1);
+ rRange.mnRow2 = static_cast<SCROW>(aData.mnPos2);
+ rRange.mbValue = static_cast<bool>(aData.mnValue);
+ return true;
+}
+
+bool ScFlatBoolRowSegments::RangeIterator::getNext(RangeData& rRange)
+{
+ ScFlatBoolSegmentsImpl::RangeData aData;
+ if (!mrSegs.mpImpl->getNext(aData))
+ return false;
+
+ rRange.mnRow1 = static_cast<SCROW>(aData.mnPos1);
+ rRange.mnRow2 = static_cast<SCROW>(aData.mnPos2);
+ rRange.mbValue = static_cast<bool>(aData.mnValue);
+ return true;
+}
+
+ScFlatBoolRowSegments::ScFlatBoolRowSegments(SCROW nMaxRow) :
+ mpImpl(new ScFlatBoolSegmentsImpl(nMaxRow))
+{
+}
+
+ScFlatBoolRowSegments::ScFlatBoolRowSegments(const ScFlatBoolRowSegments& r) :
+ mpImpl(new ScFlatBoolSegmentsImpl(*r.mpImpl))
+{
+}
+
+ScFlatBoolRowSegments::~ScFlatBoolRowSegments()
+{
+}
+
+bool ScFlatBoolRowSegments::setTrue(SCROW nRow1, SCROW nRow2)
+{
+ return mpImpl->setTrue(static_cast<SCCOLROW>(nRow1), static_cast<SCCOLROW>(nRow2));
+}
+
+bool ScFlatBoolRowSegments::setFalse(SCROW nRow1, SCROW nRow2)
+{
+ return mpImpl->setFalse(static_cast<SCCOLROW>(nRow1), static_cast<SCCOLROW>(nRow2));
+}
+
+bool ScFlatBoolRowSegments::getRangeData(SCROW nRow, RangeData& rData) const
+{
+ ScFlatBoolSegmentsImpl::RangeData aData;
+ if (!mpImpl->getRangeData(static_cast<SCCOLROW>(nRow), aData))
+ return false;
+
+ rData.mbValue = aData.mnValue;
+ rData.mnRow1 = static_cast<SCROW>(aData.mnPos1);
+ rData.mnRow2 = static_cast<SCROW>(aData.mnPos2);
+ return true;
+}
+
+bool ScFlatBoolRowSegments::getRangeDataLeaf(SCROW nRow, RangeData& rData)
+{
+ ScFlatBoolSegmentsImpl::RangeData aData;
+ if (!mpImpl->getRangeDataLeaf(static_cast<SCCOLROW>(nRow), aData))
+ return false;
+
+ rData.mbValue = aData.mnValue;
+ rData.mnRow1 = static_cast<SCROW>(aData.mnPos1);
+ rData.mnRow2 = static_cast<SCROW>(aData.mnPos2);
+ return true;
+}
+
+void ScFlatBoolRowSegments::removeSegment(SCROW nRow1, SCROW nRow2)
+{
+ mpImpl->removeSegment(static_cast<SCCOLROW>(nRow1), static_cast<SCCOLROW>(nRow2));
+}
+
+void ScFlatBoolRowSegments::insertSegment(SCROW nRow, SCROW nSize)
+{
+ mpImpl->insertSegment(static_cast<SCCOLROW>(nRow), static_cast<SCCOLROW>(nSize), true/*bSkipStartBoundary*/);
+}
+
+SCROW ScFlatBoolRowSegments::findLastTrue() const
+{
+ return mpImpl->findLastTrue(false);
+}
+
+void ScFlatBoolRowSegments::makeReady()
+{
+ mpImpl->makeReady();
+}
+
+OString ScFlatBoolRowSegments::dumpAsString()
+{
+ OString aOutput;
+ OString aSegment;
+ RangeData aRange;
+ SCROW nRow = 0;
+ while (getRangeData(nRow, aRange))
+ {
+ if (!nRow)
+ aSegment = (aRange.mbValue ? std::string_view("1") : std::string_view("0")) + OString::Concat(":");
+ else
+ aSegment.clear();
+
+ aSegment += OString::number(aRange.mnRow2) + " ";
+ aOutput += aSegment;
+ nRow = aRange.mnRow2 + 1;
+ }
+
+ return aOutput;
+}
+
+ScFlatBoolColSegments::ScFlatBoolColSegments(SCCOL nMaxCol) :
+ mpImpl(new ScFlatBoolSegmentsImpl(nMaxCol))
+{
+}
+
+ScFlatBoolColSegments::ScFlatBoolColSegments(const ScFlatBoolColSegments& r) :
+ mpImpl(new ScFlatBoolSegmentsImpl(*r.mpImpl))
+{
+}
+
+ScFlatBoolColSegments::~ScFlatBoolColSegments()
+{
+}
+
+bool ScFlatBoolColSegments::setTrue(SCCOL nCol1, SCCOL nCol2)
+{
+ return mpImpl->setTrue(static_cast<SCCOLROW>(nCol1), static_cast<SCCOLROW>(nCol2));
+}
+
+bool ScFlatBoolColSegments::setFalse(SCCOL nCol1, SCCOL nCol2)
+{
+ return mpImpl->setFalse(static_cast<SCCOLROW>(nCol1), static_cast<SCCOLROW>(nCol2));
+}
+
+bool ScFlatBoolColSegments::getRangeData(SCCOL nCol, RangeData& rData)
+{
+ ScFlatBoolSegmentsImpl::RangeData aData;
+ if (!mpImpl->getRangeData(static_cast<SCCOLROW>(nCol), aData))
+ return false;
+
+ rData.mbValue = aData.mnValue;
+ rData.mnCol1 = static_cast<SCCOL>(aData.mnPos1);
+ rData.mnCol2 = static_cast<SCCOL>(aData.mnPos2);
+ return true;
+}
+
+void ScFlatBoolColSegments::removeSegment(SCCOL nCol1, SCCOL nCol2)
+{
+ mpImpl->removeSegment(static_cast<SCCOLROW>(nCol1), static_cast<SCCOLROW>(nCol2));
+}
+
+void ScFlatBoolColSegments::insertSegment(SCCOL nCol, SCCOL nSize)
+{
+ mpImpl->insertSegment(static_cast<SCCOLROW>(nCol), static_cast<SCCOLROW>(nSize), true/*bSkipStartBoundary*/);
+}
+
+void ScFlatBoolColSegments::makeReady()
+{
+ mpImpl->makeReady();
+}
+
+OString ScFlatBoolColSegments::dumpAsString()
+{
+ OString aOutput;
+ OString aSegment;
+ RangeData aRange;
+ SCCOL nCol = 0;
+ while (getRangeData(nCol, aRange))
+ {
+ if (!nCol)
+ aSegment = (aRange.mbValue ? OString::Concat("1") : OString::Concat("0")) + OString::Concat(":");
+ else
+ aSegment.clear();
+
+ aSegment += OString::number(aRange.mnCol2) + " ";
+ aOutput += aSegment;
+ nCol = aRange.mnCol2 + 1;
+ }
+
+ return aOutput;
+}
+
+ScFlatUInt16RowSegments::ForwardIterator::ForwardIterator(ScFlatUInt16RowSegments& rSegs) :
+ mrSegs(rSegs), mnCurPos(0), mnLastPos(-1), mnCurValue(0)
+{
+}
+
+bool ScFlatUInt16RowSegments::ForwardIterator::getValue(SCROW nPos, sal_uInt16& rVal)
+{
+ if (nPos >= mnCurPos)
+ // It can only go in a forward direction.
+ mnCurPos = nPos;
+
+ if (mnCurPos > mnLastPos)
+ {
+ // position not in the current segment. Update the current value.
+ ScFlatUInt16SegmentsImpl::RangeData aData;
+ if (mnLastPos == -1)
+ {
+ // first time in this method, use the tree search based method
+ if (!mrSegs.mpImpl->getRangeData(mnCurPos, aData))
+ return false;
+ }
+ else
+ {
+ // but on subsequent calls, use the leaf method, which is faster
+ // because we have a cached iterator.
+ if (!mrSegs.mpImpl->getRangeDataLeaf(mnCurPos, aData))
+ return false;
+ }
+
+ mnCurValue = aData.mnValue;
+ mnLastPos = aData.mnPos2;
+ }
+
+ rVal = mnCurValue;
+ return true;
+}
+
+ScFlatUInt16RowSegments::ScFlatUInt16RowSegments(SCROW nMaxRow, sal_uInt16 nDefault) :
+ mpImpl(new ScFlatUInt16SegmentsImpl(nMaxRow, nDefault))
+{
+}
+
+ScFlatUInt16RowSegments::ScFlatUInt16RowSegments(const ScFlatUInt16RowSegments& r) :
+ mpImpl(new ScFlatUInt16SegmentsImpl(*r.mpImpl))
+{
+}
+
+ScFlatUInt16RowSegments::~ScFlatUInt16RowSegments()
+{
+}
+
+void ScFlatUInt16RowSegments::setValue(SCROW nRow1, SCROW nRow2, sal_uInt16 nValue)
+{
+ mpImpl->setValue(static_cast<SCCOLROW>(nRow1), static_cast<SCCOLROW>(nRow2), nValue);
+}
+
+sal_uInt16 ScFlatUInt16RowSegments::getValue(SCROW nRow)
+{
+ return mpImpl->getValue(static_cast<SCCOLROW>(nRow));
+}
+
+sal_uInt64 ScFlatUInt16RowSegments::getSumValue(SCROW nRow1, SCROW nRow2)
+{
+ return mpImpl->getSumValue(static_cast<SCCOLROW>(nRow1), static_cast<SCCOLROW>(nRow2));
+}
+
+bool ScFlatUInt16RowSegments::getRangeData(SCROW nRow, RangeData& rData)
+{
+ ScFlatUInt16SegmentsImpl::RangeData aData;
+ if (!mpImpl->getRangeData(static_cast<SCCOLROW>(nRow), aData))
+ return false;
+
+ rData.mnRow1 = aData.mnPos1;
+ rData.mnRow2 = aData.mnPos2;
+ rData.mnValue = aData.mnValue;
+ return true;
+}
+
+void ScFlatUInt16RowSegments::removeSegment(SCROW nRow1, SCROW nRow2)
+{
+ mpImpl->removeSegment(static_cast<SCCOLROW>(nRow1), static_cast<SCCOLROW>(nRow2));
+}
+
+void ScFlatUInt16RowSegments::insertSegment(SCROW nRow, SCROW nSize)
+{
+ mpImpl->insertSegment(static_cast<SCCOLROW>(nRow), static_cast<SCCOLROW>(nSize), false/*bSkipStartBoundary*/);
+}
+
+SCROW ScFlatUInt16RowSegments::findLastTrue(sal_uInt16 nValue) const
+{
+ return mpImpl->findLastTrue(nValue);
+}
+
+void ScFlatUInt16RowSegments::enableTreeSearch(bool bEnable)
+{
+ mpImpl->enableTreeSearch(bEnable);
+}
+
+void ScFlatUInt16RowSegments::setValueIf(SCROW nRow1, SCROW nRow2, sal_uInt16 nValue, const std::function<bool(sal_uInt16)>& rPredicate)
+{
+ mpImpl->setValueIf(static_cast<SCCOLROW>(nRow1), static_cast<SCCOLROW>(nRow2), nValue, rPredicate);
+}
+
+void ScFlatUInt16RowSegments::makeReady()
+{
+ mpImpl->makeReady();
+}
+
+OString ScFlatUInt16RowSegments::dumpAsString()
+{
+ OString aOutput;
+ OString aSegment;
+ RangeData aRange;
+ SCROW nRow = 0;
+ while (getRangeData(nRow, aRange))
+ {
+ aSegment = OString::number(aRange.mnValue) + ":" +
+ OString::number(aRange.mnRow2) + " ";
+ aOutput += aSegment;
+ nRow = aRange.mnRow2 + 1;
+ }
+
+ return aOutput;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/sheetevents.cxx b/sc/source/core/data/sheetevents.cxx
new file mode 100644
index 000000000..7d2152226
--- /dev/null
+++ b/sc/source/core/data/sheetevents.cxx
@@ -0,0 +1,123 @@
+/* -*- 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 <sheetevents.hxx>
+#include <com/sun/star/script/vba/VBAEventId.hpp>
+#include <optional>
+
+OUString ScSheetEvents::GetEventName(ScSheetEventId nEvent)
+{
+ static const char* aEventNames[] =
+ {
+ "OnFocus", // SC_SHEETEVENT_FOCUS
+ "OnUnfocus", // SC_SHEETEVENT_UNFOCUS
+ "OnSelect", // SC_SHEETEVENT_SELECT
+ "OnDoubleClick", // SC_SHEETEVENT_DOUBLECLICK
+ "OnRightClick", // SC_SHEETEVENT_RIGHTCLICK
+ "OnChange", // SC_SHEETEVENT_CHANGE
+ "OnCalculate" // SC_SHEETEVENT_CALCULATE
+ };
+ return OUString::createFromAscii(aEventNames[static_cast<int>(nEvent)]);
+}
+
+sal_Int32 ScSheetEvents::GetVbaSheetEventId(ScSheetEventId nEvent)
+{
+ using namespace ::com::sun::star::script::vba::VBAEventId;
+
+ static const sal_Int32 nVbaEventIds[] =
+ {
+ WORKSHEET_ACTIVATE, // SC_SHEETEVENT_FOCUS
+ WORKSHEET_DEACTIVATE, // SC_SHEETEVENT_UNFOCUS
+ WORKSHEET_SELECTIONCHANGE, // SC_SHEETEVENT_SELECT
+ WORKSHEET_BEFOREDOUBLECLICK, // SC_SHEETEVENT_DOUBLECLICK
+ WORKSHEET_BEFORERIGHTCLICK, // SC_SHEETEVENT_RIGHTCLICK
+ WORKSHEET_CHANGE, // SC_SHEETEVENT_CHANGE
+ WORKSHEET_CALCULATE // SC_SHEETEVENT_CALCULATE
+ };
+ return nVbaEventIds[static_cast<int>(nEvent)];
+}
+
+const int COUNT = static_cast<int>(ScSheetEventId::COUNT);
+
+sal_Int32 ScSheetEvents::GetVbaDocumentEventId(ScSheetEventId nEvent)
+{
+ using namespace ::com::sun::star::script::vba::VBAEventId;
+ sal_Int32 nSheetEventId = GetVbaSheetEventId(nEvent);
+ return (nSheetEventId != NO_EVENT) ? (nSheetEventId + USERDEFINED_START) : NO_EVENT;
+}
+
+ScSheetEvents::ScSheetEvents()
+{
+}
+
+ScSheetEvents::~ScSheetEvents()
+{
+ Clear();
+}
+
+void ScSheetEvents::Clear()
+{
+ mpScriptNames.reset();
+}
+
+ScSheetEvents::ScSheetEvents(const ScSheetEvents& rOther)
+{
+ *this = rOther;
+}
+
+ScSheetEvents& ScSheetEvents::operator=(const ScSheetEvents& rOther)
+{
+ if (this != &rOther)
+ {
+ Clear();
+ if (rOther.mpScriptNames)
+ {
+ mpScriptNames.reset( new std::optional<OUString>[COUNT] );
+ for (sal_Int32 nEvent=0; nEvent<COUNT; ++nEvent)
+ mpScriptNames[nEvent] = rOther.mpScriptNames[nEvent];
+ }
+ }
+ return *this;
+}
+
+const OUString* ScSheetEvents::GetScript(ScSheetEventId nEvent) const
+{
+ if (mpScriptNames)
+ {
+ std::optional<OUString> const & r = mpScriptNames[static_cast<int>(nEvent)];
+ if (r)
+ return &*r;
+ }
+ return nullptr;
+}
+
+void ScSheetEvents::SetScript(ScSheetEventId eEvent, const OUString* pNew)
+{
+ int nEvent = static_cast<int>(eEvent);
+ if (!mpScriptNames)
+ {
+ mpScriptNames.reset( new std::optional<OUString>[COUNT] );
+ }
+ if (pNew)
+ mpScriptNames[nEvent] = *pNew;
+ else
+ mpScriptNames[nEvent].reset();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/simpleformulacalc.cxx b/sc/source/core/data/simpleformulacalc.cxx
new file mode 100644
index 000000000..102373d5a
--- /dev/null
+++ b/sc/source/core/data/simpleformulacalc.cxx
@@ -0,0 +1,155 @@
+/* -*- 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 <memory>
+#include <simpleformulacalc.hxx>
+#include <document.hxx>
+#include <tokenarray.hxx>
+#include <interpre.hxx>
+#include <compiler.hxx>
+#include <sfx2/linkmgr.hxx>
+
+#define DISPLAY_LEN 66
+
+ScSimpleFormulaCalculator::ScSimpleFormulaCalculator( ScDocument& rDoc, const ScAddress& rAddr,
+ const OUString& rFormula, bool bMatrixFormula, formula::FormulaGrammar::Grammar eGram )
+ : mnFormatType(SvNumFormatType::ALL)
+ , mbCalculated(false)
+ , maAddr(rAddr)
+ , mrDoc(rDoc)
+ , maGram(eGram)
+ , mbMatrixResult(false)
+ , mbLimitString(false)
+ , mbMatrixFormula(bMatrixFormula)
+{
+ // compile already here
+ ScCompiler aComp(mrDoc, maAddr, eGram, true, bMatrixFormula);
+ mpCode = aComp.CompileString(rFormula);
+ if(mpCode->GetCodeError() == FormulaError::NONE && mpCode->GetLen())
+ aComp.CompileTokenArray();
+}
+
+ScSimpleFormulaCalculator::~ScSimpleFormulaCalculator()
+{
+}
+
+void ScSimpleFormulaCalculator::Calculate()
+{
+ if(mbCalculated)
+ return;
+
+ mbCalculated = true;
+
+ ScInterpreter aInt(mrDoc.GetFormulaCell( maAddr ), mrDoc, mrDoc.GetNonThreadedContext(), maAddr, *mpCode);
+ if (mbMatrixFormula)
+ aInt.AssertFormulaMatrix();
+
+ sfx2::LinkManager aNewLinkMgr( mrDoc.GetDocumentShell() );
+ aInt.SetLinkManager( &aNewLinkMgr );
+
+ formula::StackVar aIntType = aInt.Interpret();
+ if ( aIntType == formula::svMatrixCell )
+ {
+ ScCompiler aComp(mrDoc, maAddr, maGram);
+ OUStringBuffer aStr;
+ aComp.CreateStringFromToken(aStr, aInt.GetResultToken().get());
+
+ mbMatrixResult = true;
+
+ if (mbLimitString)
+ {
+ const sal_Unicode cCol = ScCompiler::GetNativeSymbol(ocArrayColSep)[0];
+ const sal_Unicode cRow = ScCompiler::GetNativeSymbol(ocArrayRowSep)[0];
+ const sal_Int32 n = aStr.getLength();
+ for (sal_Int32 i = DISPLAY_LEN; i < n; ++i)
+ {
+ const sal_Unicode c = aStr[i];
+ if (c == cCol || c == cRow)
+ {
+ aStr.truncate(i+1);
+ aStr.append("...");
+ break;
+ }
+ }
+ }
+
+ maMatrixFormulaResult = aStr.makeStringAndClear();
+ }
+ mnFormatType = aInt.GetRetFormatType();
+ maResult.SetToken(aInt.GetResultToken().get());
+}
+
+bool ScSimpleFormulaCalculator::IsValue()
+{
+ Calculate();
+
+ if (mbMatrixResult)
+ return false;
+
+ return maResult.IsValue();
+}
+
+bool ScSimpleFormulaCalculator::IsMatrix()
+{
+ Calculate();
+
+ return mbMatrixResult;
+}
+
+FormulaError ScSimpleFormulaCalculator::GetErrCode()
+{
+ Calculate();
+
+ FormulaError nErr = mpCode->GetCodeError();
+ if (nErr != FormulaError::NONE)
+ return nErr;
+ return maResult.GetResultError();
+}
+
+double ScSimpleFormulaCalculator::GetValue()
+{
+ Calculate();
+
+ if ((mpCode->GetCodeError() == FormulaError::NONE) &&
+ maResult.GetResultError() == FormulaError::NONE)
+ return maResult.GetDouble();
+
+ return 0.0;
+}
+
+svl::SharedString ScSimpleFormulaCalculator::GetString()
+{
+ Calculate();
+
+ if (mbMatrixResult)
+ return svl::SharedString( maMatrixFormulaResult); // string not interned
+
+ if ((mpCode->GetCodeError() == FormulaError::NONE) &&
+ maResult.GetResultError() == FormulaError::NONE)
+ return maResult.GetString();
+
+ return svl::SharedString::getEmptyString();
+}
+
+bool ScSimpleFormulaCalculator::HasColRowName() const
+{
+ return formula::FormulaTokenArrayPlainIterator(*mpCode).GetNextColRowName() != nullptr;
+}
+
+ScTokenArray* ScSimpleFormulaCalculator::GetCode()
+{
+ return mpCode.get();
+}
+
+void ScSimpleFormulaCalculator::SetLimitString(bool bLimitString)
+{
+ mbLimitString = bLimitString;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/sortparam.cxx b/sc/source/core/data/sortparam.cxx
new file mode 100644
index 000000000..cb369baae
--- /dev/null
+++ b/sc/source/core/data/sortparam.cxx
@@ -0,0 +1,306 @@
+/* -*- 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 <sortparam.hxx>
+#include <global.hxx>
+#include <address.hxx>
+#include <queryparam.hxx>
+#include <subtotalparam.hxx>
+
+#include <osl/diagnose.h>
+
+#include <algorithm>
+
+ScSortParam::ScSortParam()
+{
+ Clear();
+}
+
+ScSortParam::ScSortParam( const ScSortParam& r ) :
+ nCol1(r.nCol1),nRow1(r.nRow1),nCol2(r.nCol2),nRow2(r.nRow2),
+ aDataAreaExtras(r.aDataAreaExtras),
+ nUserIndex(r.nUserIndex),
+ bHasHeader(r.bHasHeader),bByRow(r.bByRow),bCaseSens(r.bCaseSens),
+ bNaturalSort(r.bNaturalSort),
+ bUserDef(r.bUserDef),
+ bInplace(r.bInplace),
+ nDestTab(r.nDestTab),nDestCol(r.nDestCol),nDestRow(r.nDestRow),
+ maKeyState( r.maKeyState ),
+ aCollatorLocale( r.aCollatorLocale ), aCollatorAlgorithm( r.aCollatorAlgorithm ),
+ nCompatHeader( r.nCompatHeader )
+{
+}
+
+ScSortParam::~ScSortParam() {}
+
+void ScSortParam::Clear()
+{
+ ScSortKeyState aKeyState;
+
+ nCol1=nCol2=nDestCol = 0;
+ nRow1=nRow2=nDestRow = 0;
+ aDataAreaExtras = ScDataAreaExtras();
+ aDataAreaExtras.mbCellDrawObjects = true;
+ aDataAreaExtras.mbCellFormats = true;
+ nCompatHeader = 2;
+ nDestTab = 0;
+ nUserIndex = 0;
+ bHasHeader=bCaseSens=bUserDef=bNaturalSort = false;
+ bByRow = bInplace = true;
+ aCollatorLocale = css::lang::Locale();
+ aCollatorAlgorithm.clear();
+
+ aKeyState.bDoSort = false;
+ aKeyState.nField = 0;
+ aKeyState.bAscending = true;
+
+ // Initialize to default size
+ maKeyState.assign( DEFSORT, aKeyState );
+}
+
+ScSortParam& ScSortParam::operator=( const ScSortParam& r )
+{
+ nCol1 = r.nCol1;
+ nRow1 = r.nRow1;
+ nCol2 = r.nCol2;
+ nRow2 = r.nRow2;
+ aDataAreaExtras = r.aDataAreaExtras;
+ nUserIndex = r.nUserIndex;
+ bHasHeader = r.bHasHeader;
+ bByRow = r.bByRow;
+ bCaseSens = r.bCaseSens;
+ bNaturalSort = r.bNaturalSort;
+ bUserDef = r.bUserDef;
+ bInplace = r.bInplace;
+ nDestTab = r.nDestTab;
+ nDestCol = r.nDestCol;
+ nDestRow = r.nDestRow;
+ maKeyState = r.maKeyState;
+ aCollatorLocale = r.aCollatorLocale;
+ aCollatorAlgorithm = r.aCollatorAlgorithm;
+ nCompatHeader = r.nCompatHeader;
+
+ return *this;
+}
+
+bool ScSortParam::operator==( const ScSortParam& rOther ) const
+{
+ bool bEqual = false;
+ // Number of Sorts the same?
+ sal_uInt16 nLast = 0;
+ sal_uInt16 nOtherLast = 0;
+ sal_uInt16 nSortSize = GetSortKeyCount();
+
+ if ( !maKeyState.empty() )
+ {
+ while ( maKeyState[nLast++].bDoSort && nLast < nSortSize ) ;
+ nLast--;
+ }
+
+ if ( !rOther.maKeyState.empty() )
+ {
+ while ( rOther.maKeyState[nOtherLast++].bDoSort && nOtherLast < nSortSize ) ;
+ nOtherLast--;
+ }
+
+ if ( (nLast == nOtherLast)
+ && (nCol1 == rOther.nCol1)
+ && (nRow1 == rOther.nRow1)
+ && (nCol2 == rOther.nCol2)
+ && (nRow2 == rOther.nRow2)
+ && (aDataAreaExtras == rOther.aDataAreaExtras)
+ && (bHasHeader == rOther.bHasHeader)
+ && (bByRow == rOther.bByRow)
+ && (bCaseSens == rOther.bCaseSens)
+ && (bNaturalSort == rOther.bNaturalSort)
+ && (bUserDef == rOther.bUserDef)
+ && (nUserIndex == rOther.nUserIndex)
+ && (bInplace == rOther.bInplace)
+ && (nDestTab == rOther.nDestTab)
+ && (nDestCol == rOther.nDestCol)
+ && (nDestRow == rOther.nDestRow)
+ && (aCollatorLocale.Language == rOther.aCollatorLocale.Language)
+ && (aCollatorLocale.Country == rOther.aCollatorLocale.Country)
+ && (aCollatorLocale.Variant == rOther.aCollatorLocale.Variant)
+ && (aCollatorAlgorithm == rOther.aCollatorAlgorithm)
+ && ( !maKeyState.empty() || !rOther.maKeyState.empty() )
+ )
+ {
+ bEqual = true;
+ for ( sal_uInt16 i=0; i<=nLast && bEqual; i++ )
+ bEqual = ( maKeyState[i].nField == rOther.maKeyState[i].nField ) &&
+ ( maKeyState[i].bAscending == rOther.maKeyState[i].bAscending );
+ }
+ if ( maKeyState.empty() && rOther.maKeyState.empty() )
+ bEqual = true;
+
+ return bEqual;
+}
+
+ScSortParam::ScSortParam( const ScSubTotalParam& rSub, const ScSortParam& rOld ) :
+ nCol1(rSub.nCol1),nRow1(rSub.nRow1),nCol2(rSub.nCol2),nRow2(rSub.nRow2),
+ aDataAreaExtras(rOld.aDataAreaExtras),
+ nUserIndex(rSub.nUserIndex),
+ bHasHeader(true),bByRow(true),bCaseSens(rSub.bCaseSens),bNaturalSort(rOld.bNaturalSort),
+ bUserDef(rSub.bUserDef),
+ bInplace(true),
+ nDestTab(0),nDestCol(0),nDestRow(0),
+ aCollatorLocale( rOld.aCollatorLocale ), aCollatorAlgorithm( rOld.aCollatorAlgorithm ),
+ nCompatHeader( rOld.nCompatHeader )
+{
+ aDataAreaExtras.mbCellFormats = rSub.bIncludePattern;
+ aDataAreaExtras.resetArea();
+
+ sal_uInt16 i;
+
+ // first the groups from the partial results
+ if (rSub.bDoSort)
+ for (i=0; i<MAXSUBTOTAL; i++)
+ if (rSub.bGroupActive[i])
+ {
+ ScSortKeyState key;
+ key.bDoSort = true;
+ key.nField = rSub.nField[i];
+ key.bAscending = rSub.bAscending;
+ maKeyState.push_back(key);
+ }
+
+ // then the old settings
+ for (i=0; i < rOld.GetSortKeyCount(); i++)
+ if (rOld.maKeyState[i].bDoSort)
+ {
+ SCCOLROW nThisField = rOld.maKeyState[i].nField;
+ bool bDouble = false;
+ for (sal_uInt16 j = 0; j < GetSortKeyCount(); j++)
+ if ( maKeyState[j].nField == nThisField )
+ bDouble = true;
+ if (!bDouble) // do not enter a field twice
+ {
+ ScSortKeyState key;
+ key.bDoSort = true;
+ key.nField = nThisField;
+ key.bAscending = rOld.maKeyState[i].bAscending;
+ maKeyState.push_back(key);
+ }
+ }
+}
+
+ScSortParam::ScSortParam( const ScQueryParam& rParam, SCCOL nCol ) :
+ nCol1(nCol),nRow1(rParam.nRow1),nCol2(nCol),nRow2(rParam.nRow2),nUserIndex(0),
+ bHasHeader(rParam.bHasHeader),bByRow(true),bCaseSens(rParam.bCaseSens),
+ bNaturalSort(false),
+//TODO: what about Locale and Algorithm?
+ bUserDef(false),
+ bInplace(true),
+ nDestTab(0),nDestCol(0),nDestRow(0), nCompatHeader(2)
+{
+ aDataAreaExtras.mbCellDrawObjects = true;
+
+ ScSortKeyState aKeyState;
+ aKeyState.bDoSort = true;
+ aKeyState.nField = nCol;
+ aKeyState.bAscending = true;
+
+ maKeyState.push_back( aKeyState );
+
+ // Set the rest
+ aKeyState.bDoSort = false;
+ aKeyState.nField = 0;
+
+ for (sal_uInt16 i=1; i<GetSortKeyCount(); i++)
+ maKeyState.push_back( aKeyState );
+}
+
+void ScSortParam::MoveToDest()
+{
+ if (!bInplace)
+ {
+ SCCOL nDifX = nDestCol - nCol1;
+ SCROW nDifY = nDestRow - nRow1;
+
+ 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 );
+ for (sal_uInt16 i=0; i<GetSortKeyCount(); i++)
+ if (bByRow)
+ maKeyState[i].nField += nDifX;
+ else
+ maKeyState[i].nField += nDifY;
+
+ bInplace = true;
+ }
+ else
+ {
+ OSL_FAIL("MoveToDest, bInplace == TRUE");
+ }
+}
+
+namespace sc {
+
+namespace {
+
+struct ReorderIndex
+{
+ struct LessByPos2
+ {
+ bool operator() ( const ReorderIndex& r1, const ReorderIndex& r2 ) const
+ {
+ return r1.mnPos2 < r2.mnPos2;
+ }
+ };
+
+ SCCOLROW mnPos1;
+ SCCOLROW mnPos2;
+
+ ReorderIndex( SCCOLROW nPos1, SCCOLROW nPos2 ) : mnPos1(nPos1), mnPos2(nPos2) {}
+};
+
+}
+
+void ReorderParam::reverse()
+{
+ SCCOLROW nStart;
+ if (mbByRow)
+ nStart = maSortRange.aStart.Row();
+ else
+ nStart = maSortRange.aStart.Col();
+
+ size_t n = maOrderIndices.size();
+ std::vector<ReorderIndex> aBucket;
+ aBucket.reserve(n);
+ for (size_t i = 0; i < n; ++i)
+ {
+ SCCOLROW nPos1 = i + nStart;
+ SCCOLROW nPos2 = maOrderIndices[i];
+ aBucket.emplace_back(nPos1, nPos2);
+ }
+
+ std::sort(aBucket.begin(), aBucket.end(), ReorderIndex::LessByPos2());
+ std::vector<SCCOLROW> aNew;
+ aNew.reserve(n);
+ for (size_t i = 0; i < n; ++i)
+ aNew.push_back(aBucket[i].mnPos1);
+
+ maOrderIndices.swap(aNew);
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/stlpool.cxx b/sc/source/core/data/stlpool.cxx
new file mode 100644
index 000000000..8f554896f
--- /dev/null
+++ b/sc/source/core/data/stlpool.cxx
@@ -0,0 +1,447 @@
+/* -*- 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 <scitems.hxx>
+#include <editeng/eeitem.hxx>
+#include <i18nlangtag/mslangid.hxx>
+#include <editeng/borderline.hxx>
+#include <editeng/boxitem.hxx>
+#include <editeng/brushitem.hxx>
+#include <editeng/editdata.hxx>
+#include <editeng/editeng.hxx>
+#include <editeng/editobj.hxx>
+#include <editeng/flditem.hxx>
+#include <editeng/fontitem.hxx>
+#include <svx/pageitem.hxx>
+#include <svl/itemset.hxx>
+#include <svl/zforlist.hxx>
+#include <svl/IndexedStyleSheets.hxx>
+#include <unotools/charclass.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <osl/diagnose.h>
+
+#include <sc.hrc>
+#include <attrib.hxx>
+#include <global.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <document.hxx>
+#include <docpool.hxx>
+#include <stlpool.hxx>
+#include <stlsheet.hxx>
+#include <editutil.hxx>
+#include <stylehelper.hxx>
+
+ScStyleSheetPool::ScStyleSheetPool( const SfxItemPool& rPoolP,
+ ScDocument* pDocument )
+ : SfxStyleSheetPool( rPoolP ),
+ pActualStyleSheet( nullptr ),
+ pDoc( pDocument ),
+ bHasStandardStyles( false )
+{
+}
+
+ScStyleSheetPool::~ScStyleSheetPool()
+{
+}
+
+void ScStyleSheetPool::SetDocument( ScDocument* pDocument )
+{
+ pDoc = pDocument;
+}
+
+SfxStyleSheetBase& ScStyleSheetPool::Make( const OUString& rName,
+ SfxStyleFamily eFam, SfxStyleSearchBits mask)
+{
+ if ( rName == STRING_STANDARD && Find( rName, eFam ) != nullptr )
+ {
+ // When updating styles from a template, Office 5.1 sometimes created
+ // files with multiple default styles.
+ // Create new styles in that case:
+
+ //TODO: only when loading?
+
+ OSL_FAIL("renaming additional default style");
+ sal_uInt32 nCount = GetIndexedStyleSheets().GetNumberOfStyleSheets();
+ for ( sal_uInt32 nAdd = 1; nAdd <= nCount; nAdd++ )
+ {
+ OUString aNewName = ScResId(STR_STYLENAME_STANDARD) + OUString::number( nAdd );
+ if ( Find( aNewName, eFam ) == nullptr )
+ return SfxStyleSheetPool::Make(aNewName, eFam, mask);
+ }
+ }
+
+ // Core uses translated names for both naming and display.
+ // This for all three, loading standard builtin styles from styles.xml
+ // configuration, loading documents and updating from templates.
+ return SfxStyleSheetPool::Make( ScStyleNameConversion::ProgrammaticToDisplayName( rName, eFam), eFam, mask);
+}
+
+rtl::Reference<SfxStyleSheetBase> ScStyleSheetPool::Create( const OUString& rName,
+ SfxStyleFamily eFamily,
+ SfxStyleSearchBits nMaskP )
+{
+ rtl::Reference<ScStyleSheet> pSheet = new ScStyleSheet( rName, *this, eFamily, nMaskP );
+ if ( eFamily == SfxStyleFamily::Para && ScResId(STR_STYLENAME_STANDARD) != rName )
+ pSheet->SetParent( ScResId(STR_STYLENAME_STANDARD) );
+
+ return pSheet;
+}
+
+rtl::Reference<SfxStyleSheetBase> ScStyleSheetPool::Create( const SfxStyleSheetBase& rStyle )
+{
+ OSL_ENSURE( rStyle.isScStyleSheet(), "Invalid StyleSheet-class! :-/" );
+ return new ScStyleSheet( static_cast<const ScStyleSheet&>(rStyle) );
+}
+
+void ScStyleSheetPool::Remove( SfxStyleSheetBase* pStyle )
+{
+ if ( pStyle )
+ {
+ OSL_ENSURE( SfxStyleSearchBits::UserDefined & pStyle->GetMask(),
+ "SfxStyleSearchBits::UserDefined not set!" );
+
+ static_cast<ScDocumentPool&>(rPool).StyleDeleted(static_cast<ScStyleSheet*>(pStyle));
+ SfxStyleSheetPool::Remove(pStyle);
+ }
+}
+
+void ScStyleSheetPool::CopyStyleFrom( ScStyleSheetPool* pSrcPool,
+ const OUString& rName, SfxStyleFamily eFamily )
+{
+ // this is the Dest-Pool
+
+ SfxStyleSheetBase* pStyleSheet = pSrcPool->Find( rName, eFamily );
+ if (!pStyleSheet)
+ return;
+
+ const SfxItemSet& rSourceSet = pStyleSheet->GetItemSet();
+ SfxStyleSheetBase* pDestSheet = Find( rName, eFamily );
+ if (!pDestSheet)
+ pDestSheet = &Make( rName, eFamily );
+ SfxItemSet& rDestSet = pDestSheet->GetItemSet();
+ rDestSet.PutExtended( rSourceSet, SfxItemState::DONTCARE, SfxItemState::DEFAULT );
+
+ if ( eFamily == SfxStyleFamily::Page )
+ {
+ // Set-Items
+
+ if ( const SvxSetItem* pSetItem = rSourceSet.GetItemIfSet( ATTR_PAGE_HEADERSET, false ) )
+ {
+ const SfxItemSet& rSrcSub = pSetItem->GetItemSet();
+ SfxItemSet aDestSub( *rDestSet.GetPool(), rSrcSub.GetRanges() );
+ aDestSub.PutExtended( rSrcSub, SfxItemState::DONTCARE, SfxItemState::DEFAULT );
+ }
+ if ( const SvxSetItem* pSetItem = rSourceSet.GetItemIfSet( ATTR_PAGE_FOOTERSET, false ) )
+ {
+ const SfxItemSet& rSrcSub = pSetItem->GetItemSet();
+ SfxItemSet aDestSub( *rDestSet.GetPool(), rSrcSub.GetRanges() );
+ aDestSub.PutExtended( rSrcSub, SfxItemState::DONTCARE, SfxItemState::DEFAULT );
+ rDestSet.Put( SvxSetItem( ATTR_PAGE_FOOTERSET, aDestSub ) );
+ }
+ }
+ else // cell styles
+ {
+ // number format exchange list has to be handled here, too
+
+ const SfxUInt32Item* pItem;
+ if ( pDoc && pDoc->GetFormatExchangeList() &&
+ (pItem = rSourceSet.GetItemIfSet( ATTR_VALUE_FORMAT, false )) )
+ {
+ sal_uLong nOldFormat = pItem->GetValue();
+ SvNumberFormatterIndexTable::const_iterator it = pDoc->GetFormatExchangeList()->find(nOldFormat);
+ if (it != pDoc->GetFormatExchangeList()->end())
+ {
+ sal_uInt32 nNewFormat = it->second;
+ rDestSet.Put( SfxUInt32Item( ATTR_VALUE_FORMAT, nNewFormat ) );
+ }
+ }
+ }
+}
+
+// Standard templates
+
+void ScStyleSheetPool::CopyStdStylesFrom( ScStyleSheetPool* pSrcPool )
+{
+ // Copy Default styles
+
+ CopyStyleFrom( pSrcPool, ScResId(STR_STYLENAME_STANDARD), SfxStyleFamily::Para );
+ CopyStyleFrom( pSrcPool, ScResId(STR_STYLENAME_STANDARD), SfxStyleFamily::Page );
+ CopyStyleFrom( pSrcPool, ScResId(STR_STYLENAME_REPORT), SfxStyleFamily::Page );
+}
+
+static void lcl_CheckFont( SfxItemSet& rSet, LanguageType eLang, DefaultFontType nFontType, sal_uInt16 nItemId )
+{
+ if ( eLang != LANGUAGE_NONE && eLang != LANGUAGE_DONTKNOW && eLang != LANGUAGE_SYSTEM )
+ {
+ vcl::Font aDefFont = OutputDevice::GetDefaultFont( nFontType, eLang, GetDefaultFontFlags::OnlyOne );
+ SvxFontItem aNewItem( aDefFont.GetFamilyType(), aDefFont.GetFamilyName(), aDefFont.GetStyleName(),
+ aDefFont.GetPitch(), aDefFont.GetCharSet(), nItemId );
+ if ( aNewItem != rSet.Get( nItemId ) )
+ {
+ // put item into style's ItemSet only if different from (static) default
+ rSet.Put( aNewItem );
+ }
+ }
+}
+
+void ScStyleSheetPool::CreateStandardStyles()
+{
+ // Add new entries even for CopyStdStylesFrom
+
+ Color aColBlack ( COL_BLACK );
+ OUString aStr;
+ sal_Int32 nStrLen;
+ const OUString aHelpFile;//which text???
+ SfxItemSet* pSet = nullptr;
+ SfxItemSet* pHFSet = nullptr;
+ ScEditEngineDefaulter aEdEngine( EditEngine::CreatePool().get(), true );
+ aEdEngine.SetUpdateLayout( false );
+ std::unique_ptr<EditTextObject> pEmptyTxtObj = aEdEngine.CreateTextObject();
+ std::unique_ptr<EditTextObject> pTxtObj;
+ ScPageHFItem aHeaderItem( ATTR_PAGE_HEADERRIGHT );
+ ScPageHFItem aFooterItem( ATTR_PAGE_FOOTERRIGHT );
+ ScStyleSheet* pSheet = nullptr;
+ ::editeng::SvxBorderLine aBorderLine ( &aColBlack, SvxBorderLineWidth::Medium );
+ SvxBoxItem aBoxItem ( ATTR_BORDER );
+ SvxBoxInfoItem aBoxInfoItem ( ATTR_BORDER_INNER );
+
+ OUString aStrStandard = ScResId(STR_STYLENAME_STANDARD);
+
+ // Cell format templates:
+
+ // 1. Standard
+
+ pSheet = static_cast<ScStyleSheet*>( &Make( aStrStandard, SfxStyleFamily::Para, SfxStyleSearchBits::ScStandard ) );
+ pSheet->SetHelpId( aHelpFile, HID_SC_SHEET_CELL_STD );
+
+ // if default fonts for the document's languages are different from the pool default,
+ // put them into the default style
+ // (not as pool defaults, because pool defaults can't be changed by the user)
+ // the document languages must be set before creating the default styles!
+
+ pSet = &pSheet->GetItemSet();
+ LanguageType eLatin, eCjk, eCtl;
+ pDoc->GetLanguage( eLatin, eCjk, eCtl );
+
+ // If the UI language is Korean, the default Latin font has to
+ // be queried for Korean, too (the Latin language from the document can't be Korean).
+ // This is the same logic as in SwDocShell::InitNew.
+ LanguageType eUiLanguage = Application::GetSettings().GetUILanguageTag().getLanguageType();
+ if (MsLangId::isKorean(eUiLanguage))
+ eLatin = eUiLanguage;
+
+ lcl_CheckFont( *pSet, eLatin, DefaultFontType::LATIN_SPREADSHEET, ATTR_FONT );
+ lcl_CheckFont( *pSet, eCjk, DefaultFontType::CJK_SPREADSHEET, ATTR_CJK_FONT );
+ lcl_CheckFont( *pSet, eCtl, DefaultFontType::CTL_SPREADSHEET, ATTR_CTL_FONT );
+
+ // #i55300# default CTL font size for Thai has to be larger
+ // #i59408# The 15 point size causes problems with row heights, so no different
+ // size is used for Thai in Calc for now.
+// if ( eCtl == LANGUAGE_THAI )
+// pSet->Put( SvxFontHeightItem( 300, 100, ATTR_CTL_FONT_HEIGHT ) ); // 15 pt
+
+ // Page format template:
+
+ // 1. Standard
+
+ pSheet = static_cast<ScStyleSheet*>( &Make( aStrStandard,
+ SfxStyleFamily::Page,
+ SfxStyleSearchBits::ScStandard ) );
+
+ pSet = &pSheet->GetItemSet();
+ pSheet->SetHelpId( aHelpFile, HID_SC_SHEET_PAGE_STD );
+
+ // distance to header/footer for the sheet
+ SvxSetItem aHFSetItem = pSet->Get( ATTR_PAGE_HEADERSET );
+ aHFSetItem.SetWhich(ATTR_PAGE_HEADERSET);
+ pSet->Put( aHFSetItem );
+ aHFSetItem.SetWhich(ATTR_PAGE_FOOTERSET);
+ pSet->Put( aHFSetItem );
+
+ // Header:
+ // [empty][\sheet\][empty]
+
+ aEdEngine.SetTextCurrentDefaults(OUString());
+ aEdEngine.QuickInsertField( SvxFieldItem(SvxTableField(), EE_FEATURE_FIELD), ESelection() );
+ pTxtObj = aEdEngine.CreateTextObject();
+ aHeaderItem.SetLeftArea ( *pEmptyTxtObj );
+ aHeaderItem.SetCenterArea( *pTxtObj );
+ aHeaderItem.SetRightArea ( *pEmptyTxtObj );
+ pSet->Put( aHeaderItem );
+
+ // Footer:
+ // [empty][Page \STR_PAGE\][empty]
+
+ aStr = ScResId( STR_PAGE ) + " ";
+ aEdEngine.SetTextCurrentDefaults( aStr );
+ nStrLen = aStr.getLength();
+ aEdEngine.QuickInsertField( SvxFieldItem(SvxPageField(), EE_FEATURE_FIELD), ESelection(0,nStrLen,0,nStrLen) );
+ pTxtObj = aEdEngine.CreateTextObject();
+ aFooterItem.SetLeftArea ( *pEmptyTxtObj );
+ aFooterItem.SetCenterArea( *pTxtObj );
+ aFooterItem.SetRightArea ( *pEmptyTxtObj );
+ pSet->Put( aFooterItem );
+
+ // 2. Report
+
+ pSheet = static_cast<ScStyleSheet*>( &Make( ScResId( STR_STYLENAME_REPORT ),
+ SfxStyleFamily::Page,
+ SfxStyleSearchBits::ScStandard ) );
+ pSet = &pSheet->GetItemSet();
+ pSheet->SetHelpId( aHelpFile, HID_SC_SHEET_PAGE_REP );
+
+ // Background and border
+ aBoxItem.SetLine( &aBorderLine, SvxBoxItemLine::TOP );
+ aBoxItem.SetLine( &aBorderLine, SvxBoxItemLine::BOTTOM );
+ aBoxItem.SetLine( &aBorderLine, SvxBoxItemLine::LEFT );
+ aBoxItem.SetLine( &aBorderLine, SvxBoxItemLine::RIGHT );
+ aBoxItem.SetAllDistances( 10 ); // 0.2mm
+ aBoxInfoItem.SetValid( SvxBoxInfoItemValidFlags::TOP );
+ aBoxInfoItem.SetValid( SvxBoxInfoItemValidFlags::BOTTOM );
+ aBoxInfoItem.SetValid( SvxBoxInfoItemValidFlags::LEFT );
+ aBoxInfoItem.SetValid( SvxBoxInfoItemValidFlags::RIGHT );
+ aBoxInfoItem.SetValid( SvxBoxInfoItemValidFlags::DISTANCE );
+ aBoxInfoItem.SetTable( false );
+ aBoxInfoItem.SetDist ( true );
+
+ SvxSetItem aHFSetItem2 = pSet->Get( ATTR_PAGE_HEADERSET );
+ pHFSet = &(aHFSetItem2.GetItemSet());
+
+ pHFSet->Put( SvxBrushItem( COL_LIGHTGRAY, ATTR_BACKGROUND ) );
+ pHFSet->Put( aBoxItem );
+ pHFSet->Put( aBoxInfoItem );
+ aHFSetItem2.SetWhich(ATTR_PAGE_HEADERSET);
+ pSet->Put( aHFSetItem2 );
+ aHFSetItem2.SetWhich(ATTR_PAGE_FOOTERSET);
+ pSet->Put( aHFSetItem2 );
+
+ // Footer:
+ // [\TABLE\ (\DATA\)][empty][\DATE\, \TIME\]
+
+ aStr = " ()";
+ aEdEngine.SetTextCurrentDefaults( aStr );
+ aEdEngine.QuickInsertField( SvxFieldItem(SvxFileField(), EE_FEATURE_FIELD), ESelection(0,2,0,2) );
+ aEdEngine.QuickInsertField( SvxFieldItem(SvxTableField(), EE_FEATURE_FIELD), ESelection() );
+ pTxtObj = aEdEngine.CreateTextObject();
+ aHeaderItem.SetLeftArea( *pTxtObj );
+ aHeaderItem.SetCenterArea( *pEmptyTxtObj );
+ aStr = ", ";
+ aEdEngine.SetTextCurrentDefaults( aStr );
+ aEdEngine.QuickInsertField( SvxFieldItem(SvxTimeField(), EE_FEATURE_FIELD), ESelection(0,2,0,2) );
+ aEdEngine.QuickInsertField( SvxFieldItem(SvxDateField(Date( Date::SYSTEM ),SvxDateType::Var), EE_FEATURE_FIELD),
+ ESelection() );
+ pTxtObj = aEdEngine.CreateTextObject();
+ aHeaderItem.SetRightArea( *pTxtObj );
+ pSet->Put( aHeaderItem );
+
+ // Footer:
+ // [empty][Page: \PAGE\ / \PAGE\][empty]
+
+ aStr = ScResId( STR_PAGE ) + " ";
+ nStrLen = aStr.getLength();
+ aStr += " / ";
+ sal_Int32 nStrLen2 = aStr.getLength();
+ aEdEngine.SetTextCurrentDefaults( aStr );
+ aEdEngine.QuickInsertField( SvxFieldItem(SvxPagesField(), EE_FEATURE_FIELD), ESelection(0,nStrLen2,0,nStrLen2) );
+ aEdEngine.QuickInsertField( SvxFieldItem(SvxPageField(), EE_FEATURE_FIELD), ESelection(0,nStrLen,0,nStrLen) );
+ pTxtObj = aEdEngine.CreateTextObject();
+ aFooterItem.SetLeftArea ( *pEmptyTxtObj );
+ aFooterItem.SetCenterArea( *pTxtObj );
+ aFooterItem.SetRightArea ( *pEmptyTxtObj );
+ pSet->Put( aFooterItem );
+
+ bHasStandardStyles = true;
+}
+
+namespace {
+
+struct CaseInsensitiveNamePredicate : svl::StyleSheetPredicate
+{
+ CaseInsensitiveNamePredicate(const OUString& rName, SfxStyleFamily eFam)
+ : mUppercaseName(ScGlobal::getCharClass().uppercase(rName)), mFamily(eFam)
+ {
+ }
+
+ bool
+ Check(const SfxStyleSheetBase& rStyleSheet) override
+ {
+ if (rStyleSheet.GetFamily() == mFamily)
+ {
+ OUString aUpName = ScGlobal::getCharClass().uppercase(rStyleSheet.GetName());
+ if (mUppercaseName == aUpName)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ OUString mUppercaseName;
+ SfxStyleFamily mFamily;
+};
+
+}
+
+// Functor object to find all style sheets of a family which match a given name caseinsensitively
+ScStyleSheet* ScStyleSheetPool::FindCaseIns( const OUString& rName, SfxStyleFamily eFam )
+{
+ CaseInsensitiveNamePredicate aPredicate(rName, eFam);
+ std::vector<sal_Int32> aFoundPositions = GetIndexedStyleSheets().FindPositionsByPredicate(aPredicate);
+
+ ScStyleSheet* first = nullptr; // first case insensitive match found
+ for (const auto& rPos : aFoundPositions)
+ {
+ SfxStyleSheetBase *pFound = GetStyleSheetByPositionInIndex(rPos);
+ // we do not know what kind of sheets we have.
+ if (pFound->isScStyleSheet())
+ {
+ if (pFound->GetName() == rName) // exact case sensitive match
+ return static_cast<ScStyleSheet*>(pFound);
+ if (!first)
+ first = static_cast<ScStyleSheet*>(pFound);
+ }
+ }
+ return first;
+}
+
+ScStyleSheet* ScStyleSheetPool::FindAutoStyle(const OUString& rName)
+{
+ ScStyleSheet* pStyleSheet = FindCaseIns(rName, SfxStyleFamily::Para);
+ if (!pStyleSheet)
+ if (auto pFound = Find(ScResId(STR_STYLENAME_STANDARD), SfxStyleFamily::Para))
+ if (pFound->isScStyleSheet()) // we do not know what kind of sheets we have
+ pStyleSheet = static_cast<ScStyleSheet*>(pFound);
+ return pStyleSheet;
+}
+
+void ScStyleSheetPool::setAllParaStandard()
+{
+ SfxStyleSheetBase* pSheet = First(SfxStyleFamily::Para);
+ while (pSheet)
+ {
+ pSheet->SetMask(SfxStyleSearchBits::ScStandard);
+ pSheet = Next();
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/stlsheet.cxx b/sc/source/core/data/stlsheet.cxx
new file mode 100644
index 000000000..fdf09e7cd
--- /dev/null
+++ b/sc/source/core/data/stlsheet.cxx
@@ -0,0 +1,303 @@
+/* -*- 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 <document.hxx>
+#include <stlsheet.hxx>
+#include <stlpool.hxx>
+
+#include <scitems.hxx>
+#include <editeng/boxitem.hxx>
+#include <editeng/frmdiritem.hxx>
+#include <editeng/lrspitem.hxx>
+#include <svx/pageitem.hxx>
+#include <editeng/paperinf.hxx>
+#include <editeng/shaditem.hxx>
+#include <editeng/sizeitem.hxx>
+#include <editeng/ulspitem.hxx>
+#include <editeng/xmlcnitm.hxx>
+#include <svl/itempool.hxx>
+#include <svl/itemset.hxx>
+#include <svl/numformat.hxx>
+#include <svl/hint.hxx>
+#include <o3tl/unit_conversion.hxx>
+#include <attrib.hxx>
+
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <sc.hrc>
+
+constexpr auto TWO_CM = o3tl::convert(2, o3tl::Length::cm, o3tl::Length::twip); // 1134
+constexpr auto HFDIST_CM = o3tl::convert(250, o3tl::Length::mm100, o3tl::Length::twip); // 142
+
+ScStyleSheet::ScStyleSheet( const OUString& rName,
+ const ScStyleSheetPool& rPoolP,
+ SfxStyleFamily eFamily,
+ SfxStyleSearchBits nMaskP )
+
+ : SfxStyleSheet ( rName, rPoolP, eFamily, nMaskP )
+ , eUsage( Usage::UNKNOWN )
+{
+}
+
+ScStyleSheet::ScStyleSheet( const ScStyleSheet& rStyle )
+ : SfxStyleSheet ( rStyle )
+ , eUsage( Usage::UNKNOWN )
+{
+}
+
+ScStyleSheet::~ScStyleSheet()
+{
+}
+
+bool ScStyleSheet::HasFollowSupport() const
+{
+ return false;
+}
+
+bool ScStyleSheet::HasParentSupport () const
+{
+ bool bHasParentSupport = false;
+
+ switch ( GetFamily() )
+ {
+ case SfxStyleFamily::Para: bHasParentSupport = true; break;
+ case SfxStyleFamily::Page: bHasParentSupport = false; break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+
+ return bHasParentSupport;
+}
+
+bool ScStyleSheet::SetParent( const OUString& rParentName )
+{
+ bool bResult = false;
+ OUString aEffName = rParentName;
+ SfxStyleSheetBase* pStyle = m_pPool->Find( aEffName, nFamily );
+ if (!pStyle)
+ {
+ std::unique_ptr<SfxStyleSheetIterator> pIter = m_pPool->CreateIterator(nFamily);
+ pStyle = pIter->First();
+ if (pStyle)
+ aEffName = pStyle->GetName();
+ }
+
+ if ( pStyle && aEffName != GetName() )
+ {
+ bResult = SfxStyleSheet::SetParent( aEffName );
+ if (bResult)
+ {
+ SfxItemSet& rParentSet = pStyle->GetItemSet();
+ GetItemSet().SetParent( &rParentSet );
+
+ // #i113491# Drag&Drop in the stylist's hierarchical view doesn't execute a slot,
+ // so the repaint has to come from here (after modifying the ItemSet).
+ // RepaintRange checks the document's IsVisible flag and locked repaints.
+ ScDocument* pDoc = static_cast<ScStyleSheetPool*>(GetPool())->GetDocument();
+ if (pDoc)
+ pDoc->RepaintRange( ScRange( 0,0,0, pDoc->MaxCol(),pDoc->MaxRow(),MAXTAB ) );
+ }
+ }
+
+ return bResult;
+}
+
+void ScStyleSheet::ResetParent()
+{
+ GetItemSet().SetParent(nullptr);
+}
+
+SfxItemSet& ScStyleSheet::GetItemSet()
+{
+ if ( !pSet )
+ {
+ switch ( GetFamily() )
+ {
+ case SfxStyleFamily::Page:
+ {
+ // Page templates should not be derivable,
+ // therefore suitable values are set at this point.
+ // (== Standard page template)
+
+ SfxItemPool& rItemPool = GetPool()->GetPool();
+ pSet = new SfxItemSetFixed<
+ ATTR_USERDEF, ATTR_USERDEF,
+ ATTR_WRITINGDIR, ATTR_WRITINGDIR,
+ ATTR_BACKGROUND, ATTR_BACKGROUND,
+ ATTR_BORDER, ATTR_SHADOW,
+ ATTR_LRSPACE, ATTR_PAGE_SCALETO>(rItemPool);
+
+ // If being loaded also the set is then filled in from the file,
+ // so the defaults do not need to be set.
+ // GetPrinter would then also create a new printer,
+ // because the stored Printer is not loaded yet!
+
+ ScDocument* pDoc = static_cast<ScStyleSheetPool*>(GetPool())->GetDocument();
+ if ( pDoc )
+ {
+ // Setting reasonable default values:
+ SvxPageItem aPageItem( ATTR_PAGE );
+ SvxSizeItem aPaperSizeItem( ATTR_PAGE_SIZE, SvxPaperInfo::GetDefaultPaperSize() );
+
+ SvxSetItem aHFSetItem(
+ rItemPool.GetDefaultItem(ATTR_PAGE_HEADERSET) );
+
+ SfxItemSet& rHFSet = aHFSetItem.GetItemSet();
+ SvxSizeItem aHFSizeItem( // 0,5 cm + distance
+ ATTR_PAGE_SIZE,
+ Size( 0, o3tl::convert(500, o3tl::Length::mm100, o3tl::Length::twip) + HFDIST_CM ) );
+
+ SvxULSpaceItem aHFDistItem ( HFDIST_CM,// nUp
+ HFDIST_CM,// nLow
+ ATTR_ULSPACE );
+
+ SvxLRSpaceItem aLRSpaceItem( TWO_CM, // nLeft
+ TWO_CM, // nRight
+ TWO_CM, // nTLeft
+ 0, // nFirstLineOffset
+ ATTR_LRSPACE );
+ SvxULSpaceItem aULSpaceItem( TWO_CM, // nUp
+ TWO_CM, // nLow
+ ATTR_ULSPACE );
+ SvxBoxInfoItem aBoxInfoItem( ATTR_BORDER_INNER );
+
+ aBoxInfoItem.SetTable( false );
+ aBoxInfoItem.SetDist( true );
+ aBoxInfoItem.SetValid( SvxBoxInfoItemValidFlags::DISTANCE );
+
+ aPageItem.SetLandscape( false );
+
+ rHFSet.Put( aBoxInfoItem );
+ rHFSet.Put( aHFSizeItem );
+ rHFSet.Put( aHFDistItem );
+ rHFSet.Put( SvxLRSpaceItem( 0,0,0,0, ATTR_LRSPACE ) ); // Set border to Null
+
+ aHFSetItem.SetWhich(ATTR_PAGE_HEADERSET);
+ pSet->Put( aHFSetItem );
+ aHFSetItem.SetWhich(ATTR_PAGE_FOOTERSET);
+ pSet->Put( aHFSetItem );
+ pSet->Put( aBoxInfoItem ); // Do not overwrite PoolDefault
+ // due to format templates
+
+
+ // Writing direction: not as pool default because the default for cells
+ // must remain SvxFrameDirection::Environment, and each page style's setting is
+ // supposed to be saved in the file format.
+ // The page default depends on the system language.
+ SvxFrameDirection eDirection = ScGlobal::IsSystemRTL() ?
+ SvxFrameDirection::Horizontal_RL_TB : SvxFrameDirection::Horizontal_LR_TB;
+ pSet->Put( SvxFrameDirectionItem( eDirection, ATTR_WRITINGDIR ) );
+
+ rItemPool.SetPoolDefaultItem( aPageItem );
+ rItemPool.SetPoolDefaultItem( aPaperSizeItem );
+ rItemPool.SetPoolDefaultItem( aLRSpaceItem );
+ rItemPool.SetPoolDefaultItem( aULSpaceItem );
+ rItemPool.SetPoolDefaultItem( SfxUInt16Item( ATTR_PAGE_SCALE, 100 ) );
+ ScPageScaleToItem aScaleToItem;
+ rItemPool.SetPoolDefaultItem( aScaleToItem );
+ rItemPool.SetPoolDefaultItem( SfxUInt16Item( ATTR_PAGE_SCALETOPAGES, 0 ) );
+ }
+ }
+ break;
+
+ case SfxStyleFamily::Para:
+ default:
+ pSet = new SfxItemSetFixed<ATTR_PATTERN_START, ATTR_PATTERN_END>( GetPool()->GetPool() );
+ break;
+ }
+ bMySet = true;
+ }
+ if ( nHelpId == HID_SC_SHEET_CELL_ERG1 )
+ {
+ if ( !pSet->Count() )
+ {
+ // Hack to work around that when this code is called from
+ // ~ScStyleSheetPool -> ~SfxStyleSheetPool, GetPool() is no longer
+ // an ScStyleSheetPool:
+ ScStyleSheetPool * pool = dynamic_cast<ScStyleSheetPool *>(
+ GetPool());
+ if (pool != nullptr) {
+ ScDocument* pDoc = pool->GetDocument();
+ if ( pDoc )
+ {
+ sal_uInt32 nNumFmt = pDoc->GetFormatTable()->GetStandardFormat( SvNumFormatType::CURRENCY,ScGlobal::eLnge );
+ pSet->Put( SfxUInt32Item( ATTR_VALUE_FORMAT, nNumFmt ) );
+ }
+ }
+ }
+ }
+
+ return *pSet;
+}
+
+bool ScStyleSheet::IsUsed() const
+{
+ switch (GetFamily())
+ {
+ case SfxStyleFamily::Para:
+ {
+ // Always query the document to let it decide if a rescan is necessary,
+ // and store the state.
+ ScDocument* pDoc = static_cast<ScStyleSheetPool*>(m_pPool)->GetDocument();
+ if ( pDoc && pDoc->IsStyleSheetUsed( *this ) )
+ eUsage = Usage::USED;
+ else
+ eUsage = Usage::NOTUSED;
+ return eUsage == Usage::USED;
+ }
+ case SfxStyleFamily::Page:
+ {
+ // tdf#108188 - verify that the page style is actually used
+ ScDocument* pDoc = static_cast<ScStyleSheetPool*>(m_pPool)->GetDocument();
+ if (pDoc && pDoc->IsPageStyleInUse(GetName(), nullptr))
+ eUsage = Usage::USED;
+ else
+ eUsage = Usage::NOTUSED;
+ return eUsage == Usage::USED;
+ }
+ default:
+ return true;
+ }
+}
+
+void ScStyleSheet::Notify( SfxBroadcaster&, const SfxHint& rHint )
+{
+ if ( rHint.GetId() == SfxHintId::Dying )
+ GetItemSet().SetParent( nullptr );
+}
+
+// Avoid creating a Style "Standard" if this is not the Standard-Name;
+// otherwise two styles would have the same name when storing.
+// (on loading the style is created directly per Make with the name; making this query
+// not applicable)
+//TODO: If at any time during loading SetName is called, a flag has to be set/checked for loading
+//TODO: The whole check has to be removed if for a new file version the name transformation is dropped.
+
+bool ScStyleSheet::SetName(const OUString& rNew, bool bReindexNow)
+{
+ OUString aFileStdName = STRING_STANDARD;
+ if ( rNew == aFileStdName && aFileStdName != ScResId(STR_STYLENAME_STANDARD) )
+ return false;
+ else
+ return SfxStyleSheet::SetName(rNew, bReindexNow);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/subtotalparam.cxx b/sc/source/core/data/subtotalparam.cxx
new file mode 100644
index 000000000..e8f329542
--- /dev/null
+++ b/sc/source/core/data/subtotalparam.cxx
@@ -0,0 +1,199 @@
+/* -*- 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 <subtotalparam.hxx>
+
+#include <osl/diagnose.h>
+
+ScSubTotalParam::ScSubTotalParam()
+{
+ for ( sal_uInt16 i=0; i<MAXSUBTOTAL; i++ )
+ {
+ nSubTotals[i] = 0;
+ pSubTotals[i] = nullptr;
+ pFunctions[i] = nullptr;
+ }
+
+ Clear();
+}
+
+ScSubTotalParam::ScSubTotalParam( const ScSubTotalParam& r ) :
+ nCol1(r.nCol1),nRow1(r.nRow1),nCol2(r.nCol2),nRow2(r.nRow2),nUserIndex(r.nUserIndex),
+ bRemoveOnly(r.bRemoveOnly),bReplace(r.bReplace),bPagebreak(r.bPagebreak),bCaseSens(r.bCaseSens),
+ bDoSort(r.bDoSort),bAscending(r.bAscending),bUserDef(r.bUserDef),
+ bIncludePattern(r.bIncludePattern)
+{
+ for (sal_uInt16 i=0; i<MAXSUBTOTAL; i++)
+ {
+ bGroupActive[i] = r.bGroupActive[i];
+ nField[i] = r.nField[i];
+
+ if ( (r.nSubTotals[i] > 0) && r.pSubTotals[i] && r.pFunctions[i] )
+ {
+ nSubTotals[i] = r.nSubTotals[i];
+ pSubTotals[i].reset(new SCCOL [r.nSubTotals[i]]);
+ pFunctions[i].reset(new ScSubTotalFunc [r.nSubTotals[i]]);
+
+ for (SCCOL j=0; j<r.nSubTotals[i]; j++)
+ {
+ pSubTotals[i][j] = r.pSubTotals[i][j];
+ pFunctions[i][j] = r.pFunctions[i][j];
+ }
+ }
+ else
+ {
+ nSubTotals[i] = 0;
+ }
+ }
+}
+
+void ScSubTotalParam::Clear()
+{
+ nCol1=nCol2= 0;
+ nRow1=nRow2 = 0;
+ nUserIndex = 0;
+ bPagebreak=bCaseSens=bUserDef=bIncludePattern=bRemoveOnly = false;
+ bAscending=bReplace=bDoSort = true;
+
+ for (sal_uInt16 i=0; i<MAXSUBTOTAL; i++)
+ {
+ bGroupActive[i] = false;
+ nField[i] = 0;
+
+ if ( (nSubTotals[i] > 0) && pSubTotals[i] && pFunctions[i] )
+ {
+ for ( SCCOL j=0; j<nSubTotals[i]; j++ ) {
+ pSubTotals[i][j] = 0;
+ pFunctions[i][j] = SUBTOTAL_FUNC_NONE;
+ }
+ }
+ }
+}
+
+ScSubTotalParam& ScSubTotalParam::operator=( const ScSubTotalParam& r )
+{
+ if(this == &r)
+ return *this;
+
+ nCol1 = r.nCol1;
+ nRow1 = r.nRow1;
+ nCol2 = r.nCol2;
+ nRow2 = r.nRow2;
+ bRemoveOnly = r.bRemoveOnly;
+ bReplace = r.bReplace;
+ bPagebreak = r.bPagebreak;
+ bCaseSens = r.bCaseSens;
+ bDoSort = r.bDoSort;
+ bAscending = r.bAscending;
+ bUserDef = r.bUserDef;
+ nUserIndex = r.nUserIndex;
+ bIncludePattern = r.bIncludePattern;
+
+ for (sal_uInt16 i=0; i<MAXSUBTOTAL; i++)
+ {
+ bGroupActive[i] = r.bGroupActive[i];
+ nField[i] = r.nField[i];
+ nSubTotals[i] = r.nSubTotals[i];
+
+ pSubTotals[i].reset();
+ pFunctions[i].reset();
+
+ if ( r.nSubTotals[i] > 0 )
+ {
+ pSubTotals[i].reset(new SCCOL [r.nSubTotals[i]]);
+ pFunctions[i].reset(new ScSubTotalFunc [r.nSubTotals[i]]);
+
+ for (SCCOL j=0; j<r.nSubTotals[i]; j++)
+ {
+ pSubTotals[i][j] = r.pSubTotals[i][j];
+ pFunctions[i][j] = r.pFunctions[i][j];
+ }
+ }
+ else
+ {
+ nSubTotals[i] = 0;
+ }
+ }
+
+ return *this;
+}
+
+bool ScSubTotalParam::operator==( const ScSubTotalParam& rOther ) const
+{
+ bool bEqual = (nCol1 == rOther.nCol1)
+ && (nRow1 == rOther.nRow1)
+ && (nCol2 == rOther.nCol2)
+ && (nRow2 == rOther.nRow2)
+ && (nUserIndex == rOther.nUserIndex)
+ && (bRemoveOnly == rOther.bRemoveOnly)
+ && (bReplace == rOther.bReplace)
+ && (bPagebreak == rOther.bPagebreak)
+ && (bDoSort == rOther.bDoSort)
+ && (bCaseSens == rOther.bCaseSens)
+ && (bAscending == rOther.bAscending)
+ && (bUserDef == rOther.bUserDef)
+ && (bIncludePattern== rOther.bIncludePattern);
+
+ if ( bEqual )
+ {
+ bEqual = true;
+ for ( sal_uInt16 i=0; i<MAXSUBTOTAL && bEqual; i++ )
+ {
+ bEqual = (bGroupActive[i] == rOther.bGroupActive[i])
+ && (nField[i] == rOther.nField[i])
+ && (nSubTotals[i] == rOther.nSubTotals[i]);
+
+ if ( bEqual && (nSubTotals[i] > 0) )
+ {
+ for (SCCOL j=0; (j<nSubTotals[i]) && bEqual; j++)
+ {
+ bEqual = bEqual
+ && (pSubTotals[i][j] == rOther.pSubTotals[i][j])
+ && (pFunctions[i][j] == rOther.pFunctions[i][j]);
+ }
+ }
+ }
+ }
+
+ return bEqual;
+}
+
+void ScSubTotalParam::SetSubTotals( sal_uInt16 nGroup,
+ const SCCOL* ptrSubTotals,
+ const ScSubTotalFunc* ptrFunctions,
+ sal_uInt16 nCount )
+{
+ OSL_ENSURE( (nGroup <= MAXSUBTOTAL),
+ "ScSubTotalParam::SetSubTotals(): nGroup > MAXSUBTOTAL!" );
+ OSL_ENSURE( ptrSubTotals,
+ "ScSubTotalParam::SetSubTotals(): ptrSubTotals == NULL!" );
+ OSL_ENSURE( ptrFunctions,
+ "ScSubTotalParam::SetSubTotals(): ptrFunctions == NULL!" );
+ OSL_ENSURE( (nCount > 0),
+ "ScSubTotalParam::SetSubTotals(): nCount <= 0!" );
+
+ if ( !(ptrSubTotals && ptrFunctions && (nCount > 0) && (nGroup <= MAXSUBTOTAL)) )
+ return;
+
+ // 0 is interpreted as 1, otherwise decrementing the array index
+ if (nGroup != 0)
+ nGroup--;
+
+ pSubTotals[nGroup].reset(new SCCOL[nCount]);
+ pFunctions[nGroup].reset(new ScSubTotalFunc[nCount]);
+ nSubTotals[nGroup] = static_cast<SCCOL>(nCount);
+
+ for ( sal_uInt16 i=0; i<nCount; i++ )
+ {
+ pSubTotals[nGroup][i] = ptrSubTotals[i];
+ pFunctions[nGroup][i] = ptrFunctions[i];
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/tabbgcolor.cxx b/sc/source/core/data/tabbgcolor.cxx
new file mode 100644
index 000000000..5b14ff830
--- /dev/null
+++ b/sc/source/core/data/tabbgcolor.cxx
@@ -0,0 +1,36 @@
+/* -*- 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 <tabbgcolor.hxx>
+
+ScUndoTabColorInfo::ScUndoTabColorInfo(SCTAB nTab) :
+ mnTabId(nTab),
+ maOldTabBgColor(COL_AUTO),
+ maNewTabBgColor(COL_AUTO)
+{
+}
+
+ScUndoTabColorInfo::ScUndoTabColorInfo(const ScUndoTabColorInfo& r) :
+ mnTabId(r.mnTabId),
+ maOldTabBgColor(r.maOldTabBgColor),
+ maNewTabBgColor(r.maNewTabBgColor)
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/table1.cxx b/sc/source/core/data/table1.cxx
new file mode 100644
index 000000000..b177bd55b
--- /dev/null
+++ b/sc/source/core/data/table1.cxx
@@ -0,0 +1,2729 @@
+/* -*- 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 <editeng/justifyitem.hxx>
+#include <o3tl/safeint.hxx>
+#include <o3tl/unit_conversion.hxx>
+#include <unotools/textsearch.hxx>
+#include <unotools/charclass.hxx>
+#include <osl/diagnose.h>
+
+#include <patattr.hxx>
+#include <table.hxx>
+#include <document.hxx>
+#include <drwlayer.hxx>
+#include <olinetab.hxx>
+#include <global.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <refupdat.hxx>
+#include <markdata.hxx>
+#include <progress.hxx>
+#include <prnsave.hxx>
+#include <tabprotection.hxx>
+#include <sheetevents.hxx>
+#include <segmenttree.hxx>
+#include <dbdata.hxx>
+#include <conditio.hxx>
+#include <globalnames.hxx>
+#include <cellvalue.hxx>
+#include <scmatrix.hxx>
+#include <refupdatecontext.hxx>
+#include <rowheightcontext.hxx>
+#include <compressedarray.hxx>
+#include <vcl/svapp.hxx>
+
+#include <formula/vectortoken.hxx>
+#include <token.hxx>
+
+#include <vector>
+#include <memory>
+
+using ::std::vector;
+
+namespace {
+
+ScProgress* GetProgressBar(
+ SCSIZE nCount, SCSIZE nTotalCount, ScProgress* pOuterProgress, const ScDocument* pDoc)
+{
+ if (nTotalCount < 1000)
+ {
+ // if the total number of rows is less than 1000, don't even bother
+ // with the progress bar because drawing progress bar can be very
+ // expensive especially in GTK.
+ return nullptr;
+ }
+
+ if (pOuterProgress)
+ return pOuterProgress;
+
+ if (nCount > 1)
+ return new ScProgress(
+ pDoc->GetDocumentShell(), ScResId(STR_PROGRESS_HEIGHTING), nTotalCount, true);
+
+ return nullptr;
+}
+
+void GetOptimalHeightsInColumn(
+ sc::RowHeightContext& rCxt, ScColContainer& rCol, SCROW nStartRow, SCROW nEndRow,
+ ScProgress* pProgress, sal_uLong nProgressStart )
+{
+ assert(nStartRow <= nEndRow);
+
+ // first, one time over the whole range
+ // (with the last column in the hope that they most likely still are
+ // on standard format)
+
+
+ rCol.back().GetOptimalHeight(rCxt, nStartRow, nEndRow, 0, 0);
+
+ // from there search for the standard height that is in use in the lower part
+
+ RowHeightsArray& rHeights = rCxt.getHeightArray();
+ sal_uInt16 nMinHeight = rHeights.GetValue(nEndRow);
+ SCSIZE nPos = nEndRow - 1;
+ while ( nPos )
+ {
+ auto aRangeData = rHeights.GetRangeData(nPos-1);
+ if (aRangeData.maValue < nMinHeight)
+ break;
+ nPos = std::max<SCSIZE>(0, aRangeData.mnRow1);
+ }
+
+ const SCROW nMinStart = nPos;
+
+ sal_uInt64 nWeightedCount = nProgressStart + rCol.back().GetWeightedCount(nStartRow, nEndRow);
+ const SCCOL maxCol = rCol.size() - 1; // last col done already above
+ for (SCCOL nCol=0; nCol<maxCol; nCol++)
+ {
+ rCol[nCol].GetOptimalHeight(rCxt, nStartRow, nEndRow, nMinHeight, nMinStart);
+
+ if (pProgress)
+ {
+ nWeightedCount += rCol[nCol].GetWeightedCount(nStartRow, nEndRow);
+ pProgress->SetState( nWeightedCount );
+ }
+ }
+}
+
+struct OptimalHeightsFuncObjBase
+{
+ virtual ~OptimalHeightsFuncObjBase() {}
+ virtual bool operator() (SCROW nStartRow, SCROW nEndRow, sal_uInt16 nHeight, bool bApi) = 0;
+};
+
+struct SetRowHeightOnlyFunc : public OptimalHeightsFuncObjBase
+{
+ ScTable* mpTab;
+ explicit SetRowHeightOnlyFunc(ScTable* pTab) :
+ mpTab(pTab)
+ {}
+
+ virtual bool operator() (SCROW nStartRow, SCROW nEndRow, sal_uInt16 nHeight, bool /* bApi */) override
+ {
+ mpTab->SetRowHeightOnly(nStartRow, nEndRow, nHeight);
+ return false;
+ }
+};
+
+struct SetRowHeightRangeFunc : public OptimalHeightsFuncObjBase
+{
+ ScTable* mpTab;
+ double mnPPTY;
+
+ SetRowHeightRangeFunc(ScTable* pTab, double nPPTY) :
+ mpTab(pTab),
+ mnPPTY(nPPTY)
+ {}
+
+ virtual bool operator() (SCROW nStartRow, SCROW nEndRow, sal_uInt16 nHeight, bool bApi) override
+ {
+ return mpTab->SetRowHeightRange(nStartRow, nEndRow, nHeight, mnPPTY, bApi);
+ }
+};
+
+bool SetOptimalHeightsToRows(
+ sc::RowHeightContext& rCxt,
+ OptimalHeightsFuncObjBase& rFuncObj,
+ ScBitMaskCompressedArray<SCROW, CRFlags>* pRowFlags, SCROW nStartRow, SCROW nEndRow,
+ bool bApi )
+{
+ bool bChanged = false;
+ SCROW nRngStart = 0;
+ SCROW nRngEnd = 0;
+ sal_uInt16 nLast = 0;
+ sal_uInt16 nExtraHeight = rCxt.getExtraHeight();
+ for (SCSIZE i = nStartRow; i <= o3tl::make_unsigned(nEndRow); i++)
+ {
+ size_t nIndex;
+ SCROW nRegionEndRow;
+ CRFlags nRowFlag = pRowFlags->GetValue( i, nIndex, nRegionEndRow );
+ if ( nRegionEndRow > nEndRow )
+ nRegionEndRow = nEndRow;
+ SCSIZE nMoreRows = nRegionEndRow - i; // additional equal rows after first
+
+ bool bAutoSize = !(nRowFlag & CRFlags::ManualSize);
+ if (bAutoSize || rCxt.isForceAutoSize())
+ {
+ if (nExtraHeight)
+ {
+ if (bAutoSize)
+ pRowFlags->SetValue( i, nRegionEndRow, nRowFlag | CRFlags::ManualSize);
+ }
+ else if (!bAutoSize)
+ pRowFlags->SetValue( i, nRegionEndRow, nRowFlag & ~CRFlags::ManualSize);
+
+ for (SCSIZE nInner = i; nInner <= i + nMoreRows; ++nInner)
+ {
+ if (nLast)
+ {
+ SCROW nRangeRowEnd;
+ size_t nTmp;
+ sal_uInt16 nRangeValue = rCxt.getHeightArray().GetValue(nInner, nTmp, nRangeRowEnd);
+ if (nRangeValue + nExtraHeight == nLast)
+ {
+ nRngEnd = std::min<SCSIZE>(i + nMoreRows, nRangeRowEnd);
+ nInner = nRangeRowEnd;
+ }
+ else
+ {
+ bChanged |= rFuncObj(nRngStart, nRngEnd, nLast, bApi);
+ nLast = 0;
+ }
+ }
+ if (!nLast)
+ {
+ nLast = rCxt.getHeightArray().GetValue(nInner) + rCxt.getExtraHeight();
+ nRngStart = nInner;
+ nRngEnd = nInner;
+ }
+ }
+ }
+ else
+ {
+ if (nLast)
+ bChanged |= rFuncObj(nRngStart, nRngEnd, nLast, bApi);
+ nLast = 0;
+ }
+ i += nMoreRows; // already handled - skip
+ }
+ if (nLast)
+ bChanged |= rFuncObj(nRngStart, nRngEnd, nLast, bApi);
+
+ return bChanged;
+}
+
+}
+
+ScTable::ScTable( ScDocument& rDoc, SCTAB nNewTab, const OUString& rNewName,
+ bool bColInfo, bool bRowInfo ) :
+ aCol( rDoc.GetSheetLimits(), INITIALCOLCOUNT ),
+ aName( rNewName ),
+ aCodeName( rNewName ),
+ nLinkRefreshDelay( 0 ),
+ nLinkMode( ScLinkMode::NONE ),
+ aPageStyle( ScResId(STR_STYLENAME_STANDARD) ),
+ nRepeatStartX( SCCOL_REPEAT_NONE ),
+ nRepeatEndX( SCCOL_REPEAT_NONE ),
+ nRepeatStartY( SCROW_REPEAT_NONE ),
+ nRepeatEndY( SCROW_REPEAT_NONE ),
+ mpRowHeights( static_cast<ScFlatUInt16RowSegments*>(nullptr) ),
+ mpHiddenCols(new ScFlatBoolColSegments(rDoc.MaxCol())),
+ mpHiddenRows(new ScFlatBoolRowSegments(rDoc.MaxRow())),
+ mpFilteredCols(new ScFlatBoolColSegments(rDoc.MaxCol())),
+ mpFilteredRows(new ScFlatBoolRowSegments(rDoc.MaxRow())),
+ nTableAreaX( 0 ),
+ nTableAreaY( 0 ),
+ nTableAreaVisibleX( 0 ),
+ nTableAreaVisibleY( 0 ),
+ nTab( nNewTab ),
+ rDocument( rDoc ),
+ pSortCollator( nullptr ),
+ nLockCount( 0 ),
+ aScenarioColor( COL_LIGHTGRAY ),
+ aTabBgColor( COL_AUTO ),
+ nScenarioFlags(ScScenarioFlags::NONE),
+ mpCondFormatList( new ScConditionalFormatList() ),
+ maLOKFreezeCell(-1, -1, nNewTab),
+ bScenario(false),
+ bLayoutRTL(false),
+ bLoadingRTL(false),
+ bPageSizeValid(false),
+ bTableAreaValid(false),
+ bTableAreaVisibleValid(false),
+ bVisible(true),
+ bPendingRowHeights(false),
+ bCalcNotification(false),
+ bGlobalKeepQuery(false),
+ bPrintEntireSheet(true),
+ bActiveScenario(false),
+ mbPageBreaksValid(false),
+ mbForceBreaks(false),
+ bStreamValid(false)
+{
+ aDefaultColData.InitAttrArray(new ScAttrArray(static_cast<SCCOL>(-1), nNewTab, rDoc, nullptr));
+ if (bColInfo)
+ {
+ mpColWidth.reset( new ScCompressedArray<SCCOL, sal_uInt16>( rDocument.MaxCol()+1, STD_COL_WIDTH ) );
+ mpColFlags.reset( new ScBitMaskCompressedArray<SCCOL, CRFlags>( rDocument.MaxCol()+1, CRFlags::NONE ) );
+ }
+
+ if (bRowInfo)
+ {
+ mpRowHeights.reset(new ScFlatUInt16RowSegments(rDocument.MaxRow(), ScGlobal::nStdRowHeight));
+ pRowFlags.reset(new ScBitMaskCompressedArray<SCROW, CRFlags>( rDocument.MaxRow(), CRFlags::NONE));
+ }
+
+ if ( rDocument.IsDocVisible() )
+ {
+ // when a sheet is added to a visible document,
+ // initialize its RTL flag from the system locale
+ bLayoutRTL = ScGlobal::IsSystemRTL();
+ }
+
+ ScDrawLayer* pDrawLayer = rDocument.GetDrawLayer();
+ if (pDrawLayer)
+ {
+ if ( pDrawLayer->ScAddPage( nTab ) ) // sal_False (not inserted) during Undo
+ {
+ pDrawLayer->ScRenamePage( nTab, aName );
+ sal_uLong const nx = o3tl::convert((rDocument.MaxCol()+1) * STD_COL_WIDTH, o3tl::Length::twip, o3tl::Length::mm100);
+ sal_uLong ny = o3tl::convert((rDocument.MaxRow()+1) * ScGlobal::nStdRowHeight, o3tl::Length::twip, o3tl::Length::mm10);
+ pDrawLayer->SetPageSize( static_cast<sal_uInt16>(nTab), Size( nx, ny ), false );
+ }
+ }
+
+ for (SCCOL k=0; k < aCol.size(); k++)
+ aCol[k].Init( k, nTab, rDocument, true );
+}
+
+ScTable::~ScTable() COVERITY_NOEXCEPT_FALSE
+{
+ if (!rDocument.IsInDtorClear())
+ {
+ for (SCCOL nCol = 0; nCol < aCol.size(); ++nCol)
+ {
+ aCol[nCol].FreeNotes();
+ }
+ // In the dtor, don't delete the pages in the wrong order.
+ // (or else nTab does not reflect the page number!)
+ // In ScDocument::Clear is afterwards used from Clear at the Draw Layer to delete everything.
+
+ ScDrawLayer* pDrawLayer = rDocument.GetDrawLayer();
+ if (pDrawLayer)
+ pDrawLayer->ScRemovePage( nTab );
+ }
+
+ pRowFlags.reset();
+ pSheetEvents.reset();
+ pOutlineTable.reset();
+ pSearchText.reset();
+ moRepeatColRange.reset();
+ moRepeatRowRange.reset();
+ pScenarioRanges.reset();
+ mpRangeName.reset();
+ pDBDataNoName.reset();
+ DestroySortCollator();
+}
+
+sal_Int64 ScTable::GetHashCode() const
+{
+ return sal::static_int_cast<sal_Int64>(reinterpret_cast<sal_IntPtr>(this));
+}
+
+void ScTable::SetName( const OUString& rNewName )
+{
+ aName = rNewName;
+ aUpperName.clear(); // invalidated if the name is changed
+
+ // SetStreamValid is handled in ScDocument::RenameTab
+}
+
+const OUString& ScTable::GetUpperName() const
+{
+ if (aUpperName.isEmpty() && !aName.isEmpty())
+ aUpperName = ScGlobal::getCharClass().uppercase(aName);
+ return aUpperName;
+}
+
+void ScTable::SetVisible( bool bVis )
+{
+ if (bVisible != bVis)
+ SetStreamValid(false);
+
+ bVisible = bVis;
+}
+
+void ScTable::SetStreamValid( bool bSet, bool bIgnoreLock )
+{
+ if (!bStreamValid && !bSet)
+ return; // shortcut
+ if ( bIgnoreLock || !rDocument.IsStreamValidLocked() )
+ bStreamValid = bSet;
+}
+
+void ScTable::SetPendingRowHeights( bool bSet )
+{
+ bPendingRowHeights = bSet;
+}
+
+void ScTable::SetLayoutRTL( bool bSet )
+{
+ bLayoutRTL = bSet;
+}
+
+void ScTable::SetLoadingRTL( bool bSet )
+{
+ bLoadingRTL = bSet;
+}
+
+void ScTable::SetTabBgColor(const Color& rColor)
+{
+ if (aTabBgColor != rColor)
+ {
+ // The tab color has changed. Set this table 'modified'.
+ aTabBgColor = rColor;
+ SetStreamValid(false);
+ }
+}
+
+void ScTable::SetScenario( bool bFlag )
+{
+ bScenario = bFlag;
+}
+
+void ScTable::SetLink( ScLinkMode nMode,
+ const OUString& rDoc, const OUString& rFlt, const OUString& rOpt,
+ const OUString& rTab, sal_uLong nRefreshDelay )
+{
+ nLinkMode = nMode;
+ aLinkDoc = rDoc; // File
+ aLinkFlt = rFlt; // Filter
+ aLinkOpt = rOpt; // Filter options
+ aLinkTab = rTab; // Sheet name in source file
+ nLinkRefreshDelay = nRefreshDelay; // refresh delay in seconds, 0==off
+
+ SetStreamValid(false);
+}
+
+sal_uInt16 ScTable::GetOptimalColWidth( SCCOL nCol, OutputDevice* pDev,
+ double nPPTX, double nPPTY,
+ const Fraction& rZoomX, const Fraction& rZoomY,
+ bool bFormula, const ScMarkData* pMarkData,
+ const ScColWidthParam* pParam )
+{
+ if ( nCol >= aCol.size() )
+ return ( STD_COL_WIDTH - STD_EXTRA_WIDTH );
+
+ return aCol[nCol].GetOptimalColWidth( pDev, nPPTX, nPPTY, rZoomX, rZoomY,
+ bFormula, STD_COL_WIDTH - STD_EXTRA_WIDTH, pMarkData, pParam );
+}
+
+tools::Long ScTable::GetNeededSize( SCCOL nCol, SCROW nRow,
+ OutputDevice* pDev,
+ double nPPTX, double nPPTY,
+ const Fraction& rZoomX, const Fraction& rZoomY,
+ bool bWidth, bool bTotalSize, bool bInPrintTwips )
+{
+ if ( nCol >= aCol.size() )
+ return 0;
+
+ ScNeededSizeOptions aOptions;
+ aOptions.bSkipMerged = false; // count merged cells
+ aOptions.bTotalSize = bTotalSize;
+
+ return aCol[nCol].GetNeededSize
+ ( nRow, pDev, nPPTX, nPPTY, rZoomX, rZoomY, bWidth, aOptions, nullptr, bInPrintTwips );
+}
+
+bool ScTable::SetOptimalHeight(
+ sc::RowHeightContext& rCxt, SCROW nStartRow, SCROW nEndRow, bool bApi,
+ ScProgress* pOuterProgress, sal_uInt64 nProgressStart )
+{
+ assert(nStartRow <= nEndRow);
+
+ OSL_ENSURE( rCxt.getExtraHeight() == 0 || rCxt.isForceAutoSize(),
+ "automatic OptimalHeight with Extra" );
+
+ if ( rDocument.IsAdjustHeightLocked() )
+ {
+ return false;
+ }
+
+ SCSIZE nCount = static_cast<SCSIZE>(nEndRow-nStartRow+1);
+
+ ScProgress* pProgress = GetProgressBar(nCount, GetWeightedCount(), pOuterProgress, &rDocument);
+
+ mpRowHeights->enableTreeSearch(false);
+
+ GetOptimalHeightsInColumn(rCxt, aCol, nStartRow, nEndRow, pProgress, nProgressStart);
+
+ SetRowHeightRangeFunc aFunc(this, rCxt.getPPTY());
+ bool bChanged = SetOptimalHeightsToRows(rCxt, aFunc, pRowFlags.get(), nStartRow, nEndRow, bApi);
+
+ if ( pProgress != pOuterProgress )
+ delete pProgress;
+
+ mpRowHeights->enableTreeSearch(true);
+
+ return bChanged;
+}
+
+void ScTable::SetOptimalHeightOnly(
+ sc::RowHeightContext& rCxt, SCROW nStartRow, SCROW nEndRow,
+ ScProgress* pOuterProgress, sal_uInt64 nProgressStart )
+{
+ OSL_ENSURE( rCxt.getExtraHeight() == 0 || rCxt.isForceAutoSize(),
+ "automatic OptimalHeight with Extra" );
+
+ if ( rDocument.IsAdjustHeightLocked() )
+ return;
+
+ SCSIZE nCount = static_cast<SCSIZE>(nEndRow-nStartRow+1);
+
+ ScProgress* pProgress = GetProgressBar(nCount, GetWeightedCount(), pOuterProgress, &rDocument);
+
+ GetOptimalHeightsInColumn(rCxt, aCol, nStartRow, nEndRow, pProgress, nProgressStart);
+
+ SetRowHeightOnlyFunc aFunc(this);
+
+ SetOptimalHeightsToRows(rCxt, aFunc, pRowFlags.get(), nStartRow, nEndRow, true);
+
+ if ( pProgress != pOuterProgress )
+ delete pProgress;
+}
+
+bool ScTable::GetCellArea( SCCOL& rEndCol, SCROW& rEndRow ) const
+{
+ bool bFound = false;
+ SCCOL nMaxX = 0;
+ SCROW nMaxY = 0;
+ for (SCCOL i=0; i<aCol.size(); i++)
+ {
+ if (!aCol[i].IsEmptyData())
+ {
+ bFound = true;
+ nMaxX = i;
+ SCROW nRow = aCol[i].GetLastDataPos();
+ if (nRow > nMaxY)
+ nMaxY = nRow;
+ }
+ if ( aCol[i].HasCellNotes() )
+ {
+ SCROW maxNoteRow = aCol[i].GetCellNotesMaxRow();
+ if (maxNoteRow >= nMaxY)
+ {
+ bFound = true;
+ nMaxY = maxNoteRow;
+ }
+ if (i>nMaxX)
+ {
+ bFound = true;
+ nMaxX = i;
+ }
+ }
+ if (aCol[i].HasSparklines())
+ {
+ SCROW maxSparklineRow = aCol[i].GetSparklinesMaxRow();
+ if (maxSparklineRow >= nMaxY)
+ {
+ bFound = true;
+ nMaxY = maxSparklineRow;
+ }
+ if (i > nMaxX)
+ {
+ bFound = true;
+ nMaxX = i;
+ }
+ }
+ }
+
+ rEndCol = nMaxX;
+ rEndRow = nMaxY;
+ return bFound;
+}
+
+bool ScTable::GetTableArea( SCCOL& rEndCol, SCROW& rEndRow, bool bCalcHiddens) const
+{
+ bool bRet = true; //TODO: remember?
+ if (bCalcHiddens)
+ {
+ if (!bTableAreaValid)
+ {
+ bRet = GetPrintArea(nTableAreaX, nTableAreaY, true, bCalcHiddens);
+ bTableAreaValid = true;
+ }
+ rEndCol = nTableAreaX;
+ rEndRow = nTableAreaY;
+ }
+ else
+ {
+ if (!bTableAreaVisibleValid)
+ {
+ bRet = GetPrintArea(nTableAreaVisibleX, nTableAreaVisibleY, true, bCalcHiddens);
+ bTableAreaVisibleValid = true;
+ }
+ rEndCol = nTableAreaVisibleX;
+ rEndRow = nTableAreaVisibleY;
+ }
+ return bRet;
+}
+
+const SCCOL SC_COLUMNS_STOP = 30;
+
+bool ScTable::GetPrintArea( SCCOL& rEndCol, SCROW& rEndRow, bool bNotes, bool bCalcHiddens ) const
+{
+ bool bFound = false;
+ SCCOL nMaxX = 0;
+ SCROW nMaxY = 0;
+ SCCOL i;
+
+ for (i=0; i<aCol.size(); i++) // Test data
+ {
+ if (bCalcHiddens || !rDocument.ColHidden(i, nTab))
+ {
+ if (!aCol[i].IsEmptyData())
+ {
+ bFound = true;
+ if (i>nMaxX)
+ nMaxX = i;
+ SCROW nColY = aCol[i].GetLastDataPos();
+ if (nColY > nMaxY)
+ nMaxY = nColY;
+ }
+ if (bNotes && aCol[i].HasCellNotes() )
+ {
+ SCROW maxNoteRow = aCol[i].GetCellNotesMaxRow();
+ if (maxNoteRow >= nMaxY)
+ {
+ bFound = true;
+ nMaxY = maxNoteRow;
+ }
+ if (i>nMaxX)
+ {
+ bFound = true;
+ nMaxX = i;
+ }
+ }
+ if (aCol[i].HasSparklines())
+ {
+ SCROW maxSparklineRow = aCol[i].GetSparklinesMaxRow();
+ if (maxSparklineRow >= nMaxY)
+ {
+ bFound = true;
+ nMaxY = maxSparklineRow;
+ }
+ if (i > nMaxX)
+ {
+ bFound = true;
+ nMaxX = i;
+ }
+ }
+ }
+ }
+
+ SCCOL nMaxDataX = nMaxX;
+
+ for (i=0; i<aCol.size(); i++) // Test attribute
+ {
+ if (bCalcHiddens || !rDocument.ColHidden(i, nTab))
+ {
+ SCROW nLastRow;
+ if (aCol[i].GetLastVisibleAttr( nLastRow ))
+ {
+ bFound = true;
+ nMaxX = i;
+ if (nLastRow > nMaxY)
+ nMaxY = nLastRow;
+ }
+ }
+ }
+
+ if (nMaxX == rDocument.MaxCol()) // omit attribute at the right
+ {
+ --nMaxX;
+ while ( nMaxX>0 && aCol[nMaxX].IsVisibleAttrEqual(aCol[nMaxX+1], 0, rDocument.MaxRow()) )
+ --nMaxX;
+ }
+
+ if ( nMaxX < nMaxDataX )
+ {
+ nMaxX = nMaxDataX;
+ }
+ else if ( nMaxX > nMaxDataX )
+ {
+ SCCOL nAttrStartX = nMaxDataX + 1;
+ while ( nAttrStartX < (aCol.size()-1) )
+ {
+ SCCOL nAttrEndX = nAttrStartX;
+ while ( nAttrEndX < (aCol.size()-1) && aCol[nAttrStartX].IsVisibleAttrEqual(aCol[nAttrEndX+1], 0, rDocument.MaxRow()) )
+ ++nAttrEndX;
+ if ( nAttrEndX + 1 - nAttrStartX >= SC_COLUMNS_STOP )
+ {
+ // found equally-formatted columns behind data -> stop before these columns
+ nMaxX = nAttrStartX - 1;
+
+ // also don't include default-formatted columns before that
+ SCROW nDummyRow;
+ while ( nMaxX > nMaxDataX && !aCol[nMaxX].GetLastVisibleAttr( nDummyRow ) )
+ --nMaxX;
+ break;
+ }
+ nAttrStartX = nAttrEndX + 1;
+ }
+ }
+
+ rEndCol = nMaxX;
+ rEndRow = nMaxY;
+ return bFound;
+}
+
+bool ScTable::GetPrintAreaHor( SCROW nStartRow, SCROW nEndRow,
+ SCCOL& rEndCol ) const
+{
+ bool bFound = false;
+ SCCOL nMaxX = 0;
+ SCCOL i;
+
+ for (i=0; i<aCol.size(); i++) // Test attribute
+ {
+ if (aCol[i].HasVisibleAttrIn( nStartRow, nEndRow ))
+ {
+ bFound = true;
+ nMaxX = i;
+ }
+ }
+
+ if (nMaxX == rDocument.MaxCol()) // omit attribute at the right
+ {
+ --nMaxX;
+ while ( nMaxX>0 && aCol[nMaxX].IsVisibleAttrEqual(aCol[nMaxX+1], nStartRow, nEndRow) )
+ --nMaxX;
+ }
+
+ for (i=0; i<aCol.size(); i++) // test the data
+ {
+ if (!aCol[i].IsEmptyData( nStartRow, nEndRow )) //TODO: bNotes ??????
+ {
+ bFound = true;
+ if (i > nMaxX)
+ nMaxX = i;
+ }
+ else if (aCol[i].HasSparklines())
+ {
+ if (i > nMaxX)
+ {
+ bFound = true;
+ nMaxX = i;
+ }
+ }
+ }
+
+ rEndCol = nMaxX;
+ return bFound;
+}
+
+bool ScTable::GetPrintAreaVer( SCCOL nStartCol, SCCOL nEndCol,
+ SCROW& rEndRow, bool bNotes ) const
+{
+ nStartCol = std::min<SCCOL>( nStartCol, aCol.size()-1 );
+ nEndCol = std::min<SCCOL>( nEndCol, aCol.size()-1 );
+ bool bFound = false;
+ SCROW nMaxY = 0;
+ SCCOL i;
+
+ for (i=nStartCol; i<=nEndCol; i++) // Test attribute
+ {
+ SCROW nLastRow;
+ if (aCol[i].GetLastVisibleAttr( nLastRow ))
+ {
+ bFound = true;
+ if (nLastRow > nMaxY)
+ nMaxY = nLastRow;
+ }
+ }
+
+ for (i=nStartCol; i<=nEndCol; i++) // Test data
+ {
+ if (!aCol[i].IsEmptyData())
+ {
+ bFound = true;
+ SCROW nColY = aCol[i].GetLastDataPos();
+ if (nColY > nMaxY)
+ nMaxY = nColY;
+ }
+ if (bNotes && aCol[i].HasCellNotes() )
+ {
+ SCROW maxNoteRow =aCol[i].GetCellNotesMaxRow();
+ if (maxNoteRow > nMaxY)
+ {
+ bFound = true;
+ nMaxY = maxNoteRow;
+ }
+ }
+ if (aCol[i].HasSparklines())
+ {
+ SCROW maxNoteRow = aCol[i].GetSparklinesMaxRow();
+ if (maxNoteRow > nMaxY)
+ {
+ bFound = true;
+ nMaxY = maxNoteRow;
+ }
+ }
+ }
+
+ rEndRow = nMaxY;
+ return bFound;
+}
+
+bool ScTable::GetDataStart( SCCOL& rStartCol, SCROW& rStartRow ) const
+{
+ bool bFound = false;
+ SCCOL nMinX = aCol.size()-1;
+ SCROW nMinY = rDocument.MaxRow();
+ SCCOL i;
+
+ for (i=0; i<aCol.size(); i++) // Test attribute
+ {
+ SCROW nFirstRow;
+ if (aCol[i].GetFirstVisibleAttr( nFirstRow ))
+ {
+ if (!bFound)
+ nMinX = i;
+ bFound = true;
+ if (nFirstRow < nMinY)
+ nMinY = nFirstRow;
+ }
+ }
+
+ if (nMinX == 0) // omit attribute at the right
+ {
+ if ( aCol.size() > 1 && aCol[0].IsVisibleAttrEqual(aCol[1], 0, rDocument.MaxRow())) // no single ones
+ {
+ ++nMinX;
+ while ( nMinX<(aCol.size()-1) && aCol[nMinX].IsVisibleAttrEqual(aCol[nMinX-1], 0, rDocument.MaxRow()))
+ ++nMinX;
+ }
+ }
+
+ bool bDatFound = false;
+ for (i=0; i<aCol.size(); i++) // Test data
+ {
+ if (!aCol[i].IsEmptyData())
+ {
+ if (!bDatFound && i<nMinX)
+ nMinX = i;
+ bFound = bDatFound = true;
+ SCROW nRow = aCol[i].GetFirstDataPos();
+ if (nRow < nMinY)
+ nMinY = nRow;
+ }
+ if ( aCol[i].HasCellNotes() )
+ {
+ SCROW minNoteRow = aCol[i].GetCellNotesMinRow();
+ if (minNoteRow <= nMinY)
+ {
+ bFound = true;
+ nMinY = minNoteRow;
+ }
+ if (i<nMinX)
+ {
+ bFound = true;
+ nMinX = i;
+ }
+ }
+ if (aCol[i].HasSparklines())
+ {
+ SCROW minSparkline = aCol[i].GetSparklinesMinRow();
+ if (minSparkline <= nMinY)
+ {
+ bFound = true;
+ nMinY = minSparkline;
+ }
+ if (i < nMinX)
+ {
+ bFound = true;
+ nMinX = i;
+ }
+ }
+ }
+ rStartCol = nMinX;
+ rStartRow = nMinY;
+ return bFound;
+}
+
+void ScTable::GetDataArea( SCCOL& rStartCol, SCROW& rStartRow, SCCOL& rEndCol, SCROW& rEndRow,
+ bool bIncludeOld, bool bOnlyDown ) const
+{
+ // return the smallest area containing at least all contiguous cells having data. This area
+ // is a square containing also empty cells. It may shrink or extend the area given as input
+ // Flags as modifiers:
+ //
+ // bIncludeOld = true ensure that the returned area contains at least the initial area,
+ // independently of the emptiness of rows / columns (i.e. does not allow shrinking)
+ // bOnlyDown = true means extend / shrink the inputted area only down, i.e modify only rEndRow
+
+ rStartCol = std::min<SCCOL>( rStartCol, aCol.size()-1 );
+ rEndCol = std::min<SCCOL>( rEndCol, aCol.size()-1 );
+
+ bool bLeft = false;
+ bool bRight = false;
+ bool bTop = false;
+ bool bBottom = false;
+ bool bChanged = false;
+
+ // We need to cache sc::ColumnBlockConstPosition per each column.
+ std::vector< sc::ColumnBlockConstPosition > blockPos( rEndCol + 1 );
+ for( SCCOL i = 0; i <= rEndCol; ++i )
+ aCol[ i ].InitBlockPosition( blockPos[ i ] );
+
+ do
+ {
+ bChanged = false;
+
+ if (!bOnlyDown)
+ {
+ SCROW nStart = rStartRow;
+ SCROW nEnd = rEndRow;
+ if (nStart>0) --nStart;
+ if (nEnd<rDocument.MaxRow()) ++nEnd;
+
+ if (rEndCol < (aCol.size()-1))
+ if (!aCol[rEndCol+1].IsEmptyData(nStart,nEnd))
+ {
+ assert( int( blockPos.size()) == rEndCol + 1 );
+ ++rEndCol;
+ blockPos.resize( blockPos.size() + 1 );
+ aCol[ rEndCol ].InitBlockPosition( blockPos[ rEndCol ] );
+ bChanged = true;
+ bRight = true;
+ }
+
+ if (rStartCol > 0)
+ if (!aCol[rStartCol-1].IsEmptyData(nStart,nEnd))
+ {
+ --rStartCol;
+ bChanged = true;
+ bLeft = true;
+ }
+
+ if (rStartRow > 0)
+ {
+ SCROW nTest = rStartRow-1;
+ bool needExtend = false;
+ for ( SCCOL i = rStartCol; i<=rEndCol && !needExtend; i++)
+ if (aCol[i].HasDataAt(blockPos[i], nTest))
+ needExtend = true;
+ if (needExtend)
+ {
+ --rStartRow;
+ bChanged = true;
+ bTop = true;
+ }
+ }
+ }
+
+ if (rEndRow < rDocument.MaxRow())
+ {
+ SCROW nTest = rEndRow+1;
+ bool needExtend = false;
+ for ( SCCOL i = rStartCol; i<=rEndCol && !needExtend; i++)
+ if (aCol[i].HasDataAt(blockPos[ i ], nTest))
+ needExtend = true;
+ if (needExtend)
+ {
+ ++rEndRow;
+ bChanged = true;
+ bBottom = true;
+ }
+ }
+ }
+ while( bChanged );
+
+ if ( !bIncludeOld && !bOnlyDown )
+ {
+ if ( !bLeft )
+ while ( rStartCol < rEndCol && rStartCol < (aCol.size()-1) && aCol[rStartCol].IsEmptyData(rStartRow,rEndRow) )
+ ++rStartCol;
+
+ if ( !bRight )
+ while ( rEndCol > 0 && rStartCol < rEndCol && aCol[rEndCol].IsEmptyData(rStartRow,rEndRow) )
+ --rEndCol;
+
+ if ( !bTop && rStartRow < rDocument.MaxRow() && rStartRow < rEndRow )
+ {
+ bool bShrink = true;
+ do
+ {
+ for ( SCCOL i = rStartCol; i<=rEndCol && bShrink; i++)
+ if (aCol[i].HasDataAt(rStartRow))
+ bShrink = false;
+ if (bShrink)
+ ++rStartRow;
+ } while (bShrink && rStartRow < rDocument.MaxRow() && rStartRow < rEndRow);
+ }
+ }
+
+ if ( !bIncludeOld )
+ {
+ if ( !bBottom && rEndRow > 0 && rStartRow < rEndRow )
+ {
+ SCROW nLastDataRow = GetLastDataRow( rStartCol, rEndCol, rEndRow);
+ if (nLastDataRow < rEndRow)
+ rEndRow = std::max( rStartRow, nLastDataRow);
+ }
+ }
+}
+
+bool ScTable::GetDataAreaSubrange( ScRange& rRange ) const
+{
+ SCCOL nCol1 = rRange.aStart.Col(), nCol2 = rRange.aEnd.Col();
+
+ if ( nCol1 >= aCol.size() )
+ return false;
+
+ nCol2 = std::min<SCCOL>( nCol2, aCol.size()-1 );
+
+ SCROW nRow1 = rRange.aStart.Row(), nRow2 = rRange.aEnd.Row();
+
+ SCCOL nFirstNonEmptyCol = -1, nLastNonEmptyCol = -1;
+ SCROW nRowStart = nRow2, nRowEnd = nRow1;
+
+ for ( SCCOL nCol = nCol1; nCol <= nCol2; ++nCol )
+ {
+ SCROW nRowStartThis = nRow1, nRowEndThis = nRow2;
+ bool bTrimmed = aCol[nCol].TrimEmptyBlocks(nRowStartThis, nRowEndThis);
+ if ( bTrimmed )
+ {
+ if ( nFirstNonEmptyCol == -1 )
+ nFirstNonEmptyCol = nCol;
+ nLastNonEmptyCol = nCol;
+
+ nRowStart = std::min<SCROW>(nRowStart, nRowStartThis);
+ nRowEnd = std::max<SCROW>(nRowEnd, nRowEndThis);
+ }
+ }
+
+ if ( nFirstNonEmptyCol == -1 )
+ return false;
+
+ assert(nFirstNonEmptyCol <= nLastNonEmptyCol);
+ assert(nRowStart <= nRowEnd);
+
+ rRange.aStart.Set(nFirstNonEmptyCol, nRowStart, rRange.aStart.Tab());
+ rRange.aEnd.Set(nLastNonEmptyCol, nRowEnd, rRange.aEnd.Tab());
+
+ return true;
+}
+
+bool ScTable::ShrinkToUsedDataArea( bool& o_bShrunk, SCCOL& rStartCol, SCROW& rStartRow,
+ SCCOL& rEndCol, SCROW& rEndRow, bool bColumnsOnly, bool bStickyTopRow, bool bStickyLeftCol,
+ ScDataAreaExtras* pDataAreaExtras ) const
+{
+ rStartCol = std::min<SCCOL>( rStartCol, aCol.size()-1 );
+ // check for rEndCol is done below.
+
+ o_bShrunk = false;
+
+ PutInOrder( rStartCol, rEndCol);
+ PutInOrder( rStartRow, rEndRow);
+ if (rStartCol < 0)
+ {
+ rStartCol = 0;
+ o_bShrunk = true;
+ }
+ if (rStartRow < 0)
+ {
+ rStartRow = 0;
+ o_bShrunk = true;
+ }
+ if (rEndCol >= aCol.size())
+ {
+ rEndCol = aCol.size()-1;
+ o_bShrunk = true;
+ }
+ if (rEndRow > rDocument.MaxRow())
+ {
+ rEndRow = rDocument.MaxRow();
+ o_bShrunk = true;
+ }
+
+ while (rStartCol < rEndCol)
+ {
+ if (aCol[rEndCol].IsEmptyData( rStartRow, rEndRow))
+ {
+ if (pDataAreaExtras && pDataAreaExtras->mnEndCol < rEndCol)
+ {
+ // Check in order of likeliness.
+ if ( (pDataAreaExtras->mbCellFormats
+ && aCol[rEndCol].GetPatternCount( rStartRow, rEndRow) > 1
+ && aCol[rEndCol].HasVisibleAttrIn( rStartRow, rEndRow)) ||
+ (pDataAreaExtras->mbCellNotes
+ && !aCol[rEndCol].IsNotesEmptyBlock( rStartRow, rEndRow)) ||
+ (pDataAreaExtras->mbCellDrawObjects
+ && !aCol[rEndCol].IsDrawObjectsEmptyBlock( rStartRow, rEndRow)))
+ pDataAreaExtras->mnEndCol = rEndCol;
+ }
+
+ --rEndCol;
+ o_bShrunk = true;
+ }
+ else
+ break; // while
+ }
+
+ if (!bStickyLeftCol)
+ {
+ while (rStartCol < rEndCol)
+ {
+ if (aCol[rStartCol].IsEmptyData( rStartRow, rEndRow))
+ {
+ if (pDataAreaExtras && pDataAreaExtras->mnStartCol > rStartCol)
+ {
+ // Check in order of likeliness.
+ if ( (pDataAreaExtras->mbCellFormats
+ && aCol[rStartCol].GetPatternCount( rStartRow, rEndRow) > 1
+ && aCol[rStartCol].HasVisibleAttrIn( rStartRow, rEndRow)) ||
+ (pDataAreaExtras->mbCellNotes
+ && !aCol[rStartCol].IsNotesEmptyBlock( rStartRow, rEndRow)) ||
+ (pDataAreaExtras->mbCellDrawObjects
+ && !aCol[rStartCol].IsDrawObjectsEmptyBlock( rStartRow, rEndRow)))
+ pDataAreaExtras->mnStartCol = rStartCol;
+ }
+
+ ++rStartCol;
+ o_bShrunk = true;
+ }
+ else
+ break; // while
+ }
+ }
+
+ if (!bColumnsOnly)
+ {
+ while (rStartRow < rEndRow)
+ {
+ SCROW nLastDataRow = GetLastDataRow(rStartCol, rEndCol, rEndRow, pDataAreaExtras);
+ if (0 <= nLastDataRow && nLastDataRow < rEndRow)
+ {
+ rEndRow = std::max( rStartRow, nLastDataRow);
+ o_bShrunk = true;
+ }
+ else
+ break; // while
+ }
+
+ if (!bStickyTopRow)
+ {
+ while (rStartRow < rEndRow)
+ {
+ bool bFound = false;
+ for (SCCOL i=rStartCol; i<=rEndCol && !bFound; i++)
+ {
+ if (aCol[i].HasDataAt(rStartRow, pDataAreaExtras))
+ bFound = true;
+ }
+ if (!bFound)
+ {
+ ++rStartRow;
+ o_bShrunk = true;
+ }
+ else
+ break; // while
+ }
+ }
+ }
+
+ return rStartCol != rEndCol || (bColumnsOnly ?
+ !aCol[rStartCol].IsEmptyData( rStartRow, rEndRow) :
+ (rStartRow != rEndRow ||
+ aCol[rStartCol].HasDataAt( rStartRow, pDataAreaExtras)));
+}
+
+SCROW ScTable::GetLastDataRow( SCCOL nCol1, SCCOL nCol2, SCROW nLastRow, ScDataAreaExtras* pDataAreaExtras ) const
+{
+ if ( !IsColValid( nCol1 ) || !ValidCol( nCol2 ) )
+ return -1;
+
+ nCol2 = std::min<SCCOL>( nCol2, aCol.size() - 1 );
+
+ SCROW nNewLastRow = 0;
+ for (SCCOL i = nCol1; i <= nCol2; ++i)
+ {
+ SCROW nThis = aCol[i].GetLastDataPos(nLastRow, pDataAreaExtras);
+ if (nNewLastRow < nThis)
+ nNewLastRow = nThis;
+ }
+
+ return nNewLastRow;
+}
+
+bool ScTable::IsEmptyData( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow ) const
+{
+ for( SCCOL col : GetAllocatedColumnsRange( nStartCol, nEndCol ))
+ if( !aCol[col].IsEmptyData( nStartRow, nEndRow ))
+ return false;
+ return true;
+}
+
+SCSIZE ScTable::GetEmptyLinesInBlock( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow, ScDirection eDir ) const
+{
+ SCCOL nStartColOrig = nStartCol;
+ SCCOL nEndColOrig = nEndCol;
+ nStartCol = std::min<SCCOL>( nStartCol, aCol.size()-1 );
+ nEndCol = std::min<SCCOL>( nEndCol, aCol.size()-1 );
+
+ // The region is not allocated and does not contain any data.
+ if ( nStartColOrig != nStartCol )
+ return ( ((eDir == DIR_BOTTOM) || (eDir == DIR_TOP)) ?
+ static_cast<SCSIZE>(nEndRow - nStartRow + 1) :
+ static_cast<SCSIZE>(nEndColOrig - nStartColOrig + 1) );
+
+ SCSIZE nGapRight = static_cast<SCSIZE>(nEndColOrig - nEndCol);
+ SCSIZE nCount = 0;
+ SCCOL nCol;
+ if ((eDir == DIR_BOTTOM) || (eDir == DIR_TOP))
+ {
+ nCount = static_cast<SCSIZE>(nEndRow - nStartRow + 1);
+ for (nCol = nStartCol; nCol <= nEndCol; nCol++)
+ nCount = std::min(nCount, aCol[nCol].GetEmptyLinesInBlock(nStartRow, nEndRow, eDir));
+ }
+ else if (eDir == DIR_RIGHT)
+ {
+ nCol = nEndCol;
+ while ((nCol >= nStartCol) &&
+ aCol[nCol].IsEmptyData(nStartRow, nEndRow))
+ {
+ nCount++;
+ nCol--;
+ }
+ nCount += nGapRight;
+ }
+ else
+ {
+ nCol = nStartCol;
+ while ((nCol <= nEndCol) && aCol[nCol].IsEmptyData(nStartRow, nEndRow))
+ {
+ nCount++;
+ nCol++;
+ }
+
+ // If the area between nStartCol and nEndCol are empty,
+ // add the count of unallocated columns on the right.
+ if ( nCol > nEndCol )
+ nCount += nGapRight;
+ }
+ return nCount;
+}
+
+bool ScTable::IsEmptyLine( SCROW nRow, SCCOL nStartCol, SCCOL nEndCol ) const
+{
+ // The range of columns are unallocated hence empty.
+ if ( nStartCol >= aCol.size() )
+ return true;
+
+ nEndCol = std::min<SCCOL>( nEndCol, aCol.size()-1 );
+
+ for (SCCOL i=nStartCol; i<=nEndCol; i++)
+ if (aCol[i].HasDataAt(nRow))
+ return false;
+ return true;
+}
+
+void ScTable::LimitChartArea( SCCOL& rStartCol, SCROW& rStartRow, SCCOL& rEndCol, SCROW& rEndRow ) const
+{
+ rStartCol = std::min<SCCOL>( rStartCol, aCol.size()-1 );
+ rEndCol = std::min<SCCOL>( rEndCol, aCol.size()-1 );
+
+ while ( rStartCol<rEndCol && aCol[rStartCol].IsEmptyData(rStartRow,rEndRow) )
+ ++rStartCol;
+
+ while ( rStartCol<rEndCol && aCol[rEndCol].IsEmptyData(rStartRow,rEndRow) )
+ --rEndCol;
+
+ while ( rStartRow<rEndRow && IsEmptyLine(rStartRow, rStartCol, rEndCol) )
+ ++rStartRow;
+
+ // Optimised loop for finding the bottom of the area, can be costly in large
+ // spreadsheets.
+ SCROW lastDataPos = 0;
+ for (SCCOL i=rStartCol; i<=rEndCol; i++)
+ lastDataPos = std::max(lastDataPos, aCol[i].GetLastDataPos());
+ // reduce EndRow to the last row with data
+ rEndRow = std::min(rEndRow, lastDataPos);
+ // but make sure EndRow is >= StartRow
+ rEndRow = std::max(rStartRow, rEndRow);
+}
+
+SCCOL ScTable::FindNextVisibleCol( SCCOL nCol, bool bRight ) const
+{
+ if(bRight)
+ {
+ nCol++;
+ SCCOL nEnd = 0;
+ bool bHidden = rDocument.ColHidden(nCol, nTab, nullptr, &nEnd);
+ if(bHidden)
+ nCol = nEnd +1;
+
+ return std::min<SCCOL>(rDocument.MaxCol(), nCol);
+ }
+ else
+ {
+ nCol--;
+ SCCOL nStart = rDocument.MaxCol();
+ bool bHidden = rDocument.ColHidden(nCol, nTab, &nStart);
+ if(bHidden)
+ nCol = nStart - 1;
+
+ return std::max<SCCOL>(0, nCol);
+ }
+}
+
+SCCOL ScTable::FindNextVisibleColWithContent( SCCOL nCol, bool bRight, SCROW nRow ) const
+{
+ const SCCOL nLastCol = aCol.size() - 1;
+ if(bRight)
+ {
+ // If nCol is the last allocated column index, there won't be any content to its right.
+ // To maintain the original return behaviour, return rDocument.MaxCol().
+ if(nCol >= nLastCol)
+ return rDocument.MaxCol();
+
+ do
+ {
+ nCol++;
+ SCCOL nEndCol = 0;
+ bool bHidden = rDocument.ColHidden( nCol, nTab, nullptr, &nEndCol );
+ if(bHidden)
+ {
+ nCol = nEndCol +1;
+ // Can end search early as there is no data after nLastCol.
+ // For nCol == nLastCol, it may still have data so don't want to return rDocument.MaxCol().
+ if(nCol > nLastCol)
+ return rDocument.MaxCol();
+ }
+
+ if(aCol[nCol].HasVisibleDataAt(nRow))
+ return nCol;
+ }
+ while(nCol < nLastCol); // Stop search as soon as the last allocated column is searched.
+
+ return rDocument.MaxCol();
+ }
+ else
+ {
+ // If nCol is in the unallocated range [nLastCol+1, rDocument.MaxCol()], then move it directly to nLastCol
+ // as there is no data in the unallocated range. This also makes the search faster and avoids
+ // the need for more range checks in the loop below.
+ if ( nCol > nLastCol )
+ nCol = nLastCol;
+
+ if(nCol == 0)
+ return 0;
+
+ do
+ {
+ nCol--;
+ SCCOL nStartCol = rDocument.MaxCol();
+ bool bHidden = rDocument.ColHidden( nCol, nTab, &nStartCol );
+ if(bHidden)
+ {
+ nCol = nStartCol -1;
+ if(nCol <= 0)
+ return 0;
+ }
+
+ if(aCol[nCol].HasVisibleDataAt(nRow))
+ return nCol;
+ }
+ while(nCol > 0);
+
+ return 0;
+ }
+}
+
+void ScTable::FindAreaPos( SCCOL& rCol, SCROW& rRow, ScMoveDirection eDirection ) const
+{
+ const SCCOL nLastCol = aCol.size() - 1;
+
+ if (eDirection == SC_MOVE_LEFT || eDirection == SC_MOVE_RIGHT)
+ {
+ SCCOL nNewCol = rCol;
+ bool bThere = ( nNewCol <= nLastCol ) && aCol[nNewCol].HasVisibleDataAt(rRow);
+ bool bRight = (eDirection == SC_MOVE_RIGHT);
+ if (bThere)
+ {
+ if(nNewCol >= rDocument.MaxCol() && eDirection == SC_MOVE_RIGHT)
+ return;
+ else if(nNewCol == 0 && eDirection == SC_MOVE_LEFT)
+ return;
+
+ SCCOL nNextCol = FindNextVisibleCol( nNewCol, bRight );
+
+ if( nNextCol <= nLastCol && aCol[nNextCol].HasVisibleDataAt(rRow) )
+ {
+ bool bFound = false;
+ nNewCol = nNextCol;
+ do
+ {
+ nNextCol = FindNextVisibleCol( nNewCol, bRight );
+ if( nNextCol <= nLastCol && aCol[nNextCol].HasVisibleDataAt(rRow) )
+ nNewCol = nNextCol;
+ else
+ bFound = true;
+ }
+ while(!bFound && nNextCol > 0 && nNextCol < rDocument.MaxCol());
+ }
+ else
+ {
+ nNewCol = FindNextVisibleColWithContent(nNewCol, bRight, rRow);
+ }
+ }
+ else
+ {
+ nNewCol = FindNextVisibleColWithContent(nNewCol, bRight, rRow);
+ }
+
+ if (nNewCol<0)
+ nNewCol=0;
+ if (nNewCol>rDocument.MaxCol())
+ nNewCol=rDocument.MaxCol();
+ rCol = nNewCol;
+ }
+ else
+ {
+ if ( rCol <= nLastCol )
+ aCol[rCol].FindDataAreaPos(rRow,eDirection == SC_MOVE_DOWN);
+ else
+ {
+ // The cell (rCol, rRow) is equivalent to an empty cell (although not allocated).
+ // Set rRow to 0 or rDocument.MaxRow() depending on eDirection to maintain the behaviour of
+ // ScColumn::FindDataAreaPos() when the given column is empty.
+ rRow = ( eDirection == SC_MOVE_DOWN ) ? rDocument.MaxRow() : 0;
+ }
+ }
+}
+
+bool ScTable::ValidNextPos( SCCOL nCol, SCROW nRow, const ScMarkData& rMark,
+ bool bMarked, bool bUnprotected ) const
+{
+ if (!ValidCol(nCol) || !ValidRow(nRow))
+ return false;
+
+ if (rDocument.HasAttrib(nCol, nRow, nTab, nCol, nRow, nTab, HasAttrFlags::Overlapped))
+ // Skip an overlapped cell.
+ return false;
+
+ if (bMarked && !rMark.IsCellMarked(nCol,nRow))
+ return false;
+
+ /* TODO: for cursor movement *only* this should even take the protection
+ * options (select locked, select unlocked) into account, see
+ * ScTabView::SkipCursorHorizontal() and ScTabView::SkipCursorVertical(). */
+ if (bUnprotected && rDocument.HasAttrib(nCol, nRow, nTab, nCol, nRow, nTab, HasAttrFlags::Protected))
+ return false;
+
+ if (bMarked || bUnprotected) //TODO: also in other case ???
+ {
+ // Hidden cells must be skipped, as the cursor would end up on the next cell
+ // even if it is protected or not marked.
+ //TODO: control per Extra-Parameter, only for Cursor movement ???
+
+ if (RowHidden(nRow))
+ return false;
+
+ if (ColHidden(nCol))
+ return false;
+ }
+
+ return true;
+}
+
+// Skips the current cell if it is Hidden, Overlapped or Protected and Sheet is Protected
+bool ScTable::SkipRow( const SCCOL nCol, SCROW& rRow, const SCROW nMovY,
+ const ScMarkData& rMark, const bool bUp, const SCROW nUsedY,
+ const bool bMarked, const bool bSheetProtected ) const
+{
+ if ( !ValidRow( rRow ))
+ return false;
+
+ if (bSheetProtected && rDocument.HasAttrib( nCol, rRow, nTab, nCol, rRow, nTab, HasAttrFlags::Protected))
+ {
+ if ( rRow > nUsedY )
+ rRow = (bUp ? nUsedY : rDocument.MaxRow() + nMovY);
+ else
+ rRow += nMovY;
+
+ if (bMarked)
+ rRow = rMark.GetNextMarked( nCol, rRow, bUp );
+
+ return true;
+ }
+ else
+ {
+ bool bRowHidden = RowHidden( rRow );
+ bool bOverlapped = rDocument.HasAttrib( nCol, rRow, nTab, nCol, rRow, nTab, HasAttrFlags::Overlapped );
+
+ if ( bRowHidden || bOverlapped )
+ {
+ rRow += nMovY;
+ if (bMarked)
+ rRow = rMark.GetNextMarked( nCol, rRow, bUp );
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void ScTable::GetNextPos( SCCOL& rCol, SCROW& rRow, SCCOL nMovX, SCROW nMovY,
+ bool bMarked, bool bUnprotected, const ScMarkData& rMark, SCCOL nTabStartCol ) const
+{
+ // Ensure bMarked is set only if there is a mark.
+ assert( !bMarked || rMark.IsMarked() || rMark.IsMultiMarked());
+
+ const bool bSheetProtected = IsProtected();
+
+ if ( bUnprotected && !bSheetProtected ) // Is sheet really protected?
+ bUnprotected = false;
+
+ SCCOL nCol = rCol + nMovX;
+ SCROW nRow = rRow + nMovY;
+
+ SCCOL nStartCol, nEndCol;
+ SCROW nStartRow, nEndRow;
+ if (bMarked)
+ {
+ ScRange aRange( ScAddress::UNINITIALIZED);
+ if (rMark.IsMarked())
+ aRange = rMark.GetMarkArea();
+ else if (rMark.IsMultiMarked())
+ aRange = rMark.GetMultiMarkArea();
+ else
+ {
+ // Covered by assert() above, but for NDEBUG build.
+ if (ValidColRow(nCol,nRow))
+ {
+ rCol = nCol;
+ rRow = nRow;
+ }
+ return;
+ }
+ nStartCol = aRange.aStart.Col();
+ nStartRow = aRange.aStart.Row();
+ nEndCol = aRange.aEnd.Col();
+ nEndRow = aRange.aEnd.Row();
+ }
+ else if (bUnprotected)
+ {
+ nStartCol = 0;
+ nStartRow = 0;
+ nEndCol = rCol;
+ nEndRow = rRow;
+ rDocument.GetPrintArea( nTab, nEndCol, nEndRow, true );
+ // Add some cols/rows to the print area (which is "content or
+ // visually different from empty") to enable travelling through
+ // protected forms with empty cells and no visual indicator.
+ // 42 might be good enough and not too much...
+ nEndCol = std::min<SCCOL>( nEndCol+42, rDocument.MaxCol());
+ nEndRow = std::min<SCROW>( nEndRow+42, rDocument.MaxRow());
+ }
+ else
+ {
+ // Invalid values show up for instance for Tab, when nothing is
+ // selected and not protected (left / right edge), then leave values
+ // unchanged.
+ if (ValidColRow(nCol,nRow))
+ {
+ rCol = nCol;
+ rRow = nRow;
+ }
+
+ // Caller ensures actually moving nMovY to jump to prev/next row's
+ // start col.
+ if (nTabStartCol != SC_TABSTART_NONE)
+ rCol = nTabStartCol;
+
+ return;
+ }
+
+ if ( nMovY && (bMarked || bUnprotected))
+ {
+ do
+ {
+ const bool bUp = (nMovY < 0);
+ const SCCOL nColAdd = (bUp ? -1 : 1);
+
+ if (bMarked)
+ nRow = rMark.GetNextMarked( nCol, nRow, bUp );
+
+ if (nTabStartCol != SC_TABSTART_NONE)
+ {
+ /* NOTE: If current rCol < nTabStartCol when going down, there
+ * is no way to detect if the previous Tab wrapped around to
+ * the next row or if it was a Shift+Tab going backwards. The
+ * result after a wrap is an odd jump to the next row's
+ * nTabStartCol, which is logical though and always has been
+ * the case. Similar for rCol > nTabStartCol when going up.
+ * Related, it would be nice to limit advancing the position
+ * within bounds even if another wrap would occur, but again we
+ * can't tell if previously Tab or Shift+Tab was used, so we
+ * don't know if it would be nTabStartCol to nEndCol (for Tab)
+ * or nStartCol to nTabStartCol (for Shift+Tab). */
+
+ // Continue moving horizontally.
+ nMovX = nColAdd;
+ nCol = nTabStartCol;
+ break; // do
+ }
+
+ while ( SkipRow( nCol, nRow, nMovY, rMark, bUp, nEndRow, bMarked, bSheetProtected ))
+ ;
+
+ sal_uInt16 nWrap = 0;
+ while ( nRow < nStartRow || nRow > nEndRow )
+ {
+ nCol += nColAdd;
+
+ while (nStartCol <= nCol && nCol <= nEndCol && ValidCol(nCol) && ColHidden(nCol))
+ nCol += nColAdd; // skip hidden cols
+
+ if (nCol < nStartCol)
+ {
+ nCol = nEndCol;
+
+ if (++nWrap >= 2)
+ return;
+ }
+ else if (nCol > nEndCol)
+ {
+ nCol = nStartCol;
+
+ if (++nWrap >= 2)
+ return;
+ }
+ if (nRow < nStartRow)
+ nRow = nEndRow;
+ else if (nRow > nEndRow)
+ nRow = nStartRow;
+
+ if (bMarked)
+ nRow = rMark.GetNextMarked( nCol, nRow, bUp );
+
+ while ( SkipRow( nCol, nRow, nMovY, rMark, bUp, nEndRow, bMarked, bSheetProtected ))
+ ;
+ }
+ } while (false);
+ }
+
+ if ( nMovX && ( bMarked || bUnprotected ) )
+ {
+ // wrap initial skip counting:
+ if (nCol < nStartCol)
+ {
+ nCol = nEndCol;
+ --nRow;
+ if (nRow < nStartRow)
+ nRow = nEndRow;
+ }
+ if (nCol > nEndCol)
+ {
+ nCol = nStartCol;
+ ++nRow;
+ if (nRow > nEndRow)
+ nRow = nStartRow;
+ }
+
+ if ( !ValidNextPos(nCol, nRow, rMark, bMarked, bUnprotected) )
+ {
+ const SCCOL nColCount = nEndCol - nStartCol + 1;
+ std::unique_ptr<SCROW[]> pNextRows( new SCROW[nColCount]);
+ const SCCOL nLastCol = aCol.size() - 1;
+ const bool bUp = (nMovX < 0); // Moving left also means moving up in rows.
+ const SCROW nRowAdd = (bUp ? -1 : 1);
+ sal_uInt16 nWrap = 0;
+
+ if (bUp)
+ {
+ for (SCCOL i = 0; i < nColCount; ++i)
+ pNextRows[i] = (i + nStartCol > nCol) ? (nRow + nRowAdd) : nRow;
+ }
+ else
+ {
+ for (SCCOL i = 0; i < nColCount; ++i)
+ pNextRows[i] = (i + nStartCol < nCol) ? (nRow + nRowAdd) : nRow;
+ }
+ do
+ {
+ SCROW nNextRow = pNextRows[nCol - nStartCol] + nRowAdd;
+ if ( bMarked )
+ nNextRow = rMark.GetNextMarked( nCol, nNextRow, bUp );
+ if ( bUnprotected )
+ nNextRow = ( nCol <= nLastCol ) ? aCol[nCol].GetNextUnprotected( nNextRow, bUp ) :
+ aDefaultColData.GetNextUnprotected( nNextRow, bUp );
+ pNextRows[nCol - nStartCol] = nNextRow;
+
+ if (bUp)
+ {
+ SCROW nMaxRow = nStartRow - 1;
+ for (SCCOL i = 0; i < nColCount; ++i)
+ {
+ if (pNextRows[i] >= nMaxRow) // when two equal the right one
+ {
+ nMaxRow = pNextRows[i];
+ nCol = i + nStartCol;
+ }
+ }
+ nRow = nMaxRow;
+
+ if ( nRow < nStartRow )
+ {
+ if (++nWrap >= 2)
+ return;
+ nCol = nEndCol;
+ nRow = nEndRow;
+ for (SCCOL i = 0; i < nColCount; ++i)
+ pNextRows[i] = nEndRow; // do it all over again
+ }
+ }
+ else
+ {
+ SCROW nMinRow = nEndRow + 1;
+ for (SCCOL i = 0; i < nColCount; ++i)
+ {
+ if (pNextRows[i] < nMinRow) // when two equal the left one
+ {
+ nMinRow = pNextRows[i];
+ nCol = i + nStartCol;
+ }
+ }
+ nRow = nMinRow;
+
+ if ( nRow > nEndRow )
+ {
+ if (++nWrap >= 2)
+ return;
+ nCol = nStartCol;
+ nRow = nStartRow;
+ for (SCCOL i = 0; i < nColCount; ++i)
+ pNextRows[i] = nStartRow; // do it all over again
+ }
+ }
+ }
+ while ( !ValidNextPos(nCol, nRow, rMark, bMarked, bUnprotected) );
+ }
+ }
+
+ if (ValidColRow(nCol,nRow))
+ {
+ rCol = nCol;
+ rRow = nRow;
+ }
+}
+
+bool ScTable::GetNextMarkedCell( SCCOL& rCol, SCROW& rRow, const ScMarkData& rMark ) const
+{
+ ++rRow; // next row
+
+ while ( rCol < aCol.size() )
+ {
+ ScMarkArray aArray( rMark.GetMarkArray( rCol ) );
+ while ( rRow <= rDocument.MaxRow() )
+ {
+ SCROW nStart = aArray.GetNextMarked( rRow, false );
+ if ( nStart <= rDocument.MaxRow() )
+ {
+ SCROW nEnd = aArray.GetMarkEnd( nStart, false );
+
+ const sc::CellStoreType& rCells = aCol[rCol].maCells;
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos = rCells.position(nStart);
+ sc::CellStoreType::const_iterator it = aPos.first;
+ SCROW nTestRow = nStart;
+ if (it->type == sc::element_type_empty)
+ {
+ // Skip the empty block.
+ nTestRow += it->size - aPos.second;
+ ++it;
+ if (it == rCells.end())
+ {
+ // No more block. Move on to the next column.
+ rRow = rDocument.MaxRow() + 1;
+ continue;
+ }
+ }
+
+ if (nTestRow <= nEnd)
+ {
+ // Cell found.
+ rRow = nTestRow;
+ return true;
+ }
+
+ rRow = nEnd + 1; // Search for next selected range
+ }
+ else
+ rRow = rDocument.MaxRow() + 1; // End of column
+ }
+ rRow = 0;
+ ++rCol; // test next column
+ }
+
+ // Though searched only the allocated columns, it is equivalent to a search till rDocument.MaxCol().
+ rCol = rDocument.MaxCol() + 1;
+ return false; // Through all columns
+}
+
+void ScTable::UpdateDrawRef( UpdateRefMode eUpdateRefMode, SCCOL nCol1, SCROW nRow1, SCTAB nTab1,
+ SCCOL nCol2, SCROW nRow2, SCTAB nTab2,
+ SCCOL nDx, SCROW nDy, SCTAB nDz, bool bUpdateNoteCaptionPos )
+{
+ if ( !(nTab >= nTab1 && nTab <= nTab2 && nDz == 0) ) // only within the table
+ return;
+
+ ScDrawLayer* pDrawLayer = rDocument.GetDrawLayer();
+ if ( eUpdateRefMode != URM_COPY && pDrawLayer )
+ {
+ if ( eUpdateRefMode == URM_MOVE )
+ { // source range
+ nCol1 = sal::static_int_cast<SCCOL>( nCol1 - nDx );
+ nRow1 = sal::static_int_cast<SCROW>( nRow1 - nDy );
+ nCol2 = sal::static_int_cast<SCCOL>( nCol2 - nDx );
+ nRow2 = sal::static_int_cast<SCROW>( nRow2 - nDy );
+ }
+ pDrawLayer->MoveArea( nTab, nCol1,nRow1, nCol2,nRow2, nDx,nDy,
+ (eUpdateRefMode == URM_INSDEL), bUpdateNoteCaptionPos );
+ }
+}
+
+void ScTable::UpdateReference(
+ sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc, bool bIncludeDraw, bool bUpdateNoteCaptionPos )
+{
+ bool bUpdated = false;
+ UpdateRefMode eUpdateRefMode = rCxt.meMode;
+ SCCOL nDx = rCxt.mnColDelta;
+ SCROW nDy = rCxt.mnRowDelta;
+ SCTAB nDz = rCxt.mnTabDelta;
+ SCCOL nCol1 = rCxt.maRange.aStart.Col(), nCol2 = rCxt.maRange.aEnd.Col();
+ SCROW nRow1 = rCxt.maRange.aStart.Row(), nRow2 = rCxt.maRange.aEnd.Row();
+ SCTAB nTab1 = rCxt.maRange.aStart.Tab(), nTab2 = rCxt.maRange.aEnd.Tab();
+
+ // Named expressions need to be updated before formulas accessing them.
+ if (mpRangeName)
+ mpRangeName->UpdateReference(rCxt, nTab);
+
+ if (rCxt.meMode == URM_COPY )
+ {
+ for( SCCOL col : GetAllocatedColumnsRange( rCxt.maRange.aStart.Col(), rCxt.maRange.aEnd.Col()))
+ bUpdated |= aCol[col].UpdateReference(rCxt, pUndoDoc);
+ }
+ else
+ {
+ for( SCCOL col : GetAllocatedColumnsRange( 0, rDocument.MaxCol()))
+ bUpdated |= aCol[col].UpdateReference(rCxt, pUndoDoc);
+ }
+
+ if ( bIncludeDraw )
+ UpdateDrawRef( eUpdateRefMode, nCol1, nRow1, nTab1, nCol2, nRow2, nTab2, nDx, nDy, nDz, bUpdateNoteCaptionPos );
+
+ if ( nTab >= nTab1 && nTab <= nTab2 && nDz == 0 ) // print ranges: only within the table
+ {
+ SCTAB nSTab = nTab;
+ SCTAB nETab = nTab;
+ SCCOL nSCol = 0;
+ SCROW nSRow = 0;
+ SCCOL nECol = 0;
+ SCROW nERow = 0;
+ bool bRecalcPages = false;
+
+ for ( auto& rPrintRange : aPrintRanges )
+ {
+ nSCol = rPrintRange.aStart.Col();
+ nSRow = rPrintRange.aStart.Row();
+ nECol = rPrintRange.aEnd.Col();
+ nERow = rPrintRange.aEnd.Row();
+
+ // do not try to modify sheet index of print range
+ if ( ScRefUpdate::Update( &rDocument, eUpdateRefMode,
+ nCol1,nRow1,nTab, nCol2,nRow2,nTab,
+ nDx,nDy,0,
+ nSCol,nSRow,nSTab, nECol,nERow,nETab ) )
+ {
+ rPrintRange = ScRange( nSCol, nSRow, 0, nECol, nERow, 0 );
+ bRecalcPages = true;
+ }
+ }
+
+ if ( moRepeatColRange )
+ {
+ nSCol = moRepeatColRange->aStart.Col();
+ nSRow = moRepeatColRange->aStart.Row();
+ nECol = moRepeatColRange->aEnd.Col();
+ nERow = moRepeatColRange->aEnd.Row();
+
+ // do not try to modify sheet index of repeat range
+ if ( ScRefUpdate::Update( &rDocument, eUpdateRefMode,
+ nCol1,nRow1,nTab, nCol2,nRow2,nTab,
+ nDx,nDy,0,
+ nSCol,nSRow,nSTab, nECol,nERow,nETab ) )
+ {
+ *moRepeatColRange = ScRange( nSCol, nSRow, 0, nECol, nERow, 0 );
+ bRecalcPages = true;
+ nRepeatStartX = nSCol; // for UpdatePageBreaks
+ nRepeatEndX = nECol;
+ }
+ }
+
+ if ( moRepeatRowRange )
+ {
+ nSCol = moRepeatRowRange->aStart.Col();
+ nSRow = moRepeatRowRange->aStart.Row();
+ nECol = moRepeatRowRange->aEnd.Col();
+ nERow = moRepeatRowRange->aEnd.Row();
+
+ // do not try to modify sheet index of repeat range
+ if ( ScRefUpdate::Update( &rDocument, eUpdateRefMode,
+ nCol1,nRow1,nTab, nCol2,nRow2,nTab,
+ nDx,nDy,0,
+ nSCol,nSRow,nSTab, nECol,nERow,nETab ) )
+ {
+ *moRepeatRowRange = ScRange( nSCol, nSRow, 0, nECol, nERow, 0 );
+ bRecalcPages = true;
+ nRepeatStartY = nSRow; // for UpdatePageBreaks
+ nRepeatEndY = nERow;
+ }
+ }
+
+ // updating print ranges is not necessary with multiple print ranges
+ if ( bRecalcPages && GetPrintRangeCount() <= 1 )
+ {
+ UpdatePageBreaks(nullptr);
+
+ rDocument.RepaintRange( ScRange(0,0,nTab,rDocument.MaxCol(),rDocument.MaxRow(),nTab) );
+ }
+ }
+
+ if (bUpdated)
+ SetStreamValid(false);
+
+ if(mpCondFormatList)
+ mpCondFormatList->UpdateReference(rCxt);
+
+ if (pTabProtection)
+ pTabProtection->updateReference( eUpdateRefMode, rDocument, rCxt.maRange, nDx, nDy, nDz);
+}
+
+void ScTable::UpdateTranspose( const ScRange& rSource, const ScAddress& rDest,
+ ScDocument* pUndoDoc )
+{
+ for (auto const & rpCol : aCol)
+ rpCol->UpdateTranspose( rSource, rDest, pUndoDoc );
+}
+
+void ScTable::UpdateGrow( const ScRange& rArea, SCCOL nGrowX, SCROW nGrowY )
+{
+ for (auto const & rpCol : aCol)
+ rpCol->UpdateGrow( rArea, nGrowX, nGrowY );
+}
+
+void ScTable::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt )
+{
+ // Store the old tab number in sc::UpdatedRangeNames for
+ // ScTokenArray::AdjustReferenceOnInsertedTab() to check with
+ // isNameModified()
+ if (mpRangeName)
+ mpRangeName->UpdateInsertTab(rCxt, nTab);
+
+ if (nTab >= rCxt.mnInsertPos)
+ {
+ nTab += rCxt.mnSheets;
+ if (pDBDataNoName)
+ pDBDataNoName->UpdateMoveTab(nTab - 1 ,nTab);
+ }
+
+ if (mpCondFormatList)
+ mpCondFormatList->UpdateInsertTab(rCxt);
+
+ if (pTabProtection)
+ pTabProtection->updateReference( URM_INSDEL, rDocument,
+ ScRange( 0, 0, rCxt.mnInsertPos, rDocument.MaxCol(), rDocument.MaxRow(), MAXTAB),
+ 0, 0, rCxt.mnSheets);
+
+ for (SCCOL i=0; i < aCol.size(); i++)
+ aCol[i].UpdateInsertTab(rCxt);
+
+ SetStreamValid(false);
+}
+
+void ScTable::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt )
+{
+ // Store the old tab number in sc::UpdatedRangeNames for
+ // ScTokenArray::AdjustReferenceOnDeletedTab() to check with
+ // isNameModified()
+ if (mpRangeName)
+ mpRangeName->UpdateDeleteTab(rCxt, nTab);
+
+ if (nTab > rCxt.mnDeletePos)
+ {
+ nTab -= rCxt.mnSheets;
+ if (pDBDataNoName)
+ pDBDataNoName->UpdateMoveTab(nTab + 1,nTab);
+ }
+
+ if (mpCondFormatList)
+ mpCondFormatList->UpdateDeleteTab(rCxt);
+
+ if (pTabProtection)
+ pTabProtection->updateReference( URM_INSDEL, rDocument,
+ ScRange( 0, 0, rCxt.mnDeletePos, rDocument.MaxCol(), rDocument.MaxRow(), MAXTAB),
+ 0, 0, -rCxt.mnSheets);
+
+ for (SCCOL i = 0; i < aCol.size(); ++i)
+ aCol[i].UpdateDeleteTab(rCxt);
+
+ SetStreamValid(false);
+}
+
+void ScTable::UpdateMoveTab(
+ sc::RefUpdateMoveTabContext& rCxt, SCTAB nTabNo, ScProgress* pProgress )
+{
+ nTab = nTabNo;
+ if (mpRangeName)
+ mpRangeName->UpdateMoveTab(rCxt, nTab);
+
+ if (pDBDataNoName)
+ pDBDataNoName->UpdateMoveTab(rCxt.mnOldPos, rCxt.mnNewPos);
+
+ if(mpCondFormatList)
+ mpCondFormatList->UpdateMoveTab(rCxt);
+
+ if (pTabProtection)
+ pTabProtection->updateReference( URM_REORDER, rDocument,
+ ScRange( 0, 0, rCxt.mnOldPos, rDocument.MaxCol(), rDocument.MaxRow(), MAXTAB),
+ 0, 0, rCxt.mnNewPos - rCxt.mnOldPos);
+
+ for ( SCCOL i=0; i < aCol.size(); i++ )
+ {
+ aCol[i].UpdateMoveTab(rCxt, nTabNo);
+ if (pProgress)
+ pProgress->SetState(pProgress->GetState() + aCol[i].GetCodeCount());
+ }
+
+ SetStreamValid(false);
+}
+
+void ScTable::UpdateCompile( bool bForceIfNameInUse )
+{
+ for (SCCOL i=0; i < aCol.size(); i++)
+ {
+ aCol[i].UpdateCompile( bForceIfNameInUse );
+ }
+}
+
+void ScTable::SetTabNo(SCTAB nNewTab)
+{
+ nTab = nNewTab;
+ for (SCCOL i=0; i < aCol.size(); i++)
+ aCol[i].SetTabNo(nNewTab);
+}
+
+void ScTable::FindRangeNamesInUse(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ sc::UpdatedRangeNames& rIndexes) const
+{
+ for (SCCOL i = nCol1; i <= nCol2 && IsColValid( i ); i++)
+ aCol[i].FindRangeNamesInUse(nRow1, nRow2, rIndexes);
+}
+
+void ScTable::ExtendPrintArea( OutputDevice* pDev,
+ SCCOL /* nStartCol */, SCROW nStartRow, SCCOL& rEndCol, SCROW nEndRow )
+{
+ if ( !mpColFlags || !pRowFlags )
+ {
+ OSL_FAIL("ExtendPrintArea: No ColInfo or RowInfo");
+ return;
+ }
+
+ Point aPix1000 = pDev->LogicToPixel(Point(1000,1000), MapMode(MapUnit::MapTwip));
+ double nPPTX = aPix1000.X() / 1000.0;
+ double nPPTY = aPix1000.Y() / 1000.0;
+
+ // First, mark those columns that we need to skip i.e. hidden and empty columns.
+
+ ScFlatBoolColSegments aSkipCols(rDocument.MaxCol());
+ aSkipCols.setFalse(0, rDocument.MaxCol());
+ for (SCCOL i = 0; i <= rDocument.MaxCol(); ++i)
+ {
+ SCCOL nLastCol = i;
+ if (ColHidden(i, nullptr, &nLastCol))
+ {
+ // Columns are hidden in this range.
+ aSkipCols.setTrue(i, nLastCol);
+ }
+ else
+ {
+ // These columns are visible. Check for empty columns.
+ for (SCCOL j = i; j <= nLastCol; ++j)
+ {
+ if ( j >= aCol.size() )
+ {
+ aSkipCols.setTrue( j, rDocument.MaxCol() );
+ break;
+ }
+ if (aCol[j].GetCellCount() == 0)
+ // empty
+ aSkipCols.setTrue(j,j);
+ }
+ }
+ i = nLastCol;
+ }
+
+ ScFlatBoolColSegments::RangeData aColData;
+ for (SCCOL nCol = rEndCol; nCol >= 0; --nCol)
+ {
+ if (!aSkipCols.getRangeData(nCol, aColData))
+ // Failed to get the data. This should never happen!
+ return;
+
+ if (aColData.mbValue)
+ {
+ // Skip these columns.
+ nCol = aColData.mnCol1; // move toward 0.
+ continue;
+ }
+
+ // These are visible and non-empty columns.
+ for (SCCOL nDataCol = nCol; 0 <= nDataCol && nDataCol >= aColData.mnCol1; --nDataCol)
+ {
+ SCCOL nPrintCol = nDataCol;
+ VisibleDataCellIterator aIter(rDocument, *mpHiddenRows, aCol[nDataCol]);
+ ScRefCellValue aCell = aIter.reset(nStartRow);
+ if (aCell.isEmpty())
+ // No visible cells found in this column. Skip it.
+ continue;
+
+ while (!aCell.isEmpty())
+ {
+ SCCOL nNewCol = nDataCol;
+ SCROW nRow = aIter.getRow();
+ if (nRow > nEndRow)
+ // Went past the last row position. Bail out.
+ break;
+
+ MaybeAddExtraColumn(nNewCol, nRow, pDev, nPPTX, nPPTY);
+ if (nNewCol > nPrintCol)
+ nPrintCol = nNewCol;
+ aCell = aIter.next();
+ }
+
+ if (nPrintCol > rEndCol)
+ // Make sure we don't shrink the print area.
+ rEndCol = nPrintCol;
+ }
+ nCol = aColData.mnCol1; // move toward 0.
+ }
+}
+
+void ScTable::MaybeAddExtraColumn(SCCOL& rCol, SCROW nRow, OutputDevice* pDev, double nPPTX, double nPPTY)
+{
+ // tdf#128873 we do not need to calculate text width (heavy operation)
+ // when we for sure know that an additional column will not be added
+ if (GetAllocatedColumnsCount() > rCol + 1)
+ {
+ ScRefCellValue aNextCell = aCol[rCol + 1].GetCellValue(nRow);
+ if (!aNextCell.isEmpty())
+ {
+ // return rCol as is
+ return;
+ }
+ }
+
+ ScColumn& rColumn = aCol[rCol];
+ ScRefCellValue aCell = rColumn.GetCellValue(nRow);
+ if (!aCell.hasString())
+ return;
+
+ tools::Long nPixel = rColumn.GetTextWidth(nRow);
+
+ // Width already calculated in Idle-Handler ?
+ if ( TEXTWIDTH_DIRTY == nPixel )
+ {
+ ScNeededSizeOptions aOptions;
+ aOptions.bTotalSize = true;
+ aOptions.bFormula = false; //TODO: pass as parameter
+ aOptions.bSkipMerged = false;
+
+ Fraction aZoom(1,1);
+ nPixel = rColumn.GetNeededSize(
+ nRow, pDev, nPPTX, nPPTY, aZoom, aZoom, true, aOptions, nullptr );
+
+ rColumn.SetTextWidth(nRow, static_cast<sal_uInt16>(nPixel));
+ }
+
+ tools::Long nTwips = static_cast<tools::Long>(nPixel / nPPTX);
+ tools::Long nDocW = GetColWidth( rCol );
+
+ tools::Long nMissing = nTwips - nDocW;
+ if ( nMissing > 0 )
+ {
+ // look at alignment
+
+ const ScPatternAttr* pPattern = GetPattern( rCol, nRow );
+ const SfxItemSet* pCondSet = rDocument.GetCondResult( rCol, nRow, nTab );
+
+ SvxCellHorJustify eHorJust =
+ pPattern->GetItem( ATTR_HOR_JUSTIFY, pCondSet ).GetValue();
+ if ( eHorJust == SvxCellHorJustify::Center )
+ nMissing /= 2; // distributed into both directions
+ else
+ {
+ // STANDARD is LEFT (only text is handled here)
+ bool bRight = ( eHorJust == SvxCellHorJustify::Right );
+ if ( IsLayoutRTL() )
+ bRight = !bRight;
+ if ( bRight )
+ nMissing = 0; // extended only to the left (logical)
+ }
+ }
+
+ SCCOL nNewCol = rCol;
+ while (nMissing > 0 && nNewCol < rDocument.MaxCol())
+ {
+ auto nNextCol = nNewCol + 1;
+ bool bNextEmpty = true;
+ if (GetAllocatedColumnsCount() > nNextCol)
+ {
+ ScRefCellValue aNextCell = aCol[nNextCol].GetCellValue(nRow);
+ bNextEmpty = aNextCell.isEmpty();
+ }
+ if (!bNextEmpty)
+ {
+ // Cell content in a next column ends display of this string.
+ nMissing = 0;
+ }
+ else
+ nMissing -= GetColWidth(++nNewCol);
+ }
+ rCol = nNewCol;
+}
+
+namespace {
+
+class SetTableIndex
+{
+ SCTAB mnTab;
+public:
+ explicit SetTableIndex(SCTAB nTab) : mnTab(nTab) {}
+
+ void operator() (ScRange& rRange) const
+ {
+ rRange.aStart.SetTab(mnTab);
+ rRange.aEnd.SetTab(mnTab);
+ }
+};
+
+}
+
+void ScTable::CopyPrintRange(const ScTable& rTable)
+{
+ // The table index shouldn't be used when the print range is used, but
+ // just in case set the correct table index.
+
+ aPrintRanges = rTable.aPrintRanges;
+ ::std::for_each(aPrintRanges.begin(), aPrintRanges.end(), SetTableIndex(nTab));
+
+ bPrintEntireSheet = rTable.bPrintEntireSheet;
+
+ moRepeatColRange.reset();
+ if (rTable.moRepeatColRange)
+ {
+ moRepeatColRange.emplace(*rTable.moRepeatColRange);
+ moRepeatColRange->aStart.SetTab(nTab);
+ moRepeatColRange->aEnd.SetTab(nTab);
+ }
+
+ moRepeatRowRange.reset();
+ if (rTable.moRepeatRowRange)
+ {
+ moRepeatRowRange.emplace(*rTable.moRepeatRowRange);
+ moRepeatRowRange->aStart.SetTab(nTab);
+ moRepeatRowRange->aEnd.SetTab(nTab);
+ }
+}
+
+void ScTable::SetRepeatColRange( std::optional<ScRange> oNew )
+{
+ moRepeatColRange = std::move(oNew);
+
+ SetStreamValid(false);
+
+ InvalidatePageBreaks();
+}
+
+void ScTable::SetRepeatRowRange( std::optional<ScRange> oNew )
+{
+ moRepeatRowRange = std::move(oNew);
+
+ SetStreamValid(false);
+
+ InvalidatePageBreaks();
+}
+
+void ScTable::ClearPrintRanges()
+{
+ aPrintRanges.clear();
+ bPrintEntireSheet = false;
+
+ SetStreamValid(false);
+
+ InvalidatePageBreaks(); // #i117952# forget page breaks for an old print range
+}
+
+void ScTable::AddPrintRange( const ScRange& rNew )
+{
+ bPrintEntireSheet = false;
+ if( aPrintRanges.size() < 0xFFFF )
+ aPrintRanges.push_back( rNew );
+
+ SetStreamValid(false);
+
+ InvalidatePageBreaks();
+}
+
+void ScTable::SetPrintEntireSheet()
+{
+ if( !IsPrintEntireSheet() )
+ {
+ ClearPrintRanges();
+ bPrintEntireSheet = true;
+ }
+}
+
+const ScRange* ScTable::GetPrintRange(sal_uInt16 nPos) const
+{
+ return (nPos < GetPrintRangeCount()) ? &aPrintRanges[ nPos ] : nullptr;
+}
+
+void ScTable::FillPrintSaver( ScPrintSaverTab& rSaveTab ) const
+{
+ rSaveTab.SetAreas( std::vector(aPrintRanges), bPrintEntireSheet );
+ rSaveTab.SetRepeat( moRepeatColRange, moRepeatRowRange );
+}
+
+void ScTable::RestorePrintRanges( const ScPrintSaverTab& rSaveTab )
+{
+ aPrintRanges = rSaveTab.GetPrintRanges();
+ bPrintEntireSheet = rSaveTab.IsEntireSheet();
+ SetRepeatColRange( rSaveTab.GetRepeatCol() );
+ SetRepeatRowRange( rSaveTab.GetRepeatRow() );
+
+ InvalidatePageBreaks(); // #i117952# forget page breaks for an old print range
+ UpdatePageBreaks(nullptr);
+}
+
+ScTable::VisibleDataCellIterator::VisibleDataCellIterator(const ScDocument& rDoc, ScFlatBoolRowSegments& rRowSegs, ScColumn& rColumn) :
+ mrDocument(rDoc),
+ mrRowSegs(rRowSegs),
+ mrColumn(rColumn),
+ mnCurRow(ROW_NOT_FOUND),
+ mnUBound(ROW_NOT_FOUND)
+{
+}
+
+ScRefCellValue ScTable::VisibleDataCellIterator::reset(SCROW nRow)
+{
+ if (nRow > mrDocument.MaxRow())
+ {
+ mnCurRow = ROW_NOT_FOUND;
+ return ScRefCellValue();
+ }
+
+ ScFlatBoolRowSegments::RangeData aData;
+ if (!mrRowSegs.getRangeData(nRow, aData))
+ {
+ mnCurRow = ROW_NOT_FOUND;
+ return ScRefCellValue();
+ }
+
+ if (!aData.mbValue)
+ {
+ // specified row is visible. Take it.
+ mnCurRow = nRow;
+ mnUBound = aData.mnRow2;
+ }
+ else
+ {
+ // specified row is not-visible. The first visible row is the start of
+ // the next segment.
+ mnCurRow = aData.mnRow2 + 1;
+ mnUBound = mnCurRow; // get range data on the next iteration.
+ if (mnCurRow > mrDocument.MaxRow())
+ {
+ // Make sure the row doesn't exceed our current limit.
+ mnCurRow = ROW_NOT_FOUND;
+ return ScRefCellValue();
+ }
+ }
+
+ maCell = mrColumn.GetCellValue(mnCurRow);
+ if (!maCell.isEmpty())
+ // First visible cell found.
+ return maCell;
+
+ // Find a first visible cell below this row (if any).
+ return next();
+}
+
+ScRefCellValue ScTable::VisibleDataCellIterator::next()
+{
+ if (mnCurRow == ROW_NOT_FOUND)
+ return ScRefCellValue();
+
+ while (mrColumn.GetNextDataPos(mnCurRow))
+ {
+ if (mnCurRow > mnUBound)
+ {
+ // We don't know the visibility of this row range. Query it.
+ ScFlatBoolRowSegments::RangeData aData;
+ if (!mrRowSegs.getRangeData(mnCurRow, aData))
+ {
+ mnCurRow = ROW_NOT_FOUND;
+ return ScRefCellValue();
+ }
+
+ if (aData.mbValue)
+ {
+ // This row is invisible. Skip to the last invisible row and
+ // try again.
+ mnCurRow = mnUBound = aData.mnRow2;
+ continue;
+ }
+
+ // This row is visible.
+ mnUBound = aData.mnRow2;
+ }
+
+ maCell = mrColumn.GetCellValue(mnCurRow);
+ if (!maCell.isEmpty())
+ return maCell;
+ }
+
+ mnCurRow = ROW_NOT_FOUND;
+ return ScRefCellValue();
+}
+
+void ScTable::SetAnonymousDBData(std::unique_ptr<ScDBData> pDBData)
+{
+ pDBDataNoName = std::move(pDBData);
+}
+
+sal_uLong ScTable::AddCondFormat( std::unique_ptr<ScConditionalFormat> pNew )
+{
+ if(!mpCondFormatList)
+ mpCondFormatList.reset(new ScConditionalFormatList());
+
+ sal_uInt32 nMax = mpCondFormatList->getMaxKey();
+
+ pNew->SetKey(nMax+1);
+ mpCondFormatList->InsertNew(std::move(pNew));
+
+ return nMax + 1;
+}
+
+SvtScriptType ScTable::GetScriptType( SCCOL nCol, SCROW nRow ) const
+{
+ if ( !IsColValid( nCol ) )
+ return SvtScriptType::NONE;
+
+ return aCol[nCol].GetScriptType(nRow);
+}
+
+void ScTable::SetScriptType( SCCOL nCol, SCROW nRow, SvtScriptType nType )
+{
+ if (!ValidCol(nCol))
+ return;
+
+ aCol[nCol].SetScriptType(nRow, nType);
+}
+
+SvtScriptType ScTable::GetRangeScriptType(
+ sc::ColumnBlockPosition& rBlockPos, SCCOL nCol, SCROW nRow1, SCROW nRow2 )
+{
+ if ( !IsColValid( nCol ) )
+ return SvtScriptType::NONE;
+
+ sc::CellStoreType::iterator itr = aCol[nCol].maCells.begin();
+ return aCol[nCol].GetRangeScriptType(rBlockPos.miCellTextAttrPos, nRow1, nRow2, itr);
+}
+
+formula::FormulaTokenRef ScTable::ResolveStaticReference( SCCOL nCol, SCROW nRow )
+{
+ if ( !ValidCol( nCol ) || !ValidRow( nRow ) )
+ return formula::FormulaTokenRef();
+ if ( nCol >= aCol.size() )
+ // Return a value of 0.0 if column not exists
+ return formula::FormulaTokenRef(new formula::FormulaDoubleToken(0.0));
+ return aCol[nCol].ResolveStaticReference(nRow);
+}
+
+formula::FormulaTokenRef ScTable::ResolveStaticReference( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
+{
+ if (nCol2 < nCol1 || nRow2 < nRow1)
+ return formula::FormulaTokenRef();
+
+ if ( !ValidCol( nCol1 ) || !ValidCol( nCol2 ) || !ValidRow( nRow1 ) || !ValidRow( nRow2 ) )
+ return formula::FormulaTokenRef();
+
+ SCCOL nMaxCol;
+ if ( nCol2 >= aCol.size() )
+ nMaxCol = aCol.size() - 1;
+ else
+ nMaxCol = nCol2;
+
+ ScMatrixRef pMat(new ScMatrix(nCol2-nCol1+1, nRow2-nRow1+1, 0.0));
+ for (SCCOL nCol = nCol1; nCol <= nMaxCol; ++nCol)
+ {
+ if (!aCol[nCol].ResolveStaticReference(*pMat, nCol2-nCol1, nRow1, nRow2))
+ // Column contains non-static cell. Failed.
+ return formula::FormulaTokenRef();
+ }
+
+ return formula::FormulaTokenRef(new ScMatrixToken(pMat));
+}
+
+formula::VectorRefArray ScTable::FetchVectorRefArray( SCCOL nCol, SCROW nRow1, SCROW nRow2 )
+{
+ if (nRow2 < nRow1)
+ return formula::VectorRefArray();
+
+ if ( !IsColValid( nCol ) || !ValidRow( nRow1 ) || !ValidRow( nRow2 ) )
+ return formula::VectorRefArray();
+
+ return aCol[nCol].FetchVectorRefArray(nRow1, nRow2);
+}
+
+#ifdef DBG_UTIL
+void ScTable::AssertNoInterpretNeeded( SCCOL nCol, SCROW nRow1, SCROW nRow2 )
+{
+ assert( nRow2 >= nRow1 );
+ assert( IsColValid( nCol ) && ValidRow( nRow1 ) && ValidRow( nRow2 ) );
+ return aCol[nCol].AssertNoInterpretNeeded(nRow1, nRow2);
+}
+#endif
+
+bool ScTable::HandleRefArrayForParallelism( SCCOL nCol, SCROW nRow1, SCROW nRow2, const ScFormulaCellGroupRef& mxGroup )
+{
+ if (nRow2 < nRow1)
+ return false;
+
+ if ( !IsColValid( nCol ) || !ValidRow( nRow1 ) || !ValidRow( nRow2 ) )
+ return false;
+
+ mpHiddenCols->makeReady();
+ mpHiddenRows->makeReady();
+ mpFilteredCols->makeReady();
+ mpFilteredRows->makeReady();
+
+ return aCol[nCol].HandleRefArrayForParallelism(nRow1, nRow2, mxGroup);
+}
+
+ScRefCellValue ScTable::GetRefCellValue( SCCOL nCol, SCROW nRow )
+{
+ if ( !IsColRowValid( nCol, nRow ) )
+ return ScRefCellValue();
+
+ return aCol[nCol].GetCellValue(nRow);
+}
+
+ScRefCellValue ScTable::GetRefCellValue( SCCOL nCol, SCROW nRow, sc::ColumnBlockPosition& rBlockPos )
+{
+ if ( !IsColRowValid( nCol, nRow ) )
+ return ScRefCellValue();
+
+ return aCol[nCol].GetCellValue(rBlockPos, nRow);
+}
+
+SvtBroadcaster* ScTable::GetBroadcaster( SCCOL nCol, SCROW nRow )
+{
+ if ( !IsColRowValid( nCol, nRow ) )
+ return nullptr;
+
+ return aCol[nCol].GetBroadcaster(nRow);
+}
+
+void ScTable::DeleteBroadcasters(
+ sc::ColumnBlockPosition& rBlockPos, SCCOL nCol, SCROW nRow1, SCROW nRow2 )
+{
+ if ( !IsColValid( nCol ) )
+ return;
+
+ aCol[nCol].DeleteBroadcasters(rBlockPos, nRow1, nRow2);
+}
+
+void ScTable::DeleteEmptyBroadcasters()
+{
+ for( auto& col : aCol )
+ col->DeleteEmptyBroadcasters();
+}
+
+void ScTable::FillMatrix( ScMatrix& rMat, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, svl::SharedStringPool* pPool ) const
+{
+ size_t nMatCol = 0;
+ nCol2 = ClampToAllocatedColumns(nCol2);
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol, ++nMatCol)
+ aCol[nCol].FillMatrix(rMat, nMatCol, nRow1, nRow2, pPool);
+}
+
+void ScTable::InterpretDirtyCells( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
+{
+ nCol2 = ClampToAllocatedColumns(nCol2);
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ aCol[nCol].InterpretDirtyCells(nRow1, nRow2);
+}
+
+bool ScTable::InterpretCellsIfNeeded( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
+{
+ nCol2 = ClampToAllocatedColumns(nCol2);
+ bool allInterpreted = true;
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ if(!aCol[nCol].InterpretCellsIfNeeded(nRow1, nRow2))
+ allInterpreted = false;
+ return allInterpreted;
+}
+
+void ScTable::SetFormulaResults( SCCOL nCol, SCROW nRow, const double* pResults, size_t nLen )
+{
+ if (!ValidCol(nCol))
+ return;
+
+ aCol[nCol].SetFormulaResults(nRow, pResults, nLen);
+}
+
+void ScTable::CalculateInColumnInThread( ScInterpreterContext& rContext,
+ SCCOL nColStart, SCCOL nColEnd,
+ SCROW nRowStart, SCROW nRowEnd,
+ unsigned nThisThread, unsigned nThreadsTotal)
+{
+ if (!ValidCol(nColStart) || !ValidCol(nColEnd))
+ return;
+
+ size_t nLen = nRowEnd - nRowStart + 1;
+ size_t nOffset = 0;
+ for (SCCOL nCurrCol = nColStart; nCurrCol <= nColEnd; ++nCurrCol)
+ {
+ aCol[nCurrCol].CalculateInThread( rContext, nRowStart, nLen, nOffset, nThisThread, nThreadsTotal );
+ nOffset += nLen;
+ }
+}
+
+void ScTable::HandleStuffAfterParallelCalculation( SCCOL nColStart, SCCOL nColEnd, SCROW nRow, size_t nLen,
+ ScInterpreter* pInterpreter)
+{
+ assert(ValidCol(nColStart) && ValidCol(nColEnd));
+
+ for (SCCOL nCurrCol = nColStart; nCurrCol <= nColEnd; ++nCurrCol)
+ aCol[nCurrCol].HandleStuffAfterParallelCalculation( nRow, nLen, pInterpreter );
+}
+
+#if DUMP_COLUMN_STORAGE
+void ScTable::DumpColumnStorage( SCCOL nCol ) const
+{
+ if ( !IsColValid( nCol ) )
+ return;
+
+ aCol[nCol].DumpColumnStorage();
+}
+#endif
+
+const SvtBroadcaster* ScTable::GetBroadcaster( SCCOL nCol, SCROW nRow ) const
+{
+ if ( !IsColRowValid( nCol, nRow ) )
+ return nullptr;
+
+ return aCol[nCol].GetBroadcaster(nRow);
+}
+
+void ScTable::DeleteConditionalFormat( sal_uLong nIndex )
+{
+ mpCondFormatList->erase(nIndex);
+}
+
+void ScTable::SetCondFormList( ScConditionalFormatList* pNew )
+{
+ mpCondFormatList.reset( pNew );
+}
+
+ScConditionalFormatList* ScTable::GetCondFormList()
+{
+ if(!mpCondFormatList)
+ mpCondFormatList.reset( new ScConditionalFormatList() );
+
+ return mpCondFormatList.get();
+}
+
+const ScConditionalFormatList* ScTable::GetCondFormList() const
+{
+ return mpCondFormatList.get();
+}
+
+ScColumnsRange ScTable::GetWritableColumnsRange(SCCOL nColBegin, SCCOL nColEnd)
+{
+ // because the range is inclusive, some code will pass nColEnd<nColBegin to indicate an empty range
+ if (nColEnd < nColBegin)
+ return ScColumnsRange(-1, -1);
+ assert( nColEnd >= 0 && nColEnd <= GetDoc().MaxCol());
+ CreateColumnIfNotExists(nColEnd);
+ return GetColumnsRange(nColBegin, nColEnd);
+}
+
+ScColumnsRange ScTable::GetAllocatedColumnsRange(SCCOL nColBegin, SCCOL nColEnd) const
+{
+ if (nColBegin >= aCol.size())
+ return ScColumnsRange(-1, -1);
+ // clamp end of range to available columns
+ if (nColEnd >= aCol.size())
+ nColEnd = aCol.size() - 1;
+ return GetColumnsRange(nColBegin, nColEnd);
+}
+
+ScColumnsRange ScTable::GetColumnsRange(SCCOL nColBegin, SCCOL nColEnd) const
+{
+ // because the range is inclusive, some code will pass nColEnd<nColBegin to indicate an empty range
+ if (nColEnd < nColBegin)
+ return ScColumnsRange(-1, -1);
+ assert( nColBegin >= 0 && nColBegin <= GetDoc().MaxCol());
+ assert( nColEnd >= 0 && nColEnd <= GetDoc().MaxCol());
+ return ScColumnsRange(nColBegin, nColEnd + 1); // change inclusive end to past-end
+}
+
+// out-of-line the cold part of the CreateColumnIfNotExists function
+void ScTable::CreateColumnIfNotExistsImpl( const SCCOL nScCol )
+{
+ // When doing multi-threaded load of, e.g. XLS files, we can hit this, which calls
+ // into SfxItemPool::Put, in parallel with other code that calls into SfxItemPool::Put,
+ // which is bad since that code is not thread-safe.
+ SolarMutexGuard aGuard;
+ const SCCOL aOldColSize = aCol.size();
+ aCol.resize( rDocument.GetSheetLimits(), static_cast< size_t >( nScCol + 1 ) );
+ for (SCCOL i = aOldColSize; i <= nScCol; i++)
+ aCol[i].Init( i, nTab, rDocument, false );
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/table2.cxx b/sc/source/core/data/table2.cxx
new file mode 100644
index 000000000..713be792a
--- /dev/null
+++ b/sc/source/core/data/table2.cxx
@@ -0,0 +1,4372 @@
+/* -*- 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/.
+ *
+ * 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 <memory>
+#include <table.hxx>
+#include <patattr.hxx>
+#include <docpool.hxx>
+#include <formulacell.hxx>
+#include <document.hxx>
+#include <drwlayer.hxx>
+#include <olinetab.hxx>
+#include <stlpool.hxx>
+#include <attarray.hxx>
+#include <markdata.hxx>
+#include <dociter.hxx>
+#include <conditio.hxx>
+#include <chartlis.hxx>
+#include <fillinfo.hxx>
+#include <bcaslot.hxx>
+#include <postit.hxx>
+#include <sheetevents.hxx>
+#include <segmenttree.hxx>
+#include <dbdata.hxx>
+#include <tokenarray.hxx>
+#include <clipcontext.hxx>
+#include <types.hxx>
+#include <editutil.hxx>
+#include <mtvcellfunc.hxx>
+#include <refupdatecontext.hxx>
+#include <scopetools.hxx>
+#include <tabprotection.hxx>
+#include <columnspanset.hxx>
+#include <rowheightcontext.hxx>
+#include <listenercontext.hxx>
+#include <compressedarray.hxx>
+#include <refdata.hxx>
+#include <docsh.hxx>
+
+#include <scitems.hxx>
+#include <editeng/boxitem.hxx>
+#include <editeng/editobj.hxx>
+#include <o3tl/safeint.hxx>
+#include <o3tl/unit_conversion.hxx>
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+#include <svl/poolcach.hxx>
+#include <unotools/charclass.hxx>
+#include <math.h>
+
+namespace {
+
+class ColumnRegroupFormulaCells
+{
+ ScColContainer& mrCols;
+ std::vector<ScAddress>* mpGroupPos;
+
+public:
+ ColumnRegroupFormulaCells( ScColContainer& rCols, std::vector<ScAddress>* pGroupPos ) :
+ mrCols(rCols), mpGroupPos(pGroupPos) {}
+
+ void operator() (SCCOL nCol)
+ {
+ mrCols[nCol].RegroupFormulaCells(mpGroupPos);
+ }
+};
+
+}
+
+sal_uInt16 ScTable::GetTextWidth(SCCOL nCol, SCROW nRow) const
+{
+ return aCol[nCol].GetTextWidth(nRow);
+}
+
+bool ScTable::SetOutlineTable( const ScOutlineTable* pNewOutline )
+{
+ sal_uInt16 nOldSizeX = 0;
+ sal_uInt16 nOldSizeY = 0;
+ sal_uInt16 nNewSizeX = 0;
+ sal_uInt16 nNewSizeY = 0;
+
+ if (pOutlineTable)
+ {
+ nOldSizeX = pOutlineTable->GetColArray().GetDepth();
+ nOldSizeY = pOutlineTable->GetRowArray().GetDepth();
+ pOutlineTable.reset();
+ }
+
+ if (pNewOutline)
+ {
+ pOutlineTable.reset(new ScOutlineTable( *pNewOutline ));
+ nNewSizeX = pOutlineTable->GetColArray().GetDepth();
+ nNewSizeY = pOutlineTable->GetRowArray().GetDepth();
+ }
+
+ return ( nNewSizeX != nOldSizeX || nNewSizeY != nOldSizeY ); // changed size?
+}
+
+void ScTable::StartOutlineTable()
+{
+ if (!pOutlineTable)
+ pOutlineTable.reset(new ScOutlineTable);
+}
+
+void ScTable::SetSheetEvents( std::unique_ptr<ScSheetEvents> pNew )
+{
+ pSheetEvents = std::move(pNew);
+
+ SetCalcNotification( false ); // discard notifications before the events were set
+
+ SetStreamValid(false);
+}
+
+void ScTable::SetCalcNotification( bool bSet )
+{
+ bCalcNotification = bSet;
+}
+
+bool ScTable::TestInsertRow( SCCOL nStartCol, SCCOL nEndCol, SCROW nStartRow, SCSIZE nSize ) const
+{
+ if ( nStartCol==0 && nEndCol==rDocument.MaxCol() && pOutlineTable )
+ if (!pOutlineTable->TestInsertRow(nSize))
+ return false;
+
+ SCCOL maxCol = ClampToAllocatedColumns(nEndCol);
+ for (SCCOL i=nStartCol; i<=maxCol; i++)
+ if (!aCol[i].TestInsertRow(nStartRow, nSize))
+ return false;
+
+ if( maxCol != nEndCol )
+ if (!aDefaultColData.TestInsertRow(nSize))
+ return false;
+
+ return true;
+}
+
+void ScTable::InsertRow( SCCOL nStartCol, SCCOL nEndCol, SCROW nStartRow, SCSIZE nSize )
+{
+ if (nStartCol==0 && nEndCol==rDocument.MaxCol())
+ {
+ if (mpRowHeights && pRowFlags)
+ {
+ mpRowHeights->insertSegment(nStartRow, nSize);
+ CRFlags nNewFlags = pRowFlags->Insert( nStartRow, nSize);
+ // only copy manual size flag, clear all others
+ if (nNewFlags != CRFlags::NONE && (nNewFlags != CRFlags::ManualSize))
+ pRowFlags->SetValue( nStartRow, nStartRow + nSize - 1,
+ nNewFlags & CRFlags::ManualSize);
+ }
+
+ if (pOutlineTable)
+ pOutlineTable->InsertRow( nStartRow, nSize );
+
+ mpFilteredRows->insertSegment(nStartRow, nSize);
+ mpHiddenRows->insertSegment(nStartRow, nSize);
+
+ if (!maRowManualBreaks.empty())
+ {
+ // Copy all breaks up to nStartRow (non-inclusive).
+ ::std::set<SCROW>::iterator itr1 = maRowManualBreaks.lower_bound(nStartRow);
+ ::std::set<SCROW> aNewBreaks(maRowManualBreaks.begin(), itr1);
+
+ // Copy all breaks from nStartRow (inclusive) to the last element,
+ // but add nSize to each value.
+ ::std::set<SCROW>::iterator itr2 = maRowManualBreaks.end();
+ for (; itr1 != itr2; ++itr1)
+ aNewBreaks.insert(static_cast<SCROW>(*itr1 + nSize));
+
+ maRowManualBreaks.swap(aNewBreaks);
+ }
+ }
+
+ for (SCCOL j : GetAllocatedColumnsRange(nStartCol, nEndCol))
+ aCol[j].InsertRow( nStartRow, nSize );
+ aDefaultColData.InsertRow( nStartRow, nSize );
+
+ mpCondFormatList->InsertRow(nTab, nStartCol, nEndCol, nStartRow, nSize);
+
+ InvalidatePageBreaks();
+
+ // TODO: In the future we may want to check if the table has been
+ // really modified before setting the stream invalid.
+ SetStreamValid(false);
+}
+
+void ScTable::DeleteRow(
+ const sc::ColumnSet& rRegroupCols, SCCOL nStartCol, SCCOL nEndCol, SCROW nStartRow, SCSIZE nSize,
+ bool* pUndoOutline, std::vector<ScAddress>* pGroupPos )
+{
+ if (nStartCol==0 && nEndCol==rDocument.MaxCol())
+ {
+ if (pRowFlags)
+ pRowFlags->Remove( nStartRow, nSize);
+
+ if (mpRowHeights)
+ mpRowHeights->removeSegment(nStartRow, nStartRow+nSize);
+
+ if (pOutlineTable)
+ if (pOutlineTable->DeleteRow( nStartRow, nSize ))
+ if (pUndoOutline)
+ *pUndoOutline = true;
+
+ mpFilteredRows->removeSegment(nStartRow, nStartRow+nSize);
+ mpHiddenRows->removeSegment(nStartRow, nStartRow+nSize);
+
+ if (!maRowManualBreaks.empty())
+ {
+ // Erase all manual breaks between nStartRow and nStartRow + nSize - 1 (inclusive).
+ std::set<SCROW>::iterator itr1 = maRowManualBreaks.lower_bound(nStartRow);
+ std::set<SCROW>::iterator itr2 = maRowManualBreaks.upper_bound(static_cast<SCROW>(nStartRow + nSize - 1));
+ maRowManualBreaks.erase(itr1, itr2);
+
+ // Copy all breaks from the 1st element up to nStartRow to the new container.
+ itr1 = maRowManualBreaks.lower_bound(nStartRow);
+ ::std::set<SCROW> aNewBreaks(maRowManualBreaks.begin(), itr1);
+
+ // Copy all breaks from nStartRow to the last element, but subtract each value by nSize.
+ itr2 = maRowManualBreaks.end();
+ for (; itr1 != itr2; ++itr1)
+ aNewBreaks.insert(static_cast<SCROW>(*itr1 - nSize));
+
+ maRowManualBreaks.swap(aNewBreaks);
+ }
+ }
+
+ { // scope for bulk broadcast
+ ScBulkBroadcast aBulkBroadcast( rDocument.GetBASM(), SfxHintId::ScDataChanged);
+ for (SCCOL j=nStartCol; j<=ClampToAllocatedColumns(nEndCol); j++)
+ aCol[j].DeleteRow(nStartRow, nSize, pGroupPos);
+ }
+
+ std::vector<SCCOL> aRegroupCols;
+ rRegroupCols.getColumns(nTab, aRegroupCols);
+ std::for_each(
+ aRegroupCols.begin(), aRegroupCols.end(), ColumnRegroupFormulaCells(aCol, pGroupPos));
+
+ InvalidatePageBreaks();
+
+ // TODO: In the future we may want to check if the table has been
+ // really modified before setting the stream invalid.
+ SetStreamValid(false);
+}
+
+bool ScTable::TestInsertCol( SCROW nStartRow, SCROW nEndRow, SCSIZE nSize ) const
+{
+ if ( nSize > o3tl::make_unsigned(rDocument.MaxCol()) )
+ return false;
+
+ if ( nStartRow==0 && nEndRow==rDocument.MaxRow() && pOutlineTable
+ && ! pOutlineTable->TestInsertCol(nSize) )
+ return false;
+
+ auto range = GetAllocatedColumnsRange( rDocument.MaxCol() - static_cast<SCCOL>(nSize) + 1, rDocument.MaxCol() );
+ for (auto it = range.rbegin(); it != range.rend(); ++it )
+ if (! aCol[*it].TestInsertCol(nStartRow, nEndRow))
+ return false;
+
+ return true;
+}
+
+void ScTable::InsertCol(
+ const sc::ColumnSet& rRegroupCols, SCCOL nStartCol, SCROW nStartRow, SCROW nEndRow, SCSIZE nSize )
+{
+ if (nStartRow==0 && nEndRow==rDocument.MaxRow())
+ {
+ if (mpColWidth && mpColFlags)
+ {
+ mpColWidth->InsertPreservingSize(nStartCol, nSize, STD_COL_WIDTH);
+ // The inserted columns have the same widths as the columns, which were selected for insert.
+ for (SCSIZE i=0; i < std::min(rDocument.MaxCol()-nSize-nStartCol, nSize); ++i)
+ mpColWidth->SetValue(nStartCol + i, mpColWidth->GetValue(nStartCol+i+nSize));
+ mpColFlags->InsertPreservingSize(nStartCol, nSize, CRFlags::NONE);
+ }
+ if (pOutlineTable)
+ pOutlineTable->InsertCol( nStartCol, nSize );
+
+ mpHiddenCols->insertSegment(nStartCol, static_cast<SCCOL>(nSize));
+ mpFilteredCols->insertSegment(nStartCol, static_cast<SCCOL>(nSize));
+
+ if (!maColManualBreaks.empty())
+ {
+ // Copy all breaks up to nStartCol (non-inclusive).
+ ::std::set<SCCOL>::iterator itr1 = maColManualBreaks.lower_bound(nStartCol);
+ ::std::set<SCCOL> aNewBreaks(maColManualBreaks.begin(), itr1);
+
+ // Copy all breaks from nStartCol (inclusive) to the last element,
+ // but add nSize to each value.
+ ::std::set<SCCOL>::iterator itr2 = maColManualBreaks.end();
+ for (; itr1 != itr2; ++itr1)
+ aNewBreaks.insert(static_cast<SCCOL>(*itr1 + nSize));
+
+ maColManualBreaks.swap(aNewBreaks);
+ }
+ }
+
+ // Make sure there are enough columns at the end.
+ CreateColumnIfNotExists(std::min<SCCOL>(rDocument.MaxCol(), std::max(nStartCol, aCol.size()) + nSize - 1 ));
+ if ((nStartRow == 0) && (nEndRow == rDocument.MaxRow()))
+ {
+ // Move existing columns back, this will swap last empty columns in the inserted place.
+ for (SCCOL nCol = aCol.size() - 1 - nSize; nCol >= nStartCol; --nCol)
+ aCol[nCol].SwapCol(aCol[nCol+nSize]);
+ }
+ else
+ {
+ for (SCSIZE i=0; static_cast<SCCOL>(i+nSize)+nStartCol < aCol.size(); i++)
+ aCol[aCol.size() - 1 - nSize - i].MoveTo(nStartRow, nEndRow, aCol[aCol.size() - 1 - i]);
+ }
+
+ std::vector<SCCOL> aRegroupCols;
+ rRegroupCols.getColumns(nTab, aRegroupCols);
+ std::for_each(aRegroupCols.begin(), aRegroupCols.end(), ColumnRegroupFormulaCells(aCol, nullptr));
+
+ if (nStartCol>0) // copy old attributes
+ {
+ sal_uInt16 nWhichArray[2];
+ nWhichArray[0] = ATTR_MERGE;
+ nWhichArray[1] = 0;
+
+ sc::CopyToDocContext aCxt(rDocument);
+ for (SCSIZE i=0; i<nSize; i++)
+ {
+ aCol[nStartCol-1].CopyToColumn(aCxt, nStartRow, nEndRow, InsertDeleteFlags::ATTRIB,
+ false, aCol[nStartCol+i] );
+ aCol[nStartCol+i].RemoveFlags( nStartRow, nEndRow,
+ ScMF::Hor | ScMF::Ver | ScMF::Auto );
+ aCol[nStartCol+i].ClearItems( nStartRow, nEndRow, nWhichArray );
+ }
+ }
+
+ mpCondFormatList->InsertCol(nTab, nStartRow, nEndRow, nStartCol, nSize);
+
+ InvalidatePageBreaks();
+
+ // TODO: In the future we may want to check if the table has been
+ // really modified before setting the stream invalid.
+ SetStreamValid(false);
+}
+
+void ScTable::DeleteCol(
+ const sc::ColumnSet& rRegroupCols, SCCOL nStartCol, SCROW nStartRow, SCROW nEndRow, SCSIZE nSize, bool* pUndoOutline )
+{
+ if (nStartRow==0 && nEndRow==rDocument.MaxRow())
+ {
+ if (mpColWidth && mpColFlags)
+ {
+ assert( nStartCol + nSize <= o3tl::make_unsigned(rDocument.MaxCol()+1) ); // moving 0 if ==rDocument.MaxCol()+1 is correct
+ mpColWidth->RemovePreservingSize(nStartCol, nSize, STD_COL_WIDTH);
+ mpColFlags->RemovePreservingSize(nStartCol, nSize, CRFlags::NONE);
+ }
+ if (pOutlineTable)
+ if (pOutlineTable->DeleteCol( nStartCol, nSize ))
+ if (pUndoOutline)
+ *pUndoOutline = true;
+
+ SCCOL nRmSize = nStartCol + static_cast<SCCOL>(nSize);
+ mpHiddenCols->removeSegment(nStartCol, nRmSize);
+ mpFilteredCols->removeSegment(nStartCol, nRmSize);
+
+ if (!maColManualBreaks.empty())
+ {
+ // Erase all manual breaks between nStartCol and nStartCol + nSize - 1 (inclusive).
+ std::set<SCCOL>::iterator itr1 = maColManualBreaks.lower_bound(nStartCol);
+ std::set<SCCOL>::iterator itr2 = maColManualBreaks.upper_bound(static_cast<SCCOL>(nStartCol + nSize - 1));
+ maColManualBreaks.erase(itr1, itr2);
+
+ // Copy all breaks from the 1st element up to nStartCol to the new container.
+ itr1 = maColManualBreaks.lower_bound(nStartCol);
+ ::std::set<SCCOL> aNewBreaks(maColManualBreaks.begin(), itr1);
+
+ // Copy all breaks from nStartCol to the last element, but subtract each value by nSize.
+ itr2 = maColManualBreaks.end();
+ for (; itr1 != itr2; ++itr1)
+ aNewBreaks.insert(static_cast<SCCOL>(*itr1 - nSize));
+
+ maColManualBreaks.swap(aNewBreaks);
+ }
+ }
+
+ for (SCCOL col = nStartCol; col <= ClampToAllocatedColumns(nStartCol + nSize - 1); ++col)
+ aCol[col].DeleteArea(nStartRow, nEndRow, InsertDeleteFlags::ALL, false);
+
+ if ((nStartRow == 0) && (nEndRow == rDocument.MaxRow()))
+ {
+ for (SCCOL nCol = nStartCol + nSize; nCol < aCol.size(); ++nCol)
+ aCol[nCol].SwapCol(aCol[nCol - nSize]);
+ }
+ else
+ {
+ for (SCSIZE i=0; static_cast<SCCOL>(i+nSize)+nStartCol < aCol.size(); i++)
+ aCol[nStartCol + nSize + i].MoveTo(nStartRow, nEndRow, aCol[nStartCol + i]);
+ }
+
+ std::vector<SCCOL> aRegroupCols;
+ rRegroupCols.getColumns(nTab, aRegroupCols);
+ std::for_each(aRegroupCols.begin(), aRegroupCols.end(), ColumnRegroupFormulaCells(aCol, nullptr));
+
+ InvalidatePageBreaks();
+
+ // TODO: In the future we may want to check if the table has been
+ // really modified before setting the stream invalid.
+ SetStreamValid(false);
+}
+
+void ScTable::DeleteArea(
+ SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, InsertDeleteFlags nDelFlag,
+ bool bBroadcast, sc::ColumnSpanSet* pBroadcastSpans )
+{
+ if ( nCol2 >= aCol.size() ) nCol2 = aCol.size() - 1;
+ if (nRow2 > rDocument.MaxRow()) nRow2 = rDocument.MaxRow();
+ if (ValidColRow(nCol1, nRow1) && ValidColRow(nCol2, nRow2))
+ {
+ { // scope for bulk broadcast
+ ScBulkBroadcast aBulkBroadcast( rDocument.GetBASM(), SfxHintId::ScDataChanged);
+ for (SCCOL i = nCol1; i <= nCol2; i++)
+ aCol[i].DeleteArea(nRow1, nRow2, nDelFlag, bBroadcast, pBroadcastSpans);
+ }
+
+ // Do not set protected cell in a protected table
+
+ if ( IsProtected() && (nDelFlag & InsertDeleteFlags::ATTRIB) )
+ {
+ ScPatternAttr aPattern(rDocument.GetPool());
+ aPattern.GetItemSet().Put( ScProtectionAttr( false ) );
+ ApplyPatternArea( nCol1, nRow1, nCol2, nRow2, aPattern );
+ }
+
+ if( nDelFlag & InsertDeleteFlags::ATTRIB )
+ mpCondFormatList->DeleteArea( nCol1, nRow1, nCol2, nRow2 );
+ }
+
+ // TODO: In the future we may want to check if the table has been
+ // really modified before setting the stream invalid.
+ SetStreamValid(false);
+}
+
+void ScTable::DeleteSelection( InsertDeleteFlags nDelFlag, const ScMarkData& rMark, bool bBroadcast )
+{
+ { // scope for bulk broadcast
+ ScBulkBroadcast aBulkBroadcast( rDocument.GetBASM(), SfxHintId::ScDataChanged);
+ for (SCCOL i=0; i < aCol.size(); i++)
+ aCol[i].DeleteSelection(nDelFlag, rMark, bBroadcast);
+ }
+
+ ScRangeList aRangeList;
+ rMark.FillRangeListWithMarks(&aRangeList, false);
+
+ for (size_t i = 0; i < aRangeList.size(); ++i)
+ {
+ const ScRange & rRange = aRangeList[i];
+
+ if((nDelFlag & InsertDeleteFlags::ATTRIB) && rRange.aStart.Tab() == nTab)
+ mpCondFormatList->DeleteArea( rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row() );
+ }
+
+ // Do not set protected cell in a protected sheet
+
+ if ( IsProtected() && (nDelFlag & InsertDeleteFlags::ATTRIB) )
+ {
+ ScDocumentPool* pPool = rDocument.GetPool();
+ SfxItemSetFixed<ATTR_PATTERN_START, ATTR_PATTERN_END> aSet( *pPool );
+ aSet.Put( ScProtectionAttr( false ) );
+ SfxItemPoolCache aCache( pPool, &aSet );
+ ApplySelectionCache( &aCache, rMark );
+ }
+
+ // TODO: In the future we may want to check if the table has been
+ // really modified before setting the stream invalid.
+ SetStreamValid(false);
+}
+
+// pTable = Clipboard
+void ScTable::CopyToClip(
+ sc::CopyToClipContext& rCxt, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ ScTable* pTable )
+{
+ if (!ValidColRow(nCol1, nRow1) || !ValidColRow(nCol2, nRow2))
+ return;
+
+ // copy content
+ //local range names need to be copied first for formula cells
+ if (!pTable->mpRangeName && mpRangeName)
+ pTable->mpRangeName.reset( new ScRangeName(*mpRangeName) );
+
+ nCol2 = ClampToAllocatedColumns(nCol2);
+
+ for ( SCCOL i = nCol1; i <= nCol2; i++)
+ aCol[i].CopyToClip(rCxt, nRow1, nRow2, pTable->CreateColumnIfNotExists(i)); // notes are handled at column level
+
+ // copy widths/heights, and only "hidden", "filtered" and "manual" flags
+ // also for all preceding columns/rows, to have valid positions for drawing objects
+
+ if (mpColWidth && pTable->mpColWidth)
+ pTable->mpColWidth->CopyFrom(*mpColWidth, 0, nCol2);
+
+ pTable->CopyColHidden(*this, 0, nCol2);
+ pTable->CopyColFiltered(*this, 0, nCol2);
+ if (pDBDataNoName)
+ pTable->SetAnonymousDBData(std::unique_ptr<ScDBData>(new ScDBData(*pDBDataNoName)));
+
+ if (pRowFlags && pTable->pRowFlags && mpRowHeights && pTable->mpRowHeights)
+ {
+ pTable->pRowFlags->CopyFromAnded( *pRowFlags, 0, nRow2, CRFlags::ManualSize);
+ pTable->CopyRowHeight(*this, 0, nRow2, 0);
+ }
+
+ pTable->CopyRowHidden(*this, 0, nRow2);
+ pTable->CopyRowFiltered(*this, 0, nRow2);
+
+ // If necessary replace formulas with values
+
+ if ( IsProtected() )
+ for (SCCOL i = nCol1; i <= nCol2; i++)
+ pTable->aCol[i].RemoveProtected(nRow1, nRow2);
+
+ pTable->mpCondFormatList.reset(new ScConditionalFormatList(pTable->rDocument, *mpCondFormatList));
+}
+
+void ScTable::CopyToClip(
+ sc::CopyToClipContext& rCxt, const ScRangeList& rRanges, ScTable* pTable )
+{
+ for ( size_t i = 0, nListSize = rRanges.size(); i < nListSize; ++i )
+ {
+ const ScRange & r = rRanges[ i ];
+ CopyToClip( rCxt, r.aStart.Col(), r.aStart.Row(), r.aEnd.Col(), r.aEnd.Row(), pTable);
+ }
+}
+
+void ScTable::CopyStaticToDocument(
+ SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, const SvNumberFormatterMergeMap& rMap, ScTable* pDestTab )
+{
+ if (nCol1 > nCol2 || nRow1 > nRow2)
+ return;
+
+ const SCCOL nFirstUnallocated = std::clamp<SCCOL>(GetAllocatedColumnsCount(), nCol1, nCol2 + 1);
+ if (nFirstUnallocated > nCol1)
+ pDestTab->CreateColumnIfNotExists(nFirstUnallocated - 1);
+
+ for (SCCOL i = nCol1; i < nFirstUnallocated; ++i)
+ {
+ ScColumn& rSrcCol = aCol[i];
+ ScColumn& rDestCol = pDestTab->aCol[i];
+ rSrcCol.CopyStaticToDocument(nRow1, nRow2, rMap, rDestCol);
+ }
+
+ // Maybe copy this table's default attrs to dest not limiting to already allocated in dest?
+ const SCCOL nLastInDest = std::min<SCCOL>(pDestTab->GetAllocatedColumnsCount() - 1, nCol2);
+ for (SCCOL i = nFirstUnallocated; i <= nLastInDest; ++i)
+ {
+ ScColumn& rDestCol = pDestTab->aCol[i];
+ rDestCol.maCellTextAttrs.set_empty(nRow1, nRow2);
+ rDestCol.maCells.set_empty(nRow1, nRow2);
+ for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
+ {
+ sal_uInt32 nNumFmt = aDefaultColData.GetPattern(nRow)->GetNumberFormat(
+ rDocument.GetNonThreadedContext().GetFormatTable());
+ SvNumberFormatterMergeMap::const_iterator itNum = rMap.find(nNumFmt);
+ if (itNum != rMap.end())
+ nNumFmt = itNum->second;
+
+ rDestCol.SetNumberFormat(nRow, nNumFmt);
+ }
+ rDestCol.CellStorageModified();
+ }
+}
+
+void ScTable::CopyCellToDocument(SCCOL nSrcCol, SCROW nSrcRow, SCCOL nDestCol, SCROW nDestRow, ScTable& rDestTab )
+{
+ if (!ValidColRow(nSrcCol, nSrcRow) || !ValidColRow(nDestCol, nDestRow))
+ return;
+
+ if (nSrcCol >= GetAllocatedColumnsCount())
+ {
+ if (nDestCol < rDestTab.GetAllocatedColumnsCount())
+ {
+ ScColumn& rDestCol = rDestTab.aCol[nDestCol];
+ rDestCol.maCells.set_empty(nDestRow, nDestRow);
+ rDestCol.maCellTextAttrs.set_empty(nDestRow, nDestRow);
+ rDestCol.maCellNotes.set_empty(nDestRow, nDestRow);
+ rDestCol.CellStorageModified();
+ }
+ return;
+ }
+
+ ScColumn& rSrcCol = aCol[nSrcCol];
+ ScColumn& rDestCol = rDestTab.CreateColumnIfNotExists(nDestCol);
+ rSrcCol.CopyCellToDocument(nSrcRow, nDestRow, rDestCol);
+}
+
+namespace {
+
+bool CheckAndDeduplicateCondFormat(ScDocument& rDocument, ScConditionalFormat* pOldFormat, const ScConditionalFormat* pNewFormat, SCTAB nTab)
+{
+ if (!pOldFormat)
+ return false;
+
+ if (pOldFormat->EqualEntries(*pNewFormat, true))
+ {
+ const ScRangeList& rNewRangeList = pNewFormat->GetRange();
+ ScRangeList& rDstRangeList = pOldFormat->GetRangeList();
+ for (size_t i = 0; i < rNewRangeList.size(); ++i)
+ {
+ rDstRangeList.Join(rNewRangeList[i]);
+ }
+ rDocument.AddCondFormatData(rNewRangeList, nTab, pOldFormat->GetKey());
+ return true;
+ }
+
+ return false;
+}
+
+}
+
+void ScTable::CopyConditionalFormat( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ SCCOL nDx, SCROW nDy, const ScTable* pTable)
+{
+ ScRange aOldRange( nCol1 - nDx, nRow1 - nDy, pTable->nTab, nCol2 - nDx, nRow2 - nDy, pTable->nTab);
+ ScRange aNewRange( nCol1, nRow1, nTab, nCol2, nRow2, nTab );
+ bool bSameDoc = rDocument.GetStyleSheetPool() == pTable->rDocument.GetStyleSheetPool();
+
+ for(const auto& rxCondFormat : *pTable->mpCondFormatList)
+ {
+ const ScRangeList& rCondFormatRange = rxCondFormat->GetRange();
+ if(!rCondFormatRange.Intersects( aOldRange ))
+ continue;
+
+ ScRangeList aIntersectedRange = rCondFormatRange.GetIntersectedRange(aOldRange);
+ std::unique_ptr<ScConditionalFormat> pNewFormat = rxCondFormat->Clone(&rDocument);
+
+ pNewFormat->SetRange(aIntersectedRange);
+ sc::RefUpdateContext aRefCxt(rDocument);
+ aRefCxt.meMode = URM_COPY;
+ aRefCxt.maRange = aNewRange;
+ aRefCxt.mnColDelta = nDx;
+ aRefCxt.mnRowDelta = nDy;
+ aRefCxt.mnTabDelta = nTab - pTable->nTab;
+ pNewFormat->UpdateReference(aRefCxt, true);
+
+ if (bSameDoc && pTable->nTab == nTab && CheckAndDeduplicateCondFormat(rDocument, mpCondFormatList->GetFormat(rxCondFormat->GetKey()), pNewFormat.get(), nTab))
+ {
+ continue;
+ }
+ sal_uLong nMax = 0;
+ bool bDuplicate = false;
+ for(const auto& rxCond : *mpCondFormatList)
+ {
+ // Check if there is the same format in the destination
+ // If there is, then simply expand its range
+ if (CheckAndDeduplicateCondFormat(rDocument, rxCond.get(), pNewFormat.get(), nTab))
+ {
+ bDuplicate = true;
+ break;
+ }
+
+ if (rxCond->GetKey() > nMax)
+ nMax = rxCond->GetKey();
+ }
+ // Do not add duplicate entries
+ if (bDuplicate)
+ {
+ continue;
+ }
+
+ pNewFormat->SetKey(nMax + 1);
+ auto pNewFormatTmp = pNewFormat.get();
+ mpCondFormatList->InsertNew(std::move(pNewFormat));
+
+ if(!bSameDoc)
+ {
+ for(size_t i = 0, n = pNewFormatTmp->size();
+ i < n; ++i)
+ {
+ OUString aStyleName;
+ const ScFormatEntry* pEntry = pNewFormatTmp->GetEntry(i);
+ if(pEntry->GetType() == ScFormatEntry::Type::Condition ||
+ pEntry->GetType() == ScFormatEntry::Type::ExtCondition)
+ aStyleName = static_cast<const ScCondFormatEntry*>(pEntry)->GetStyle();
+ else if(pEntry->GetType() == ScFormatEntry::Type::Date)
+ aStyleName = static_cast<const ScCondDateFormatEntry*>(pEntry)->GetStyleName();
+
+ if(!aStyleName.isEmpty())
+ {
+ if(rDocument.GetStyleSheetPool()->Find(aStyleName, SfxStyleFamily::Para))
+ continue;
+
+ rDocument.GetStyleSheetPool()->CopyStyleFrom(
+ pTable->rDocument.GetStyleSheetPool(), aStyleName, SfxStyleFamily::Para );
+ }
+ }
+ }
+
+ rDocument.AddCondFormatData( pNewFormatTmp->GetRange(), nTab, pNewFormatTmp->GetKey() );
+ }
+}
+
+bool ScTable::InitColumnBlockPosition( sc::ColumnBlockPosition& rBlockPos, SCCOL nCol )
+{
+ if (!ValidCol(nCol))
+ return false;
+
+ CreateColumnIfNotExists(nCol).InitBlockPosition(rBlockPos);
+ return true;
+}
+
+// pTable is source
+
+void ScTable::CopyFromClip(
+ sc::CopyFromClipContext& rCxt, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ SCCOL nDx, SCROW nDy, ScTable* pTable )
+{
+ if (nCol2 > rDocument.MaxCol())
+ nCol2 = rDocument.MaxCol();
+ if (nRow2 > rDocument.MaxRow())
+ nRow2 = rDocument.MaxRow();
+
+ if (!(ValidColRow(nCol1, nRow1) && ValidColRow(nCol2, nRow2)))
+ return;
+
+ CreateColumnIfNotExists(nCol2);
+ for ( SCCOL i = nCol1; i <= nCol2; i++)
+ {
+ pTable->CreateColumnIfNotExists(i - nDx);
+ aCol[i].CopyFromClip(rCxt, nRow1, nRow2, nDy, pTable->aCol[i - nDx]); // notes are handles at column level
+ }
+
+ if (rCxt.getInsertFlag() & InsertDeleteFlags::ATTRIB)
+ {
+ // make sure that there are no old references to the cond formats
+ sal_uInt16 nWhichArray[2];
+ nWhichArray[0] = ATTR_CONDITIONAL;
+ nWhichArray[1] = 0;
+ for ( SCCOL i = nCol1; i <= nCol2; ++i)
+ aCol[i].ClearItems(nRow1, nRow2, nWhichArray);
+ }
+
+ if ((rCxt.getInsertFlag() & InsertDeleteFlags::ATTRIB) == InsertDeleteFlags::NONE)
+ return;
+
+ if (nRow1==0 && nRow2==rDocument.MaxRow() && mpColWidth && pTable->mpColWidth)
+ mpColWidth->CopyFrom(*pTable->mpColWidth, nCol1, nCol2, nCol1 - nDx);
+
+ if (nCol1==0 && nCol2==rDocument.MaxCol() && mpRowHeights && pTable->mpRowHeights &&
+ pRowFlags && pTable->pRowFlags)
+ {
+ CopyRowHeight(*pTable, nRow1, nRow2, -nDy);
+ // Must copy CRFlags::ManualSize bit too, otherwise pRowHeight doesn't make sense
+ for (SCROW j=nRow1; j<=nRow2; j++)
+ {
+ if ( pTable->pRowFlags->GetValue(j-nDy) & CRFlags::ManualSize )
+ pRowFlags->OrValue( j, CRFlags::ManualSize);
+ else
+ pRowFlags->AndValue( j, ~CRFlags::ManualSize);
+ }
+ }
+
+ // Do not set protected cell in a protected sheet
+ if (IsProtected() && (rCxt.getInsertFlag() & InsertDeleteFlags::ATTRIB))
+ {
+ ScPatternAttr aPattern(rDocument.GetPool());
+ aPattern.GetItemSet().Put( ScProtectionAttr( false ) );
+ ApplyPatternArea( nCol1, nRow1, nCol2, nRow2, aPattern );
+ }
+
+ // create deep copies for conditional formatting
+ CopyConditionalFormat( nCol1, nRow1, nCol2, nRow2, nDx, nDy, pTable);
+}
+
+void ScTable::MixData(
+ sc::MixDocContext& rCxt, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ ScPasteFunc nFunction, bool bSkipEmpty, const ScTable* pSrcTab )
+{
+ for (SCCOL i=nCol1; i<=nCol2; i++)
+ aCol[i].MixData(rCxt, nRow1, nRow2, nFunction, bSkipEmpty, pSrcTab->aCol[i]);
+}
+
+// Selection form this document
+void ScTable::MixMarked(
+ sc::MixDocContext& rCxt, const ScMarkData& rMark, ScPasteFunc nFunction,
+ bool bSkipEmpty, const ScTable* pSrcTab )
+{
+ for (SCCOL i=0; i < aCol.size(); i++)
+ aCol[i].MixMarked(rCxt, rMark, nFunction, bSkipEmpty, pSrcTab->aCol[i]);
+}
+
+namespace {
+
+class TransClipHandler
+{
+ ScTable& mrClipTab;
+ const ScTable& mrSrcTab;
+ SCTAB mnSrcTab;
+ SCCOL mnCol1;
+ SCCOL mnSrcCol;
+ size_t mnTopRow;
+ size_t mnEndRow;
+ SCROW mnTransRow;
+ SCROW mnFilteredRows = 0;
+ SCROW mnRowDestOffset = 0;
+ bool mbAsLink;
+ bool mbWasCut;
+ bool mbIncludeFiltered;
+ InsertDeleteFlags mnFlags;
+
+ ScAddress getDestPos(size_t nRow) const
+ {
+ return ScAddress(static_cast<SCCOL>(mnCol1 + nRow - mnTopRow), mnTransRow,
+ mrClipTab.GetTab());
+ }
+
+ ScFormulaCell* createRefCell(size_t nSrcRow, const ScAddress& rDestPos) const
+ {
+ ScAddress aSrcPos(mnSrcCol, nSrcRow, mnSrcTab);
+ ScSingleRefData aRef;
+ aRef.InitAddress(aSrcPos); // Absolute reference.
+ aRef.SetFlag3D(true);
+
+ ScTokenArray aArr(mrClipTab.GetDoc());
+ aArr.AddSingleReference(aRef);
+ return new ScFormulaCell(mrClipTab.GetDoc(), rDestPos, aArr);
+ }
+
+ void setLink(size_t nRow)
+ {
+ SCCOL nTransCol = mnCol1 + nRow - mnTopRow - mnFilteredRows + mnRowDestOffset;
+ mrClipTab.SetFormulaCell(nTransCol, mnTransRow,
+ createRefCell(nRow, getDestPos(nRow)));
+ }
+
+public:
+ TransClipHandler(ScTable& rClipTab, const ScTable& rSrcTab, SCTAB nSrcTab, SCCOL nCol1,
+ SCCOL nSrcCol, size_t nTopRow, size_t nEndRow, SCROW nCombinedStartRow,
+ SCROW nRowDestOffset, bool bAsLink, bool bWasCut,
+ const InsertDeleteFlags& nFlags, const bool bIncludeFiltered,
+ std::vector<SCROW>& rFilteredRows)
+ : mrClipTab(rClipTab)
+ , mrSrcTab(rSrcTab)
+ , mnSrcTab(nSrcTab)
+ , mnCol1(nCol1)
+ , mnSrcCol(nSrcCol)
+ , mnTopRow(nTopRow)
+ , mnEndRow(nEndRow)
+ , mnTransRow(nSrcCol - nCol1 + nCombinedStartRow)
+ , mnRowDestOffset(nRowDestOffset)
+ , mbAsLink(bAsLink)
+ , mbWasCut(bWasCut)
+ , mbIncludeFiltered(bIncludeFiltered)
+ , mnFlags(nFlags)
+ {
+ // Create list of filtered rows.
+ if (!mbIncludeFiltered)
+ {
+ for (SCROW curRow = nTopRow; curRow <= static_cast<SCROW>(mnEndRow); ++curRow)
+ {
+ // maybe this loop could be optimized
+ bool bFiltered = mrSrcTab.RowFiltered(curRow, nullptr, nullptr);
+ if (bFiltered)
+ rFilteredRows.push_back(curRow);
+ }
+ }
+ }
+
+ void operator() (size_t nRow, double fVal)
+ {
+ bool bFiltered = mrSrcTab.RowFiltered(nRow, nullptr, nullptr);
+ if (!mbIncludeFiltered && bFiltered)
+ {
+ mnFilteredRows++;
+ return;
+ }
+
+ if (mbAsLink)
+ {
+ setLink(nRow);
+ return;
+ }
+
+ SCCOL nTransCol = mnCol1 + nRow - mnTopRow - mnFilteredRows + mnRowDestOffset;
+ mrClipTab.SetValue(nTransCol, mnTransRow, fVal);
+ }
+
+ void operator() (size_t nRow, const svl::SharedString& rStr)
+ {
+ bool bFiltered = mrSrcTab.RowFiltered(nRow, nullptr, nullptr);
+ if (!mbIncludeFiltered && bFiltered)
+ {
+ mnFilteredRows++;
+ return;
+ }
+
+ if (mbAsLink)
+ {
+ setLink(nRow);
+ return;
+ }
+
+ SCCOL nTransCol = mnCol1 + nRow - mnTopRow - mnFilteredRows + mnRowDestOffset;
+ mrClipTab.SetRawString(nTransCol, mnTransRow, rStr);
+ }
+
+ void operator() (size_t nRow, const EditTextObject* p)
+ {
+ bool bFiltered = mrSrcTab.RowFiltered(nRow, nullptr, nullptr);
+ if (!mbIncludeFiltered && bFiltered)
+ {
+ mnFilteredRows++;
+ return;
+ }
+
+ if (mbAsLink)
+ {
+ setLink(nRow);
+ return;
+ }
+
+ SCCOL nTransCol = mnCol1 + nRow - mnTopRow - mnFilteredRows + mnRowDestOffset;
+ mrClipTab.SetEditText(nTransCol, mnTransRow, ScEditUtil::Clone(*p, mrClipTab.GetDoc()));
+ }
+
+ void operator() (size_t nRow, const ScFormulaCell* p)
+ {
+ bool bFiltered = mrSrcTab.RowFiltered(nRow, nullptr, nullptr);
+ if (!mbIncludeFiltered && bFiltered)
+ {
+ mnFilteredRows++;
+ return;
+ }
+
+ if (mbAsLink)
+ {
+ setLink(nRow);
+ return;
+ }
+
+ ScFormulaCell* pNew = new ScFormulaCell(*p, mrClipTab.GetDoc(),
+ getDestPos(nRow - mnFilteredRows + mnRowDestOffset),
+ ScCloneFlags::StartListening);
+
+ // rotate reference
+ // for Cut, the references are later adjusted through UpdateTranspose
+
+ if (!mbWasCut)
+ pNew->TransposeReference();
+
+ SCCOL nTransCol = mnCol1 + nRow - mnTopRow - mnFilteredRows + mnRowDestOffset;
+ mrClipTab.SetFormulaCell(nTransCol, mnTransRow, pNew);
+ }
+
+ // empty cells
+ void operator()(const int /*type*/, size_t nRow, size_t nDataSize)
+ {
+ for (size_t curRow = nRow; curRow < nRow + nDataSize; ++curRow)
+ {
+ bool bFiltered = mrSrcTab.RowFiltered(curRow, nullptr, nullptr);
+ if (!mbIncludeFiltered && bFiltered)
+ {
+ mnFilteredRows++;
+ continue;
+ }
+
+ if (mbAsLink && mnFlags == InsertDeleteFlags::ALL)
+ {
+ // with InsertDeleteFlags::ALL, also create links (formulas) for empty cells
+ setLink(nRow);
+ continue;
+ }
+ }
+ }
+};
+}
+
+void ScTable::TransposeClip(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ SCROW nCombinedStartRow, SCROW nRowDestOffset, ScTable* pTransClip,
+ InsertDeleteFlags nFlags, bool bAsLink, bool bIncludeFiltered)
+{
+ bool bWasCut = rDocument.IsCutMode();
+
+ for (SCCOL nCol : GetWritableColumnsRange(nCol1, nCol2))
+ {
+ std::vector<SCROW> aFilteredRows;
+
+ TransClipHandler aFunc(*pTransClip, *this, nTab, nCol1, nCol, nRow1, nRow2,
+ nCombinedStartRow, nRowDestOffset, bAsLink, bWasCut, nFlags,
+ bIncludeFiltered, aFilteredRows);
+
+ const sc::CellStoreType& rCells = aCol[nCol].maCells;
+
+ // Loop through all rows by iterator and call aFunc operators
+ sc::ParseAll(rCells.begin(), rCells, nRow1, nRow2, aFunc,
+ aFunc);
+
+ // Attributes
+ if (nFlags & InsertDeleteFlags::ATTRIB)
+ TransposeColPatterns(pTransClip, nCol1, nCol, nRow1, nRow2, nCombinedStartRow,
+ bIncludeFiltered, aFilteredRows, nRowDestOffset);
+
+ // Cell Notes - fdo#68381 paste cell notes on Transpose
+ if ((nFlags & InsertDeleteFlags::NOTE) && rDocument.HasColNotes(nCol, nTab))
+ TransposeColNotes(pTransClip, nCol1, nCol, nRow1, nRow2, nCombinedStartRow,
+ bIncludeFiltered, nRowDestOffset);
+ }
+}
+
+static void lcl_SetTransposedPatternInRows(ScTable* pTransClip, SCROW nAttrRow1, SCROW nAttrRow2,
+ SCCOL nCol1, SCROW nRow1, SCROW nCombinedStartRow, SCCOL nCol,
+ const ScPatternAttr& rPatternAttr, bool bIncludeFiltered,
+ const std::vector<SCROW>& rFilteredRows,
+ SCROW nRowDestOffset)
+{
+ for (SCROW nRow = nAttrRow1; nRow <= nAttrRow2; nRow++)
+ {
+ size_t nFilteredRowAdjustment = 0;
+ if (!bIncludeFiltered)
+ {
+ // aFilteredRows is sorted thus lower_bound() can be used.
+ // lower_bound() has a logarithmic complexity O(log(n))
+ auto itRow1 = std::lower_bound(rFilteredRows.begin(), rFilteredRows.end(), nRow1);
+ auto itRow = std::lower_bound(rFilteredRows.begin(), rFilteredRows.end(), nRow);
+ bool bRefRowIsFiltered = itRow != rFilteredRows.end() && *itRow == nRow;
+ if (bRefRowIsFiltered)
+ continue;
+
+ // How many filtered rows are between the formula cell and the reference?
+ // distance() has a constant complexity O(1) for vectors
+ nFilteredRowAdjustment = std::distance(itRow1, itRow);
+ }
+
+ pTransClip->SetPattern(
+ static_cast<SCCOL>(nCol1 + nRow - nRow1 - nFilteredRowAdjustment + nRowDestOffset),
+ static_cast<SCROW>(nCombinedStartRow + nCol - nCol1), rPatternAttr);
+ }
+}
+
+void ScTable::TransposeColPatterns(ScTable* pTransClip, SCCOL nCol1, SCCOL nCol, SCROW nRow1,
+ SCROW nRow2, SCROW nCombinedStartRow, bool bIncludeFiltered,
+ const std::vector<SCROW>& rFilteredRows, SCROW nRowDestOffset)
+{
+ SCROW nAttrRow1 = {}; // spurious -Werror=maybe-uninitialized
+ SCROW nAttrRow2 = {}; // spurious -Werror=maybe-uninitialized
+ const ScPatternAttr* pPattern;
+ std::unique_ptr<ScAttrIterator> pAttrIter(aCol[nCol].CreateAttrIterator( nRow1, nRow2 ));
+ while ( (pPattern = pAttrIter->Next( nAttrRow1, nAttrRow2 )) != nullptr )
+ {
+ if ( !IsDefaultItem( pPattern ) )
+ {
+ const SfxItemSet& rSet = pPattern->GetItemSet();
+ if ( rSet.GetItemState( ATTR_MERGE, false ) == SfxItemState::DEFAULT &&
+ rSet.GetItemState( ATTR_MERGE_FLAG, false ) == SfxItemState::DEFAULT &&
+ rSet.GetItemState( ATTR_BORDER, false ) == SfxItemState::DEFAULT )
+ {
+ // Set pattern in cells from nAttrRow1 to nAttrRow2
+ // no borders or merge items involved - use pattern as-is
+ lcl_SetTransposedPatternInRows(pTransClip, nAttrRow1, nAttrRow2, nCol1, nRow1,
+ nCombinedStartRow, nCol, *pPattern,
+ bIncludeFiltered, rFilteredRows, nRowDestOffset);
+ }
+ else
+ {
+ // transpose borders and merge values, remove merge flags (refreshed after pasting)
+ ScPatternAttr aNewPattern( *pPattern );
+ SfxItemSet& rNewSet = aNewPattern.GetItemSet();
+
+ const SvxBoxItem& rOldBox = rSet.Get(ATTR_BORDER);
+ if ( rOldBox.GetTop() || rOldBox.GetBottom() || rOldBox.GetLeft() || rOldBox.GetRight() )
+ {
+ SvxBoxItem aNew( ATTR_BORDER );
+ aNew.SetLine( rOldBox.GetLine( SvxBoxItemLine::TOP ), SvxBoxItemLine::LEFT );
+ aNew.SetLine( rOldBox.GetLine( SvxBoxItemLine::LEFT ), SvxBoxItemLine::TOP );
+ aNew.SetLine( rOldBox.GetLine( SvxBoxItemLine::BOTTOM ), SvxBoxItemLine::RIGHT );
+ aNew.SetLine( rOldBox.GetLine( SvxBoxItemLine::RIGHT ), SvxBoxItemLine::BOTTOM );
+ aNew.SetDistance( rOldBox.GetDistance( SvxBoxItemLine::TOP ), SvxBoxItemLine::LEFT );
+ aNew.SetDistance( rOldBox.GetDistance( SvxBoxItemLine::LEFT ), SvxBoxItemLine::TOP );
+ aNew.SetDistance( rOldBox.GetDistance( SvxBoxItemLine::BOTTOM ), SvxBoxItemLine::RIGHT );
+ aNew.SetDistance( rOldBox.GetDistance( SvxBoxItemLine::RIGHT ), SvxBoxItemLine::BOTTOM );
+ rNewSet.Put( aNew );
+ }
+
+ const ScMergeAttr& rOldMerge = rSet.Get(ATTR_MERGE);
+ if (rOldMerge.IsMerged())
+ rNewSet.Put( ScMergeAttr( std::min(
+ static_cast<SCCOL>(rOldMerge.GetRowMerge()),
+ static_cast<SCCOL>(rDocument.MaxCol()+1 - (nAttrRow2-nRow1))),
+ std::min(
+ static_cast<SCROW>(rOldMerge.GetColMerge()),
+ static_cast<SCROW>(rDocument.MaxRow()+1 - (nCol-nCol1)))));
+ const ScMergeFlagAttr& rOldFlag = rSet.Get(ATTR_MERGE_FLAG);
+ if (rOldFlag.IsOverlapped())
+ {
+ ScMF nNewFlags = rOldFlag.GetValue() & ~ScMF( ScMF::Hor | ScMF::Ver );
+ if ( nNewFlags != ScMF::NONE )
+ rNewSet.Put( ScMergeFlagAttr( nNewFlags ) );
+ else
+ rNewSet.ClearItem( ATTR_MERGE_FLAG );
+ }
+
+ // Set pattern in cells from nAttrRow1 to nAttrRow2
+ lcl_SetTransposedPatternInRows(pTransClip, nAttrRow1, nAttrRow2, nCol1, nRow1,
+ nCombinedStartRow, nCol, aNewPattern,
+ bIncludeFiltered, rFilteredRows, nRowDestOffset);
+ }
+ }
+ }
+}
+
+void ScTable::TransposeColNotes(ScTable* pTransClip, SCCOL nCol1, SCCOL nCol, SCROW nRow1,
+ SCROW nRow2, SCROW nCombinedStartRow, bool bIncludeFiltered,
+ SCROW nRowDestOffset)
+{
+ sc::CellNoteStoreType::const_iterator itBlk = aCol[nCol].maCellNotes.begin(), itBlkEnd = aCol[nCol].maCellNotes.end();
+
+ // Locate the top row position.
+ size_t nOffsetInBlock = 0;
+ size_t nBlockStart = 0, nBlockEnd = 0, nRowPos = static_cast<size_t>(nRow1);
+ for (; itBlk != itBlkEnd; ++itBlk, nBlockStart = nBlockEnd)
+ {
+ nBlockEnd = nBlockStart + itBlk->size;
+ if (nBlockStart <= nRowPos && nRowPos < nBlockEnd)
+ {
+ // Found.
+ nOffsetInBlock = nRowPos - nBlockStart;
+ break;
+ }
+ }
+
+ if (itBlk == itBlkEnd)
+ // Specified range found
+ return;
+
+ nRowPos = static_cast<size_t>(nRow2); // End row position.
+ SCCOL nFilteredRows = 0;
+
+ // Keep processing until we hit the end row position.
+ sc::cellnote_block::const_iterator itData, itDataEnd;
+ for (; itBlk != itBlkEnd; ++itBlk, nBlockStart = nBlockEnd, nOffsetInBlock = 0)
+ {
+ nBlockEnd = nBlockStart + itBlk->size;
+
+ if (itBlk->data)
+ {
+ itData = sc::cellnote_block::begin(*itBlk->data);
+ std::advance(itData, nOffsetInBlock);
+
+ // selected area is smaller than the iteration block
+ if (nBlockStart <= nRowPos && nRowPos < nBlockEnd)
+ {
+ // This block contains the end row. Only process partially.
+ size_t nOffsetEnd = nRowPos - nBlockStart + 1;
+ itDataEnd = sc::cellnote_block::begin(*itBlk->data);
+ std::advance(itDataEnd, nOffsetEnd);
+ size_t curRow = nBlockStart + nOffsetInBlock;
+ for (; itData != itDataEnd; ++itData, ++curRow)
+ {
+ bool bFiltered = this->RowFiltered(curRow, nullptr, nullptr);
+ if (!bIncludeFiltered && bFiltered)
+ {
+ nFilteredRows++;
+ continue;
+ }
+
+ ScAddress aDestPos(
+ static_cast<SCCOL>(nCol1 + curRow - nRow1 - nFilteredRows + nRowDestOffset),
+ static_cast<SCROW>(nCombinedStartRow + nCol - nCol1), pTransClip->nTab);
+ pTransClip->rDocument.ReleaseNote(aDestPos);
+ ScPostIt* pNote = *itData;
+ if (pNote)
+ {
+ std::unique_ptr<ScPostIt> pClonedNote = pNote->Clone( ScAddress(nCol, curRow, nTab), pTransClip->rDocument, aDestPos, true );
+ pTransClip->rDocument.SetNote(aDestPos, std::move(pClonedNote));
+ }
+ }
+ break; // we reached the last valid block
+ }
+ else
+ {
+ itDataEnd = sc::cellnote_block::end(*itBlk->data);
+ size_t curRow = nBlockStart + nOffsetInBlock;
+ for (; itData != itDataEnd; ++itData, ++curRow)
+ {
+ bool bFiltered = this->RowFiltered(curRow, nullptr, nullptr);
+ if (!bIncludeFiltered && bFiltered)
+ {
+ nFilteredRows++;
+ continue;
+ }
+
+ ScAddress aDestPos(
+ static_cast<SCCOL>(nCol1 + curRow - nRow1 - nFilteredRows + nRowDestOffset),
+ static_cast<SCROW>(nCombinedStartRow + nCol - nCol1), pTransClip->nTab);
+ pTransClip->rDocument.ReleaseNote(aDestPos);
+ ScPostIt* pNote = *itData;
+ if (pNote)
+ {
+ std::unique_ptr<ScPostIt> pClonedNote = pNote->Clone( ScAddress(nCol, curRow, nTab), pTransClip->rDocument, aDestPos, true );
+ pTransClip->rDocument.SetNote(aDestPos, std::move(pClonedNote));
+ }
+ }
+ }
+ }
+ else // remove dest notes for rows without notes
+ {
+ for (size_t curRow = nBlockStart + nOffsetInBlock;
+ curRow <= nBlockEnd && curRow <= nRowPos; ++curRow)
+ {
+ bool bFiltered = this->RowFiltered(curRow, nullptr, nullptr);
+ if (!bIncludeFiltered && bFiltered && curRow < nBlockEnd)
+ {
+ nFilteredRows++;
+ continue;
+ }
+
+ ScAddress aDestPos(
+ static_cast<SCCOL>(nCol1 + curRow - nRow1 - nFilteredRows + nRowDestOffset),
+ static_cast<SCROW>(nCombinedStartRow + nCol - nCol1), pTransClip->nTab);
+ pTransClip->rDocument.ReleaseNote(aDestPos);
+ }
+ }
+ }
+}
+
+ScColumn* ScTable::FetchColumn( SCCOL nCol )
+{
+ if (!ValidCol(nCol))
+ return nullptr;
+
+ return &CreateColumnIfNotExists(nCol);
+}
+
+const ScColumn* ScTable::FetchColumn( SCCOL nCol ) const
+{
+ if (!ValidCol(nCol) || nCol >= GetAllocatedColumnsCount())
+ return nullptr;
+
+ return &aCol[nCol];
+}
+
+void ScTable::StartListeners( sc::StartListeningContext& rCxt, bool bAll )
+{
+ std::shared_ptr<const sc::ColumnSet> pColSet = rCxt.getColumnSet();
+ if (!pColSet)
+ {
+ for (SCCOL i=0; i < aCol.size(); i++)
+ aCol[i].StartListeners(rCxt, bAll);
+ }
+ else if (pColSet->hasTab( nTab))
+ {
+ std::vector<SCCOL> aColumns;
+ pColSet->getColumns( nTab, aColumns);
+ for (auto i : aColumns)
+ {
+ if (0 <= i && i < aCol.size())
+ aCol[i].StartListeners(rCxt, bAll);
+ }
+ }
+}
+
+void ScTable::AttachFormulaCells(
+ sc::StartListeningContext& rCxt, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
+{
+ nCol2 = ClampToAllocatedColumns(nCol2);
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ aCol[nCol].AttachFormulaCells(rCxt, nRow1, nRow2);
+}
+
+void ScTable::DetachFormulaCells(
+ sc::EndListeningContext& rCxt, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
+{
+ nCol2 = ClampToAllocatedColumns(nCol2);
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ aCol[nCol].DetachFormulaCells(rCxt, nRow1, nRow2);
+}
+
+void ScTable::SetDirtyFromClip(
+ SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, sc::ColumnSpanSet& rBroadcastSpans )
+{
+ if ( nCol2 >= aCol.size() ) nCol2 = aCol.size() - 1;
+ if (nCol2 > rDocument.MaxCol()) nCol2 = rDocument.MaxCol();
+ if (nRow2 > rDocument.MaxRow()) nRow2 = rDocument.MaxRow();
+ if (ValidColRow(nCol1, nRow1) && ValidColRow(nCol2, nRow2))
+ for (SCCOL i = nCol1; i <= nCol2; i++)
+ aCol[i].SetDirtyFromClip(nRow1, nRow2, rBroadcastSpans);
+}
+
+void ScTable::StartListeningFormulaCells(
+ sc::StartListeningContext& rStartCxt, sc::EndListeningContext& rEndCxt,
+ SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
+{
+ if ( nCol2 >= aCol.size() ) nCol2 = aCol.size() - 1;
+ if (nCol2 > rDocument.MaxCol()) nCol2 = rDocument.MaxCol();
+ if (nRow2 > rDocument.MaxRow()) nRow2 = rDocument.MaxRow();
+ if (ValidColRow(nCol1, nRow1) && ValidColRow(nCol2, nRow2))
+ for (SCCOL i = nCol1; i <= nCol2; i++)
+ aCol[i].StartListeningFormulaCells(rStartCxt, rEndCxt, nRow1, nRow2);
+}
+
+void ScTable::CopyToTable(
+ sc::CopyToDocContext& rCxt, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ InsertDeleteFlags nFlags, bool bMarked, ScTable* pDestTab, const ScMarkData* pMarkData,
+ bool bAsLink, bool bColRowFlags, bool bGlobalNamesToLocal, bool bCopyCaptions )
+{
+ if (!ValidColRow(nCol1, nRow1) || !ValidColRow(nCol2, nRow2))
+ return;
+
+ const bool bToUndoDoc = pDestTab->rDocument.IsUndo();
+ const bool bFromUndoDoc = rDocument.IsUndo();
+
+ if ((bToUndoDoc || bFromUndoDoc) && (nFlags & InsertDeleteFlags::CONTENTS) && mpRangeName)
+ {
+ // Copying formulas may create sheet-local named expressions on the
+ // destination sheet. Add existing to Undo first.
+ // During Undo restore the previous named expressions.
+ pDestTab->SetRangeName( std::unique_ptr<ScRangeName>( new ScRangeName( *GetRangeName())));
+ if (!pDestTab->rDocument.IsClipOrUndo())
+ {
+ ScDocShell* pDocSh = static_cast<ScDocShell*>(pDestTab->rDocument.GetDocumentShell());
+ if (pDocSh)
+ pDocSh->SetAreasChangedNeedBroadcast();
+ }
+ }
+
+ if (nFlags != InsertDeleteFlags::NONE)
+ {
+ InsertDeleteFlags nTempFlags( nFlags &
+ ~InsertDeleteFlags( InsertDeleteFlags::NOTE | InsertDeleteFlags::ADDNOTES));
+ // tdf#102364 - in some pathological cases CopyToTable() replacing cells with new cells
+ // can lead to repetitive splitting and rejoining of the same formula group, which can get
+ // quadratically expensive with large groups. So do the grouping just once at the end.
+ sc::DelayFormulaGroupingSwitch delayGrouping( pDestTab->rDocument, true );
+ for (SCCOL i = nCol1; i <= ClampToAllocatedColumns(nCol2); i++)
+ aCol[i].CopyToColumn(rCxt, nRow1, nRow2, bToUndoDoc ? nFlags : nTempFlags, bMarked,
+ pDestTab->CreateColumnIfNotExists(i), pMarkData, bAsLink, bGlobalNamesToLocal);
+ }
+
+ if (!bColRowFlags) // Column widths/Row heights/Flags
+ return;
+
+ if (bToUndoDoc && (nFlags & InsertDeleteFlags::ATTRIB))
+ {
+ pDestTab->mpCondFormatList.reset(new ScConditionalFormatList(pDestTab->rDocument, *mpCondFormatList));
+ }
+
+ if (pDBDataNoName)
+ {
+ std::unique_ptr<ScDBData> pNewDBData(new ScDBData(*pDBDataNoName));
+ SCCOL aCol1, aCol2;
+ SCROW aRow1, aRow2;
+ SCTAB aTab;
+ pNewDBData->GetArea(aTab, aCol1, aRow1, aCol2, aRow2);
+ pNewDBData->MoveTo(pDestTab->nTab, aCol1, aRow1, aCol2, aRow2);
+ pDestTab->SetAnonymousDBData(std::move(pNewDBData));
+ }
+ // Charts have to be adjusted when hide/show
+ ScChartListenerCollection* pCharts = pDestTab->rDocument.GetChartListenerCollection();
+
+ bool bFlagChange = false;
+
+ bool bWidth = (nRow1==0 && nRow2==rDocument.MaxRow() && mpColWidth && pDestTab->mpColWidth);
+ bool bHeight = (nCol1==0 && nCol2==rDocument.MaxCol() && mpRowHeights && pDestTab->mpRowHeights);
+
+ if (bWidth || bHeight)
+ {
+ if (bWidth)
+ {
+ auto destTabColWidthIt = pDestTab->mpColWidth->begin() + nCol1;
+ auto thisTabColWidthIt = mpColWidth->begin() + nCol1;
+ pDestTab->mpColWidth->CopyFrom(*mpColWidth, nCol1, nCol2);
+ pDestTab->mpColFlags->CopyFrom(*mpColFlags, nCol1, nCol2);
+ for (SCCOL i = nCol1; i <= nCol2; ++i)
+ {
+ bool bThisHidden = ColHidden(i);
+ bool bHiddenChange = (pDestTab->ColHidden(i) != bThisHidden);
+ bool bChange = bHiddenChange || (*destTabColWidthIt != *thisTabColWidthIt);
+ pDestTab->SetColHidden(i, i, bThisHidden);
+ //TODO: collect changes?
+ if (bHiddenChange && pCharts)
+ pCharts->SetRangeDirty(ScRange( i, 0, nTab, i, rDocument.MaxRow(), nTab ));
+
+ if (bChange)
+ bFlagChange = true;
+
+ ++destTabColWidthIt;
+ ++thisTabColWidthIt;
+ }
+ pDestTab->SetColManualBreaks( std::set(maColManualBreaks) );
+ }
+
+ if (bHeight)
+ {
+ bool bChange = pDestTab->GetRowHeight(nRow1, nRow2) != GetRowHeight(nRow1, nRow2);
+
+ if (bChange)
+ bFlagChange = true;
+
+ pDestTab->CopyRowHeight(*this, nRow1, nRow2, 0);
+ pDestTab->pRowFlags->CopyFrom(*pRowFlags, nRow1, nRow2);
+
+ // Hidden flags.
+ for (SCROW i = nRow1; i <= nRow2; ++i)
+ {
+ SCROW nLastRow;
+ bool bHidden = RowHidden(i, nullptr, &nLastRow);
+ if (nLastRow >= nRow2)
+ // the last row shouldn't exceed the upper bound the caller specified.
+ nLastRow = nRow2;
+
+ bool bHiddenChanged = pDestTab->SetRowHidden(i, nLastRow, bHidden);
+ if (bHiddenChanged && pCharts)
+ // Hidden flags differ.
+ pCharts->SetRangeDirty(ScRange(0, i, nTab, rDocument.MaxCol(), nLastRow, nTab));
+
+ if (bHiddenChanged)
+ bFlagChange = true;
+
+ // Jump to the last row of the identical flag segment.
+ i = nLastRow;
+ }
+
+ // Filtered flags.
+ for (SCROW i = nRow1; i <= nRow2; ++i)
+ {
+ SCROW nLastRow;
+ bool bFiltered = RowFiltered(i, nullptr, &nLastRow);
+ if (nLastRow >= nRow2)
+ // the last row shouldn't exceed the upper bound the caller specified.
+ nLastRow = nRow2;
+ pDestTab->SetRowFiltered(i, nLastRow, bFiltered);
+ i = nLastRow;
+ }
+ pDestTab->SetRowManualBreaks( std::set(maRowManualBreaks) );
+ }
+ }
+
+ if (bFlagChange)
+ pDestTab->InvalidatePageBreaks();
+
+ if(nFlags & InsertDeleteFlags::ATTRIB)
+ {
+ pDestTab->mpCondFormatList->DeleteArea(nCol1, nRow1, nCol2, nRow2);
+ pDestTab->CopyConditionalFormat(nCol1, nRow1, nCol2, nRow2, 0, 0, this);
+ }
+
+ if(nFlags & InsertDeleteFlags::OUTLINE) // also only when bColRowFlags
+ pDestTab->SetOutlineTable( pOutlineTable.get() );
+
+ if (nFlags & InsertDeleteFlags::SPARKLINES)
+ {
+ CopySparklinesToTable(nCol1, nRow1, nCol2, nRow2, pDestTab);
+ }
+
+ if (!bToUndoDoc && bCopyCaptions && (nFlags & (InsertDeleteFlags::NOTE | InsertDeleteFlags::ADDNOTES)))
+ {
+ bool bCloneCaption = (nFlags & InsertDeleteFlags::NOCAPTIONS) == InsertDeleteFlags::NONE;
+ CopyCaptionsToTable( nCol1, nRow1, nCol2, nRow2, pDestTab, bCloneCaption);
+ }
+}
+
+void ScTable::CopySparklinesToTable(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, ScTable* pDestTab)
+{
+ if (!ValidColRow(nCol1, nRow1) || !ValidColRow(nCol2, nRow2))
+ return;
+
+ nCol2 = ClampToAllocatedColumns(nCol2);
+ for (SCCOL i = nCol1; i <= nCol2; i++)
+ {
+ aCol[i].CopyCellSparklinesToDocument(nRow1, nRow2, pDestTab->CreateColumnIfNotExists(i));
+ }
+}
+
+void ScTable::CopyCaptionsToTable( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, ScTable* pDestTab,
+ bool bCloneCaption )
+{
+ if (!ValidColRow(nCol1, nRow1) || !ValidColRow(nCol2, nRow2))
+ return;
+
+ nCol2 = ClampToAllocatedColumns(nCol2);
+ for (SCCOL i = nCol1; i <= nCol2; i++)
+ {
+ aCol[i].CopyCellNotesToDocument(nRow1, nRow2, pDestTab->CreateColumnIfNotExists(i), bCloneCaption);
+ pDestTab->aCol[i].UpdateNoteCaptions(nRow1, nRow2);
+ }
+}
+
+void ScTable::UndoToTable(
+ sc::CopyToDocContext& rCxt, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ InsertDeleteFlags nFlags, bool bMarked, ScTable* pDestTab )
+{
+ if (!(ValidColRow(nCol1, nRow1) && ValidColRow(nCol2, nRow2)))
+ return;
+
+ bool bWidth = (nRow1==0 && nRow2==rDocument.MaxRow() && mpColWidth && pDestTab->mpColWidth);
+ bool bHeight = (nCol1==0 && nCol2==rDocument.MaxCol() && mpRowHeights && pDestTab->mpRowHeights);
+
+ if ((nFlags & InsertDeleteFlags::CONTENTS) && mpRangeName)
+ {
+ // Undo sheet-local named expressions created during copying
+ // formulas. If mpRangeName is not set then the Undo wasn't even
+ // set to an empty ScRangeName map so don't "undo" that.
+ pDestTab->SetRangeName( std::unique_ptr<ScRangeName>( new ScRangeName( *GetRangeName())));
+ if (!pDestTab->rDocument.IsClipOrUndo())
+ {
+ ScDocShell* pDocSh = static_cast<ScDocShell*>(pDestTab->rDocument.GetDocumentShell());
+ if (pDocSh)
+ pDocSh->SetAreasChangedNeedBroadcast();
+ }
+
+ }
+
+ for ( SCCOL i = 0; i < aCol.size(); i++)
+ {
+ auto& rDestCol = pDestTab->CreateColumnIfNotExists(i);
+ if ( i >= nCol1 && i <= nCol2 )
+ aCol[i].UndoToColumn(rCxt, nRow1, nRow2, nFlags, bMarked, rDestCol);
+ else
+ aCol[i].CopyToColumn(rCxt, 0, rDocument.MaxRow(), InsertDeleteFlags::FORMULA, false, rDestCol);
+ }
+
+ if (nFlags & InsertDeleteFlags::ATTRIB)
+ pDestTab->mpCondFormatList.reset(new ScConditionalFormatList(pDestTab->rDocument, *mpCondFormatList));
+
+ if (!(bWidth||bHeight))
+ return;
+
+ if (bWidth)
+ {
+ pDestTab->mpColWidth->CopyFrom(*mpColWidth, nCol1, nCol2);
+ pDestTab->SetColManualBreaks( std::set(maColManualBreaks) );
+ }
+ if (bHeight)
+ {
+ pDestTab->CopyRowHeight(*this, nRow1, nRow2, 0);
+ pDestTab->SetRowManualBreaks( std::set(maRowManualBreaks) );
+ }
+}
+
+void ScTable::CopyUpdated( const ScTable* pPosTab, ScTable* pDestTab ) const
+{
+ pDestTab->CreateColumnIfNotExists(aCol.size()-1);
+ for (SCCOL i=0; i < aCol.size(); i++)
+ aCol[i].CopyUpdated( pPosTab->FetchColumn(i), pDestTab->aCol[i] );
+}
+
+void ScTable::InvalidateTableArea()
+{
+ bTableAreaValid = false;
+ bTableAreaVisibleValid = false;
+}
+
+void ScTable::InvalidatePageBreaks()
+{
+ mbPageBreaksValid = false;
+}
+
+void ScTable::CopyScenarioTo( ScTable* pDestTab ) const
+{
+ OSL_ENSURE( bScenario, "bScenario == FALSE" );
+
+ for (SCCOL i=0; i < aCol.size(); i++)
+ aCol[i].CopyScenarioTo( pDestTab->CreateColumnIfNotExists(i) );
+}
+
+void ScTable::CopyScenarioFrom( const ScTable* pSrcTab )
+{
+ OSL_ENSURE( bScenario, "bScenario == FALSE" );
+
+ SCCOL nEndCol = pSrcTab->aCol.size();
+ CreateColumnIfNotExists(nEndCol);
+ for (SCCOL i=0; i < nEndCol; i++)
+ aCol[i].CopyScenarioFrom( pSrcTab->aCol[i] );
+}
+
+void ScTable::MarkScenarioIn( ScMarkData& rDestMark, ScScenarioFlags nNeededBits ) const
+{
+ OSL_ENSURE( bScenario, "bScenario == FALSE" );
+
+ if ( ( nScenarioFlags & nNeededBits ) != nNeededBits ) // Are all Bits set?
+ return;
+
+ for (SCCOL i=0; i < aCol.size(); i++)
+ aCol[i].MarkScenarioIn( rDestMark );
+}
+
+bool ScTable::HasScenarioRange( const ScRange& rRange ) const
+{
+ OSL_ENSURE( bScenario, "bScenario == FALSE" );
+
+ ScRange aTabRange = rRange;
+ aTabRange.aStart.SetTab( nTab );
+ aTabRange.aEnd.SetTab( nTab );
+
+ const ScRangeList* pList = GetScenarioRanges();
+
+ if (pList)
+ {
+ for ( size_t j = 0, n = pList->size(); j < n; j++ )
+ {
+ const ScRange & rR = (*pList)[j];
+ if ( rR.Intersects( aTabRange ) )
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void ScTable::InvalidateScenarioRanges()
+{
+ pScenarioRanges.reset();
+}
+
+const ScRangeList* ScTable::GetScenarioRanges() const
+{
+ OSL_ENSURE( bScenario, "bScenario == FALSE" );
+
+ if (!pScenarioRanges)
+ {
+ const_cast<ScTable*>(this)->pScenarioRanges.reset(new ScRangeList);
+ ScMarkData aMark(rDocument.GetSheetLimits());
+ MarkScenarioIn( aMark, ScScenarioFlags::NONE ); // always
+ aMark.FillRangeListWithMarks( pScenarioRanges.get(), false );
+ }
+ return pScenarioRanges.get();
+}
+
+bool ScTable::TestCopyScenarioTo( const ScTable* pDestTab ) const
+{
+ OSL_ENSURE( bScenario, "bScenario == FALSE" );
+
+ if (!pDestTab->IsProtected())
+ return true;
+
+ bool bOk = true;
+ for (SCCOL i=0; i < aCol.size() && bOk; i++)
+ bOk = aCol[i].TestCopyScenarioTo( pDestTab->aCol[i] );
+ return bOk;
+}
+
+bool ScTable::SetString( SCCOL nCol, SCROW nRow, SCTAB nTabP, const OUString& rString,
+ const ScSetStringParam * pParam )
+{
+ if (!ValidColRow(nCol,nRow))
+ {
+ return false;
+ }
+
+ return CreateColumnIfNotExists(nCol).SetString(
+ nRow, nTabP, rString, rDocument.GetAddressConvention(), pParam);
+}
+
+bool ScTable::SetEditText( SCCOL nCol, SCROW nRow, std::unique_ptr<EditTextObject> pEditText )
+{
+ if (!ValidColRow(nCol, nRow))
+ {
+ return false;
+ }
+
+ CreateColumnIfNotExists(nCol).SetEditText(nRow, std::move(pEditText));
+ return true;
+}
+
+void ScTable::SetEditText( SCCOL nCol, SCROW nRow, const EditTextObject& rEditText, const SfxItemPool* pEditPool )
+{
+ if (!ValidColRow(nCol, nRow))
+ return;
+
+ CreateColumnIfNotExists(nCol).SetEditText(nRow, rEditText, pEditPool);
+}
+
+SCROW ScTable::GetFirstEditTextRow( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) const
+{
+ if (!ValidCol(nCol1) || !ValidCol(nCol2) || nCol2 < nCol1)
+ return -1;
+
+ if (!ValidRow(nRow1) || !ValidRow(nRow2) || nRow2 < nRow1)
+ return -1;
+
+ nCol2 = ClampToAllocatedColumns(nCol2);
+ SCROW nFirst = rDocument.MaxRow()+1;
+ for (SCCOL i = nCol1; i <= nCol2; ++i)
+ {
+ const ScColumn& rCol = aCol[i];
+ SCROW nThisFirst = -1;
+ if (const_cast<ScColumn&>(rCol).HasEditCells(nRow1, nRow2, nThisFirst))
+ {
+ if (nThisFirst == nRow1)
+ return nRow1;
+
+ if (nThisFirst < nFirst)
+ nFirst = nThisFirst;
+ }
+ }
+
+ return nFirst == (rDocument.MaxRow()+1) ? -1 : nFirst;
+}
+
+void ScTable::SetEmptyCell( SCCOL nCol, SCROW nRow )
+{
+ if (!ValidColRow(nCol, nRow) || nCol >= GetAllocatedColumnsCount())
+ return;
+
+ aCol[nCol].Delete(nRow);
+}
+
+void ScTable::SetFormula(
+ SCCOL nCol, SCROW nRow, const ScTokenArray& rArray, formula::FormulaGrammar::Grammar eGram )
+{
+ if (!ValidColRow(nCol, nRow))
+ return;
+
+ CreateColumnIfNotExists(nCol).SetFormula(nRow, rArray, eGram);
+}
+
+void ScTable::SetFormula(
+ SCCOL nCol, SCROW nRow, const OUString& rFormula, formula::FormulaGrammar::Grammar eGram )
+{
+ if (!ValidColRow(nCol, nRow))
+ return;
+
+ CreateColumnIfNotExists(nCol).SetFormula(nRow, rFormula, eGram);
+}
+
+ScFormulaCell* ScTable::SetFormulaCell( SCCOL nCol, SCROW nRow, ScFormulaCell* pCell )
+{
+ if (!ValidColRow(nCol, nRow))
+ {
+ delete pCell;
+ return nullptr;
+ }
+
+ return CreateColumnIfNotExists(nCol).SetFormulaCell(nRow, pCell, sc::ConvertToGroupListening);
+}
+
+bool ScTable::SetFormulaCells( SCCOL nCol, SCROW nRow, std::vector<ScFormulaCell*>& rCells )
+{
+ if (!ValidCol(nCol))
+ return false;
+
+ return CreateColumnIfNotExists(nCol).SetFormulaCells(nRow, rCells);
+}
+
+svl::SharedString ScTable::GetSharedString( SCCOL nCol, SCROW nRow ) const
+{
+ if (!ValidColRow(nCol, nRow) || nCol >= GetAllocatedColumnsCount())
+ return svl::SharedString();
+
+ return aCol[nCol].GetSharedString(nRow);
+}
+
+void ScTable::SetValue( SCCOL nCol, SCROW nRow, const double& rVal )
+{
+ if (ValidColRow(nCol, nRow))
+ CreateColumnIfNotExists(nCol).SetValue(nRow, rVal);
+}
+
+void ScTable::SetRawString( SCCOL nCol, SCROW nRow, const svl::SharedString& rStr )
+{
+ if (ValidColRow(nCol, nRow))
+ CreateColumnIfNotExists(nCol).SetRawString(nRow, rStr);
+}
+
+OUString ScTable::GetString( SCCOL nCol, SCROW nRow, const ScInterpreterContext* pContext ) const
+{
+ if (ValidColRow(nCol,nRow) && nCol < GetAllocatedColumnsCount())
+ return aCol[nCol].GetString( nRow, pContext );
+ else
+ return OUString();
+}
+
+double* ScTable::GetValueCell( SCCOL nCol, SCROW nRow )
+{
+ if (!ValidColRow(nCol, nRow))
+ return nullptr;
+
+ return CreateColumnIfNotExists(nCol).GetValueCell(nRow);
+}
+
+OUString ScTable::GetInputString( SCCOL nCol, SCROW nRow, const svl::SharedString** pShared, bool bForceSystemLocale ) const
+{
+ if (ValidColRow(nCol, nRow) && nCol < GetAllocatedColumnsCount())
+ return aCol[nCol].GetInputString( nRow, pShared, bForceSystemLocale );
+ else
+ return OUString();
+}
+
+double ScTable::GetValue( SCCOL nCol, SCROW nRow ) const
+{
+ if (ValidColRow(nCol, nRow) && nCol < GetAllocatedColumnsCount())
+ return aCol[nCol].GetValue( nRow );
+ return 0.0;
+}
+
+const EditTextObject* ScTable::GetEditText( SCCOL nCol, SCROW nRow ) const
+{
+ if (!ValidColRow(nCol, nRow) || nCol >= GetAllocatedColumnsCount())
+ return nullptr;
+
+ return aCol[nCol].GetEditText(nRow);
+}
+
+void ScTable::RemoveEditTextCharAttribs( SCCOL nCol, SCROW nRow, const ScPatternAttr& rAttr )
+{
+ if (!ValidColRow(nCol, nRow) || nCol >= GetAllocatedColumnsCount())
+ return;
+
+ return aCol[nCol].RemoveEditTextCharAttribs(nRow, rAttr);
+}
+
+OUString ScTable::GetFormula( SCCOL nCol, SCROW nRow ) const
+{
+ if (ValidColRow(nCol, nRow) && nCol < GetAllocatedColumnsCount())
+ return aCol[nCol].GetFormula( nRow );
+ else
+ return OUString();
+}
+
+const ScFormulaCell* ScTable::GetFormulaCell( SCCOL nCol, SCROW nRow ) const
+{
+ if (!ValidColRow(nCol, nRow) || nCol >= GetAllocatedColumnsCount())
+ return nullptr;
+
+ return aCol[nCol].GetFormulaCell(nRow);
+}
+
+ScFormulaCell* ScTable::GetFormulaCell( SCCOL nCol, SCROW nRow )
+{
+ if (!ValidColRow(nCol, nRow) || nCol >= GetAllocatedColumnsCount())
+ return nullptr;
+
+ return aCol[nCol].GetFormulaCell(nRow);
+}
+
+// Sparklines
+
+std::shared_ptr<sc::Sparkline> ScTable::GetSparkline(SCCOL nCol, SCROW nRow)
+{
+ if (!ValidCol(nCol) || nCol >= GetAllocatedColumnsCount())
+ return std::shared_ptr<sc::Sparkline>();
+
+ sc::SparklineCell* pSparklineCell = aCol[nCol].GetSparklineCell(nRow);
+ if (!pSparklineCell)
+ return std::shared_ptr<sc::Sparkline>();
+
+ return pSparklineCell->getSparkline();
+}
+
+sc::Sparkline* ScTable::CreateSparkline(SCCOL nCol, SCROW nRow, std::shared_ptr<sc::SparklineGroup> const& pSparklineGroup)
+{
+ if (!ValidCol(nCol))
+ return nullptr;
+
+ ScColumn& rColumn = CreateColumnIfNotExists(nCol);
+
+ std::shared_ptr<sc::Sparkline> pSparkline(new sc::Sparkline(nCol, nRow, pSparklineGroup));
+ rColumn.CreateSparklineCell(nRow, pSparkline);
+
+ return pSparkline.get();
+}
+
+bool ScTable::DeleteSparkline(SCCOL nCol, SCROW nRow)
+{
+ if (!ValidCol(nCol) || nCol >= GetAllocatedColumnsCount())
+ return false;
+
+ aCol[nCol].DeleteSparkline(nRow);
+
+ return true;
+}
+
+sc::SparklineList& ScTable::GetSparklineList()
+{
+ return maSparklineList;
+}
+
+// Notes
+
+std::unique_ptr<ScPostIt> ScTable::ReleaseNote( SCCOL nCol, SCROW nRow )
+{
+ if (!ValidCol(nCol) || nCol >= GetAllocatedColumnsCount())
+ return nullptr;
+
+ return aCol[nCol].ReleaseNote(nRow);
+}
+
+ScPostIt* ScTable::GetNote( SCCOL nCol, SCROW nRow )
+{
+ if (!ValidCol(nCol) || nCol >= GetAllocatedColumnsCount())
+ return nullptr;
+ return aCol[nCol].GetCellNote(nRow);
+}
+
+void ScTable::SetNote( SCCOL nCol, SCROW nRow, std::unique_ptr<ScPostIt> pNote )
+{
+ if (!ValidColRow(nCol, nRow))
+ return;
+
+ CreateColumnIfNotExists(nCol).SetCellNote(nRow, std::move(pNote));
+}
+
+size_t ScTable::GetNoteCount( SCCOL nCol ) const
+{
+ if (!ValidCol(nCol) || nCol >= GetAllocatedColumnsCount())
+ return 0;
+
+ return aCol[nCol].GetNoteCount();
+}
+
+SCROW ScTable::GetNotePosition( SCCOL nCol, size_t nIndex ) const
+{
+ if (!ValidCol(nCol) || nCol >= GetAllocatedColumnsCount())
+ return -1;
+
+ return aCol[nCol].GetNotePosition(nIndex);
+}
+
+void ScTable::CreateAllNoteCaptions()
+{
+ for (SCCOL i = 0; i < aCol.size(); ++i)
+ aCol[i].CreateAllNoteCaptions();
+}
+
+void ScTable::ForgetNoteCaptions( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, bool bPreserveData )
+{
+ if (!ValidCol(nCol1) || !ValidCol(nCol2))
+ return;
+ if ( nCol2 >= aCol.size() ) nCol2 = aCol.size() - 1;
+ for (SCCOL i = nCol1; i <= nCol2; ++i)
+ aCol[i].ForgetNoteCaptions(nRow1, nRow2, bPreserveData);
+}
+
+void ScTable::GetAllNoteEntries( std::vector<sc::NoteEntry>& rNotes ) const
+{
+ for (SCCOL nCol = 0; nCol < aCol.size(); ++nCol)
+ aCol[nCol].GetAllNoteEntries(rNotes);
+}
+
+void ScTable::GetNotesInRange( const ScRange& rRange, std::vector<sc::NoteEntry>& rNotes ) const
+{
+ SCROW nStartRow = rRange.aStart.Row();
+ SCROW nEndRow = rRange.aEnd.Row();
+ SCCOL nEndCol = ClampToAllocatedColumns(rRange.aEnd.Col());
+ for (SCCOL nCol = rRange.aStart.Col(); nCol <= nEndCol; ++nCol)
+ {
+ aCol[nCol].GetNotesInRange(nStartRow, nEndRow, rNotes);
+ }
+}
+
+CommentCaptionState ScTable::GetAllNoteCaptionsState(const ScRange& rRange, std::vector<sc::NoteEntry>& rNotes )
+{
+ SCROW nStartRow = rRange.aStart.Row();
+ SCROW nEndRow = rRange.aEnd.Row();
+ bool bIsFirstNoteShownState = true; // because of error: -Werror=maybe-uninitialized
+ bool bFirstControl = true;
+
+ ScTable* pTab = rDocument.FetchTable(nTab);
+ assert(pTab);
+ const SCCOL nEndCol = pTab->ClampToAllocatedColumns(rRange.aEnd.Col());
+ for (SCCOL nCol = rRange.aStart.Col(); nCol <= nEndCol; ++nCol)
+ {
+ if (bFirstControl && rDocument.HasColNotes(nCol, nTab)) // detect status of first note caption
+ {
+ aCol[nCol].GetNotesInRange(nStartRow, nEndRow, rNotes);
+ bIsFirstNoteShownState = rNotes.begin()->mpNote->IsCaptionShown();
+ bFirstControl = false;
+ }
+
+ if (rDocument.HasColNotes(nCol, nTab))
+ {
+ aCol[nCol].GetNotesInRange(nStartRow, nEndRow, rNotes);
+
+ bool bIsMixedState = std::any_of(rNotes.begin(), rNotes.end(), [bIsFirstNoteShownState](const sc::NoteEntry& rNote) {
+ // compare the first note caption with others
+ return bIsFirstNoteShownState != rNote.mpNote->IsCaptionShown(); });
+ if (bIsMixedState)
+ return CommentCaptionState::MIXED;
+ }
+ }
+ return bIsFirstNoteShownState ? CommentCaptionState::ALLSHOWN : CommentCaptionState::ALLHIDDEN;
+}
+
+void ScTable::GetUnprotectedCells( ScRangeList& rRangeList ) const
+{
+ for (auto const & pCol : aCol)
+ pCol->GetUnprotectedCells(0, rDocument.MaxRow(), rRangeList);
+}
+
+bool ScTable::ContainsNotesInRange( const ScRange& rRange ) const
+{
+ SCROW nStartRow = rRange.aStart.Row();
+ SCROW nEndRow = rRange.aEnd.Row();
+ SCCOL nEndCol = ClampToAllocatedColumns(rRange.aEnd.Col());
+ for (SCCOL nCol = rRange.aStart.Col(); nCol <= nEndCol; ++nCol)
+ {
+ bool bContainsNote = !aCol[nCol].IsNotesEmptyBlock(nStartRow, nEndRow);
+ if(bContainsNote)
+ return true;
+ }
+
+ return false;
+}
+
+CellType ScTable::GetCellType( SCCOL nCol, SCROW nRow ) const
+{
+ if (ValidColRow(nCol, nRow) && nCol < GetAllocatedColumnsCount())
+ return aCol[nCol].GetCellType( nRow );
+ return CELLTYPE_NONE;
+}
+
+ScRefCellValue ScTable::GetCellValue( SCCOL nCol, SCROW nRow ) const
+{
+ if (!ValidColRow(nCol, nRow) || nCol >= GetAllocatedColumnsCount())
+ return ScRefCellValue();
+
+ return aCol[nCol].GetCellValue(nRow);
+}
+
+ScRefCellValue ScTable::GetCellValue( SCCOL nCol, sc::ColumnBlockPosition& rBlockPos, SCROW nRow )
+{
+ if (!ValidColRow(nCol, nRow) || nCol >= GetAllocatedColumnsCount())
+ return ScRefCellValue();
+
+ return aCol[nCol].GetCellValue(rBlockPos, nRow);
+}
+
+void ScTable::GetFirstDataPos(SCCOL& rCol, SCROW& rRow) const
+{
+ rCol = 0;
+ rRow = rDocument.MaxRow()+1;
+ while (rCol < (aCol.size() - 1) && aCol[rCol].IsEmptyData() )
+ ++rCol;
+ SCCOL nCol = rCol;
+ while (nCol < aCol.size() && rRow > 0)
+ {
+ if (!aCol[nCol].IsEmptyData())
+ rRow = ::std::min( rRow, aCol[nCol].GetFirstDataPos());
+ ++nCol;
+ }
+}
+
+void ScTable::GetLastDataPos(SCCOL& rCol, SCROW& rRow) const
+{
+ rCol = aCol.size() - 1;
+ rRow = 0;
+ while (aCol[rCol].IsEmptyData() && (rCol > 0))
+ rCol--;
+ SCCOL nCol = rCol;
+ while (nCol >= 0 && rRow < rDocument.MaxRow())
+ rRow = ::std::max( rRow, aCol[nCol--].GetLastDataPos());
+}
+
+bool ScTable::HasData( SCCOL nCol, SCROW nRow ) const
+{
+ if (ValidColRow(nCol, nRow) && nCol < GetAllocatedColumnsCount())
+ return aCol[nCol].HasDataAt( nRow );
+ else
+ return false;
+}
+
+bool ScTable::HasStringData( SCCOL nCol, SCROW nRow ) const
+{
+ if (ValidColRow(nCol, nRow) && nCol < GetAllocatedColumnsCount())
+ return aCol[nCol].HasStringData( nRow );
+ else
+ return false;
+}
+
+bool ScTable::HasValueData( SCCOL nCol, SCROW nRow ) const
+{
+ if (ValidColRow(nCol, nRow) && nCol < GetAllocatedColumnsCount())
+ return aCol[nCol].HasValueData( nRow );
+ else
+ return false;
+}
+
+bool ScTable::HasStringCells( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL nEndCol, SCROW nEndRow ) const
+{
+ if (ValidCol(nEndCol))
+ {
+ nEndCol = ClampToAllocatedColumns(nEndCol);
+ for (SCCOL nCol = nStartCol; nCol <= nEndCol; nCol++)
+ if (aCol[nCol].HasStringCells(nStartRow, nEndRow))
+ return true;
+ }
+
+ return false;
+}
+
+void ScTable::SetDirtyVar()
+{
+ for (SCCOL i=0; i < aCol.size(); i++)
+ aCol[i].SetDirtyVar();
+}
+
+void ScTable::CheckVectorizationState()
+{
+ sc::AutoCalcSwitch aACSwitch(rDocument, false);
+
+ for (SCCOL i = 0; i < aCol.size(); i++)
+ aCol[i].CheckVectorizationState();
+}
+
+void ScTable::SetAllFormulasDirty( const sc::SetFormulaDirtyContext& rCxt )
+{
+ sc::AutoCalcSwitch aACSwitch(rDocument, false);
+
+ for (SCCOL i=0; i < aCol.size(); i++)
+ aCol[i].SetAllFormulasDirty(rCxt);
+}
+
+void ScTable::SetDirty( const ScRange& rRange, ScColumn::BroadcastMode eMode )
+{
+ sc::AutoCalcSwitch aSwitch(rDocument, false);
+ SCCOL nCol2 = rRange.aEnd.Col();
+ nCol2 = ClampToAllocatedColumns(nCol2);
+ for (SCCOL i=rRange.aStart.Col(); i<=nCol2; i++)
+ aCol[i].SetDirty(rRange.aStart.Row(), rRange.aEnd.Row(), eMode);
+}
+
+void ScTable::SetTableOpDirty( const ScRange& rRange )
+{
+ sc::AutoCalcSwitch aSwitch(rDocument, false);
+ const SCCOL nCol2 = ClampToAllocatedColumns(rRange.aEnd.Col());
+ for (SCCOL i=rRange.aStart.Col(); i<=nCol2; i++)
+ aCol[i].SetTableOpDirty( rRange );
+}
+
+void ScTable::SetDirtyAfterLoad()
+{
+ sc::AutoCalcSwitch aSwitch(rDocument, false);
+ for (SCCOL i=0; i < aCol.size(); i++)
+ aCol[i].SetDirtyAfterLoad();
+}
+
+void ScTable::SetDirtyIfPostponed()
+{
+ sc::AutoCalcSwitch aSwitch(rDocument, false);
+ ScBulkBroadcast aBulkBroadcast( rDocument.GetBASM(), SfxHintId::ScDataChanged);
+ for (SCCOL i=0; i < aCol.size(); i++)
+ aCol[i].SetDirtyIfPostponed();
+}
+
+void ScTable::BroadcastRecalcOnRefMove()
+{
+ sc::AutoCalcSwitch aSwitch(rDocument, false);
+ for (SCCOL i = 0; i < aCol.size(); ++i)
+ aCol[i].BroadcastRecalcOnRefMove();
+}
+
+bool ScTable::BroadcastBroadcasters( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, SfxHintId nHint )
+{
+ bool bBroadcasted = false;
+ sc::AutoCalcSwitch aSwitch(rDocument, false);
+ nCol2 = ClampToAllocatedColumns(nCol2);
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ bBroadcasted |= aCol[nCol].BroadcastBroadcasters( nRow1, nRow2, nHint);
+ return bBroadcasted;
+}
+
+void ScTable::SetLoadingMedium(bool bLoading)
+{
+ mpRowHeights->enableTreeSearch(!bLoading);
+}
+
+void ScTable::CalcAll()
+{
+ for (SCCOL i=0; i < aCol.size(); i++)
+ aCol[i].CalcAll();
+
+ mpCondFormatList->CalcAll();
+}
+
+void ScTable::CompileAll( sc::CompileFormulaContext& rCxt )
+{
+ for (SCCOL i = 0; i < aCol.size(); ++i)
+ aCol[i].CompileAll(rCxt);
+
+ if(mpCondFormatList)
+ mpCondFormatList->CompileAll();
+}
+
+void ScTable::CompileXML( sc::CompileFormulaContext& rCxt, ScProgress& rProgress )
+{
+ if (mpRangeName)
+ mpRangeName->CompileUnresolvedXML(rCxt);
+
+ for (SCCOL i=0; i < aCol.size(); i++)
+ {
+ aCol[i].CompileXML(rCxt, rProgress);
+ }
+
+ if(mpCondFormatList)
+ mpCondFormatList->CompileXML();
+}
+
+bool ScTable::CompileErrorCells( sc::CompileFormulaContext& rCxt, FormulaError nErrCode )
+{
+ bool bCompiled = false;
+ for (SCCOL i = 0; i < aCol.size(); ++i)
+ {
+ if (aCol[i].CompileErrorCells(rCxt, nErrCode))
+ bCompiled = true;
+ }
+
+ return bCompiled;
+}
+
+void ScTable::CalcAfterLoad( sc::CompileFormulaContext& rCxt, bool bStartListening )
+{
+ for (SCCOL i = 0; i < aCol.size(); ++i)
+ aCol[i].CalcAfterLoad(rCxt, bStartListening);
+}
+
+void ScTable::ResetChanged( const ScRange& rRange )
+{
+ SCCOL nStartCol = rRange.aStart.Col();
+ SCROW nStartRow = rRange.aStart.Row();
+ SCCOL nEndCol = ClampToAllocatedColumns(rRange.aEnd.Col());
+ SCROW nEndRow = rRange.aEnd.Row();
+
+ for (SCCOL nCol=nStartCol; nCol<=nEndCol; nCol++)
+ aCol[nCol].ResetChanged(nStartRow, nEndRow);
+}
+
+// Attribute
+
+const SfxPoolItem* ScTable::GetAttr( SCCOL nCol, SCROW nRow, sal_uInt16 nWhich ) const
+{
+ if (!ValidColRow(nCol, nRow))
+ return nullptr;
+ return &ColumnData(nCol).GetAttr( nRow, nWhich );
+}
+
+const SfxPoolItem* ScTable::GetAttr( SCCOL nCol, SCROW nRow, sal_uInt16 nWhich, SCROW& nStartRow, SCROW& nEndRow ) const
+{
+ if (!ValidColRow(nCol, nRow))
+ return nullptr;
+ return &ColumnData(nCol).GetAttr( nRow, nWhich, nStartRow, nEndRow );
+}
+
+sal_uInt32 ScTable::GetNumberFormat( const ScInterpreterContext& rContext, const ScAddress& rPos ) const
+{
+ if (ValidColRow(rPos.Col(), rPos.Row()))
+ return ColumnData(rPos.Col()).GetNumberFormat(rContext, rPos.Row());
+ return 0;
+}
+
+sal_uInt32 ScTable::GetNumberFormat( SCCOL nCol, SCROW nRow ) const
+{
+ return GetNumberFormat(rDocument.GetNonThreadedContext(), ScAddress(nCol, nRow, nTab));
+}
+
+sal_uInt32 ScTable::GetNumberFormat( SCCOL nCol, SCROW nStartRow, SCROW nEndRow ) const
+{
+ if (!ValidCol(nCol) || !ValidRow(nStartRow) || !ValidRow(nEndRow))
+ return 0;
+
+ return ColumnData(nCol).GetNumberFormat(nStartRow, nEndRow);
+}
+
+void ScTable::SetNumberFormat( SCCOL nCol, SCROW nRow, sal_uInt32 nNumberFormat )
+{
+ if (!ValidColRow(nCol, nRow))
+ return;
+
+ CreateColumnIfNotExists(nCol).SetNumberFormat(nRow, nNumberFormat);
+}
+
+const ScPatternAttr* ScTable::GetPattern( SCCOL nCol, SCROW nRow ) const
+{
+ if (!ValidColRow(nCol,nRow))
+ return nullptr;
+ return ColumnData(nCol).GetPattern( nRow );
+}
+
+const ScPatternAttr* ScTable::GetMostUsedPattern( SCCOL nCol, SCROW nStartRow, SCROW nEndRow ) const
+{
+ if ( ValidColRow( nCol, nStartRow ) && ValidRow( nEndRow ) && (nStartRow <= nEndRow))
+ return ColumnData(nCol).GetMostUsedPattern( nStartRow, nEndRow );
+ return nullptr;
+}
+
+bool ScTable::HasAttrib( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, HasAttrFlags nMask ) const
+{
+ for(SCCOL nCol = nCol1; nCol <= nCol2 && nCol < aCol.size(); ++nCol )
+ if( aCol[nCol].HasAttrib( nRow1, nRow2, nMask ))
+ return true;
+ if( nCol2 >= aCol.size())
+ return aDefaultColData.HasAttrib( nRow1, nRow2, nMask );
+ return false;
+}
+
+bool ScTable::HasAttrib( SCCOL nCol, SCROW nRow, HasAttrFlags nMask, SCROW* nStartRow, SCROW* nEndRow ) const
+{
+ return ColumnData(nCol).HasAttrib( nRow, nMask, nStartRow, nEndRow );
+}
+
+bool ScTable::HasAttribSelection( const ScMarkData& rMark, HasAttrFlags nMask ) const
+{
+ std::vector<sc::ColRowSpan> aSpans = rMark.GetMarkedColSpans();
+
+ for (const sc::ColRowSpan & aSpan : aSpans)
+ {
+ for (SCCOLROW j = aSpan.mnStart; j <= aSpan.mnEnd; ++j)
+ {
+ if (aCol[j].HasAttribSelection(rMark, nMask))
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ScTable::ExtendMerge( SCCOL nStartCol, SCROW nStartRow,
+ SCCOL& rEndCol, SCROW& rEndRow,
+ bool bRefresh )
+{
+ if (!(ValidCol(nStartCol) && ValidCol(rEndCol)))
+ {
+ OSL_FAIL("ScTable::ExtendMerge: invalid column number");
+ return false;
+ }
+ if( rEndCol >= aCol.size())
+ assert( !aDefaultColData.GetAttr( nStartRow, ATTR_MERGE ).IsMerged());
+ bool bFound = false;
+ SCCOL nOldEndX = ClampToAllocatedColumns(rEndCol);
+ SCROW nOldEndY = rEndRow;
+ for (SCCOL i=nStartCol; i<=nOldEndX; i++)
+ bFound |= aCol[i].ExtendMerge( i, nStartRow, nOldEndY, rEndCol, rEndRow, bRefresh );
+ return bFound;
+}
+
+void ScTable::SetMergedCells( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
+{
+ ScMergeAttr aAttr(nCol2-nCol1+1, nRow2-nRow1+1);
+ ApplyAttr(nCol1, nRow1, aAttr);
+
+ if (nCol1 < nCol2)
+ ApplyFlags(nCol1+1, nRow1, nCol2, nRow2, ScMF::Hor);
+
+ if (nRow1 < nRow2)
+ ApplyFlags(nCol1, nRow1+1, nCol1, nRow2, ScMF::Ver);
+
+ if (nCol1 < nCol2 && nRow1 < nRow2)
+ ApplyFlags(nCol1+1, nRow1+1, nCol2, nRow2, ScMF::Hor | ScMF::Ver);
+}
+
+bool ScTable::IsBlockEmpty( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) const
+{
+ if (!(ValidCol(nCol1) && ValidCol(nCol2)))
+ {
+ OSL_FAIL("ScTable::IsBlockEmpty: invalid column number");
+ return false;
+ }
+ nCol2 = ClampToAllocatedColumns(nCol2);
+ bool bEmpty = true;
+ for (SCCOL i=nCol1; i<=nCol2 && bEmpty; i++)
+ {
+ bEmpty = aCol[i].IsEmptyData( nRow1, nRow2 );
+ if (bEmpty)
+ {
+ bEmpty = aCol[i].IsSparklinesEmptyBlock(nRow1, nRow2);
+ }
+ if (bEmpty)
+ {
+ bEmpty = aCol[i].IsNotesEmptyBlock(nRow1, nRow2);
+ }
+ }
+ return bEmpty;
+}
+
+SCSIZE ScTable::FillMaxRot( RowInfo* pRowInfo, SCSIZE nArrCount, SCCOL nX1, SCCOL nX2,
+ SCCOL nCol, SCROW nAttrRow1, SCROW nAttrRow2, SCSIZE nArrY,
+ const ScPatternAttr* pPattern, const SfxItemSet* pCondSet )
+{
+ // Return value = new nArrY
+
+ ScRotateDir nRotDir = pPattern->GetRotateDir( pCondSet );
+ if ( nRotDir != ScRotateDir::NONE )
+ {
+ bool bHit = true;
+ if ( nCol+1 < nX1 ) // column to the left
+ bHit = ( nRotDir != ScRotateDir::Left );
+ else if ( nCol > nX2+1 ) // column to the right
+ bHit = ( nRotDir != ScRotateDir::Right ); // ScRotateDir::Standard may now also be extended to the left
+
+ if ( bHit )
+ {
+ double nFactor = 0.0;
+ if ( nCol > nX2+1 )
+ {
+ Degree100 nRotVal = pPattern->
+ GetItem( ATTR_ROTATE_VALUE, pCondSet ).GetValue();
+ double nRealOrient = toRadians(nRotVal);
+ double nCos = cos( nRealOrient );
+ double nSin = sin( nRealOrient );
+ //TODO: limit !!!
+ //TODO: additional factor for varying PPT X/Y !!!
+
+ // for ScRotateDir::Left this gives a negative value,
+ // if the mode is considered
+ nFactor = -fabs( nCos / nSin );
+ }
+
+ for ( SCROW nRow = nAttrRow1; nRow <= nAttrRow2; nRow++ )
+ {
+ if (!RowHidden(nRow))
+ {
+ bool bHitOne = true;
+ if ( nCol > nX2+1 )
+ {
+ // Does the rotated cell extend into the visible range?
+
+ SCCOL nTouchedCol = nCol;
+ tools::Long nWidth = static_cast<tools::Long>(mpRowHeights->getValue(nRow) * nFactor);
+ OSL_ENSURE(nWidth <= 0, "Wrong direction");
+ while ( nWidth < 0 && nTouchedCol > 0 )
+ {
+ --nTouchedCol;
+ nWidth += GetColWidth( nTouchedCol );
+ }
+ if ( nTouchedCol > nX2 )
+ bHitOne = false;
+ }
+
+ if (bHitOne)
+ {
+ while ( nArrY<nArrCount && pRowInfo[nArrY].nRowNo < nRow )
+ ++nArrY;
+ if ( nArrY<nArrCount && pRowInfo[nArrY].nRowNo == nRow )
+ pRowInfo[nArrY].nRotMaxCol = nCol;
+ }
+ }
+ }
+ }
+ }
+
+ return nArrY;
+}
+
+void ScTable::FindMaxRotCol( RowInfo* pRowInfo, SCSIZE nArrCount, SCCOL nX1, SCCOL nX2 )
+{
+ if ( !mpColWidth || !mpRowHeights || !mpColFlags || !pRowFlags )
+ {
+ OSL_FAIL( "Row/column info missing" );
+ return;
+ }
+
+ // nRotMaxCol is initialized to SC_ROTMAX_NONE, nRowNo is already set
+
+ SCROW nY1 = pRowInfo[0].nRowNo;
+ SCROW nY2 = pRowInfo[nArrCount-1].nRowNo;
+
+ for (SCCOL nCol : GetColumnsRange(0, rDocument.MaxCol()))
+ {
+ if (!ColHidden(nCol))
+ {
+ SCSIZE nArrY = 0;
+ ScDocAttrIterator aIter( rDocument, nTab, nCol, nY1, nCol, nY2 );
+ SCCOL nAttrCol;
+ SCROW nAttrRow1, nAttrRow2;
+ const ScPatternAttr* pPattern = aIter.GetNext( nAttrCol, nAttrRow1, nAttrRow2 );
+ while ( pPattern )
+ {
+ if ( const ScCondFormatItem* pCondItem = pPattern->GetItemSet().GetItemIfSet( ATTR_CONDITIONAL ) )
+ {
+ // Run through all formats, so that each cell does not have to be
+ // handled individually
+
+ const ScCondFormatIndexes& rCondFormatData = pCondItem->GetCondFormatData();
+ ScStyleSheetPool* pStylePool = rDocument.GetStyleSheetPool();
+ if (mpCondFormatList && pStylePool && !rCondFormatData.empty())
+ {
+ for(const auto& rItem : rCondFormatData)
+ {
+ const ScConditionalFormat* pFormat = mpCondFormatList->GetFormat(rItem);
+ if ( pFormat )
+ {
+ size_t nEntryCount = pFormat->size();
+ for (size_t nEntry=0; nEntry<nEntryCount; nEntry++)
+ {
+ const ScFormatEntry* pEntry = pFormat->GetEntry(nEntry);
+ if(pEntry->GetType() != ScFormatEntry::Type::Condition &&
+ pEntry->GetType() != ScFormatEntry::Type::ExtCondition)
+ continue;
+
+ OUString aStyleName = static_cast<const ScCondFormatEntry*>(pEntry)->GetStyle();
+ if (!aStyleName.isEmpty())
+ {
+ SfxStyleSheetBase* pStyleSheet =
+ pStylePool->Find( aStyleName, SfxStyleFamily::Para );
+ if ( pStyleSheet )
+ {
+ FillMaxRot( pRowInfo, nArrCount, nX1, nX2,
+ nCol, nAttrRow1, nAttrRow2,
+ nArrY, pPattern, &pStyleSheet->GetItemSet() );
+ // not changing nArrY
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ nArrY = FillMaxRot( pRowInfo, nArrCount, nX1, nX2,
+ nCol, nAttrRow1, nAttrRow2,
+ nArrY, pPattern, nullptr );
+
+ pPattern = aIter.GetNext( nAttrCol, nAttrRow1, nAttrRow2 );
+ }
+ }
+ }
+}
+
+bool ScTable::HasBlockMatrixFragment( const SCCOL nCol1, SCROW nRow1, const SCCOL nCol2, SCROW nRow2,
+ bool bNoMatrixAtAll ) const
+{
+ using namespace sc;
+
+ if ( !IsColValid( nCol1 ) )
+ return false;
+
+ const SCCOL nMaxCol2 = std::min<SCCOL>( nCol2, aCol.size() - 1 );
+
+ MatrixEdge nEdges = MatrixEdge::Nothing;
+
+ if ( nCol1 == nMaxCol2 )
+ { // left and right column
+ const MatrixEdge n = MatrixEdge::Left | MatrixEdge::Right;
+ nEdges = aCol[nCol1].GetBlockMatrixEdges( nRow1, nRow2, n, bNoMatrixAtAll );
+ if ((nEdges != MatrixEdge::Nothing) && (((nEdges & n)!=n) || (nEdges & (MatrixEdge::Inside|MatrixEdge::Open))))
+ return true; // left or right edge is missing or open
+ }
+ else
+ { // left column
+ nEdges = aCol[nCol1].GetBlockMatrixEdges(nRow1, nRow2, MatrixEdge::Left, bNoMatrixAtAll);
+ if ((nEdges != MatrixEdge::Nothing) && ((!(nEdges & MatrixEdge::Left)) || (nEdges & (MatrixEdge::Inside|MatrixEdge::Open))))
+ return true; // left edge missing or open
+ // right column
+ nEdges = aCol[nMaxCol2].GetBlockMatrixEdges(nRow1, nRow2, MatrixEdge::Right, bNoMatrixAtAll);
+ if ((nEdges != MatrixEdge::Nothing) && ((!(nEdges & MatrixEdge::Right)) || (nEdges & (MatrixEdge::Inside|MatrixEdge::Open))))
+ return true; // right edge is missing or open
+ }
+
+ if (bNoMatrixAtAll)
+ {
+ for (SCCOL i=nCol1; i<=nMaxCol2; i++)
+ {
+ nEdges = aCol[i].GetBlockMatrixEdges( nRow1, nRow2, MatrixEdge::Nothing, bNoMatrixAtAll);
+ if (nEdges != MatrixEdge::Nothing
+ && (nEdges != (MatrixEdge::Top | MatrixEdge::Left | MatrixEdge::Bottom | MatrixEdge::Right)))
+ return true;
+ }
+ }
+ else if ( nRow1 == nRow2 )
+ { // Row on top and on bottom
+ bool bOpen = false;
+ const MatrixEdge n = MatrixEdge::Bottom | MatrixEdge::Top;
+ for ( SCCOL i=nCol1; i<=nMaxCol2; i++)
+ {
+ nEdges = aCol[i].GetBlockMatrixEdges( nRow1, nRow1, n, bNoMatrixAtAll );
+ if (nEdges != MatrixEdge::Nothing)
+ {
+ if ( (nEdges & n) != n )
+ return true; // Top or bottom edge missing
+ if (nEdges & MatrixEdge::Left)
+ bOpen = true; // left edge open, continue
+ else if ( !bOpen )
+ return true; // Something exist that has not been opened
+ if (nEdges & MatrixEdge::Right)
+ bOpen = false; // Close right edge
+ }
+ }
+ if ( bOpen )
+ return true;
+ }
+ else
+ {
+ int j;
+ MatrixEdge n;
+ SCROW nR;
+ // first top row, then bottom row
+ for ( j=0, n = MatrixEdge::Top, nR=nRow1; j<2;
+ j++, n = MatrixEdge::Bottom, nR=nRow2)
+ {
+ bool bOpen = false;
+ for ( SCCOL i=nCol1; i<=nMaxCol2; i++)
+ {
+ nEdges = aCol[i].GetBlockMatrixEdges( nR, nR, n, bNoMatrixAtAll );
+ if ( nEdges != MatrixEdge::Nothing)
+ {
+ // in top row no top edge respectively
+ // in bottom row no bottom edge
+ if ( (nEdges & n) != n )
+ return true;
+ if (nEdges & MatrixEdge::Left)
+ bOpen = true; // open left edge, continue
+ else if ( !bOpen )
+ return true; // Something exist that has not been opened
+ if (nEdges & MatrixEdge::Right)
+ bOpen = false; // Close right edge
+ }
+ }
+ if ( bOpen )
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ScTable::HasSelectionMatrixFragment( const ScMarkData& rMark ) const
+{
+ std::vector<sc::ColRowSpan> aSpans = rMark.GetMarkedColSpans();
+ ScRangeList rangeList = rMark.GetMarkedRanges();
+
+ for (const sc::ColRowSpan & aSpan : aSpans)
+ {
+ SCCOL nEndCol = ClampToAllocatedColumns(aSpan.mnEnd);
+ for ( SCCOLROW j=aSpan.mnStart; j<=nEndCol; j++ )
+ {
+ if ( aCol[j].HasSelectionMatrixFragment(rMark, rangeList) )
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ScTable::IsBlockEditable( SCCOL nCol1, SCROW nRow1, SCCOL nCol2,
+ SCROW nRow2, bool* pOnlyNotBecauseOfMatrix /* = NULL */,
+ bool bNoMatrixAtAll ) const
+{
+ if ( !ValidColRow( nCol2, nRow2 ) )
+ {
+ SAL_WARN("sc", "IsBlockEditable: invalid column or row " << nCol2 << " " << nRow2);
+ if (pOnlyNotBecauseOfMatrix)
+ *pOnlyNotBecauseOfMatrix = false;
+ return false;
+ }
+
+ bool bIsEditable = true;
+ if ( nLockCount )
+ bIsEditable = false;
+ else if ( IsProtected() && !rDocument.IsScenario(nTab) )
+ {
+ bIsEditable = !HasAttrib( nCol1, nRow1, nCol2, nRow2, HasAttrFlags::Protected );
+ if (!bIsEditable)
+ {
+ // An enhanced protection permission may override the attribute.
+ if (pTabProtection)
+ bIsEditable = pTabProtection->isBlockEditable( ScRange( nCol1, nRow1, nTab, nCol2, nRow2, nTab));
+ }
+ if (bIsEditable)
+ {
+ // If Sheet is protected and cells are not protected then
+ // check the active scenario protect flag if this range is
+ // on the active scenario range. Note the 'copy back' must also
+ // be set to apply protection.
+ sal_uInt16 nScenTab = nTab+1;
+ while(rDocument.IsScenario(nScenTab))
+ {
+ ScRange aEditRange(nCol1, nRow1, nScenTab, nCol2, nRow2, nScenTab);
+ if(rDocument.IsActiveScenario(nScenTab) && rDocument.HasScenarioRange(nScenTab, aEditRange))
+ {
+ ScScenarioFlags nFlags;
+ rDocument.GetScenarioFlags(nScenTab,nFlags);
+ bIsEditable = !((nFlags & ScScenarioFlags::Protected) && (nFlags & ScScenarioFlags::TwoWay));
+ break;
+ }
+ nScenTab++;
+ }
+ }
+ }
+ else if (rDocument.IsScenario(nTab))
+ {
+ // Determine if the preceding sheet is protected
+ SCTAB nActualTab = nTab;
+ do
+ {
+ nActualTab--;
+ }
+ while(rDocument.IsScenario(nActualTab));
+
+ if(rDocument.IsTabProtected(nActualTab))
+ {
+ ScRange aEditRange(nCol1, nRow1, nTab, nCol2, nRow2, nTab);
+ if(rDocument.HasScenarioRange(nTab, aEditRange))
+ {
+ ScScenarioFlags nFlags;
+ rDocument.GetScenarioFlags(nTab,nFlags);
+ bIsEditable = !(nFlags & ScScenarioFlags::Protected);
+ }
+ }
+ }
+ if ( bIsEditable )
+ {
+ if (HasBlockMatrixFragment( nCol1, nRow1, nCol2, nRow2, bNoMatrixAtAll))
+ {
+ bIsEditable = false;
+ if ( pOnlyNotBecauseOfMatrix )
+ *pOnlyNotBecauseOfMatrix = true;
+ }
+ else if ( pOnlyNotBecauseOfMatrix )
+ *pOnlyNotBecauseOfMatrix = false;
+ }
+ else if ( pOnlyNotBecauseOfMatrix )
+ *pOnlyNotBecauseOfMatrix = false;
+ return bIsEditable;
+}
+
+bool ScTable::IsSelectionEditable( const ScMarkData& rMark,
+ bool* pOnlyNotBecauseOfMatrix /* = NULL */ ) const
+{
+ bool bIsEditable = true;
+ if ( nLockCount )
+ bIsEditable = false;
+ else if ( IsProtected() && !rDocument.IsScenario(nTab) )
+ {
+ ScRangeList aRanges;
+ rMark.FillRangeListWithMarks( &aRanges, false );
+ bIsEditable = !HasAttribSelection( rMark, HasAttrFlags::Protected );
+ if (!bIsEditable)
+ {
+ // An enhanced protection permission may override the attribute.
+ if (pTabProtection)
+ bIsEditable = pTabProtection->isSelectionEditable( aRanges);
+ }
+ if (bIsEditable)
+ {
+ // If Sheet is protected and cells are not protected then
+ // check the active scenario protect flag if this area is
+ // in the active scenario range.
+ SCTAB nScenTab = nTab+1;
+ while(rDocument.IsScenario(nScenTab) && bIsEditable)
+ {
+ if(rDocument.IsActiveScenario(nScenTab))
+ {
+ for (size_t i=0, nRange = aRanges.size(); (i < nRange) && bIsEditable; i++ )
+ {
+ const ScRange & rRange = aRanges[ i ];
+ if(rDocument.HasScenarioRange(nScenTab, rRange))
+ {
+ ScScenarioFlags nFlags;
+ rDocument.GetScenarioFlags(nScenTab,nFlags);
+ bIsEditable = !((nFlags & ScScenarioFlags::Protected) && (nFlags & ScScenarioFlags::TwoWay));
+ }
+ }
+ }
+ nScenTab++;
+ }
+ }
+ }
+ else if (rDocument.IsScenario(nTab))
+ {
+ // Determine if the preceding sheet is protected
+ SCTAB nActualTab = nTab;
+ do
+ {
+ nActualTab--;
+ }
+ while(rDocument.IsScenario(nActualTab));
+
+ if(rDocument.IsTabProtected(nActualTab))
+ {
+ ScRangeList aRanges;
+ rMark.FillRangeListWithMarks( &aRanges, false );
+ for (size_t i = 0, nRange = aRanges.size(); (i < nRange) && bIsEditable; i++)
+ {
+ const ScRange & rRange = aRanges[ i ];
+ if(rDocument.HasScenarioRange(nTab, rRange))
+ {
+ ScScenarioFlags nFlags;
+ rDocument.GetScenarioFlags(nTab,nFlags);
+ bIsEditable = !(nFlags & ScScenarioFlags::Protected);
+ }
+ }
+ }
+ }
+ if ( bIsEditable )
+ {
+ if ( HasSelectionMatrixFragment( rMark ) )
+ {
+ bIsEditable = false;
+ if ( pOnlyNotBecauseOfMatrix )
+ *pOnlyNotBecauseOfMatrix = true;
+ }
+ else if ( pOnlyNotBecauseOfMatrix )
+ *pOnlyNotBecauseOfMatrix = false;
+ }
+ else if ( pOnlyNotBecauseOfMatrix )
+ *pOnlyNotBecauseOfMatrix = false;
+ return bIsEditable;
+}
+
+void ScTable::LockTable()
+{
+ ++nLockCount;
+}
+
+void ScTable::UnlockTable()
+{
+ if (nLockCount)
+ --nLockCount;
+ else
+ {
+ OSL_FAIL("UnlockTable without LockTable");
+ }
+}
+
+void ScTable::MergeSelectionPattern( ScMergePatternState& rState, const ScMarkData& rMark, bool bDeep ) const
+{
+ std::vector<sc::ColRowSpan> aSpans = rMark.GetMarkedColSpans();
+
+ for (const sc::ColRowSpan & rSpan : aSpans)
+ {
+ SCCOL maxCol = ClampToAllocatedColumns(rSpan.mnEnd);
+ for (SCCOL i = rSpan.mnStart; i <= maxCol; ++i)
+ {
+ aCol[i].MergeSelectionPattern( rState, rMark, bDeep );
+ }
+ }
+}
+
+void ScTable::MergePatternArea( ScMergePatternState& rState, SCCOL nCol1, SCROW nRow1,
+ SCCOL nCol2, SCROW nRow2, bool bDeep ) const
+{
+ const SCCOL nEndCol = ClampToAllocatedColumns(nCol2);
+ for (SCCOL i=nCol1; i<=nEndCol; i++)
+ aCol[i].MergePatternArea( rState, nRow1, nRow2, bDeep );
+ if (nEndCol != nCol2)
+ aDefaultColData.MergePatternArea( rState, nRow1, nRow2, bDeep );
+}
+
+void ScTable::MergeBlockFrame( SvxBoxItem* pLineOuter, SvxBoxInfoItem* pLineInner, ScLineFlags& rFlags,
+ SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow ) const
+{
+ if (ValidColRow(nStartCol, nStartRow) && ValidColRow(nEndCol, nEndRow))
+ {
+ PutInOrder(nStartCol, nEndCol);
+ PutInOrder(nStartRow, nEndRow);
+ nEndCol = ClampToAllocatedColumns(nEndCol);
+ for (SCCOL i=nStartCol; i<=nEndCol; i++)
+ aCol[i].MergeBlockFrame( pLineOuter, pLineInner, rFlags,
+ nStartRow, nEndRow, (i==nStartCol), nEndCol-i );
+ }
+}
+
+void ScTable::ApplyBlockFrame(const SvxBoxItem& rLineOuter, const SvxBoxInfoItem* pLineInner,
+ SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow)
+{
+ if (ValidColRow(nStartCol, nStartRow) && ValidColRow(nEndCol, nEndRow))
+ {
+ PutInOrder(nStartCol, nEndCol);
+ PutInOrder(nStartRow, nEndRow);
+ CreateColumnIfNotExists(nEndCol);
+ for (SCCOL i=nStartCol; i<=nEndCol; i++)
+ aCol[i].ApplyBlockFrame(rLineOuter, pLineInner,
+ nStartRow, nEndRow, (i==nStartCol), nEndCol-i);
+ }
+}
+
+void ScTable::ApplyPattern( SCCOL nCol, SCROW nRow, const ScPatternAttr& rAttr )
+{
+ if (ValidColRow(nCol,nRow))
+ CreateColumnIfNotExists(nCol).ApplyPattern( nRow, rAttr );
+}
+
+void ScTable::ApplyPatternArea( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow,
+ const ScPatternAttr& rAttr, ScEditDataArray* pDataArray,
+ bool* const pIsChanged )
+{
+ if (!ValidColRow(nStartCol, nStartRow) || !ValidColRow(nEndCol, nEndRow))
+ return;
+ PutInOrder(nStartCol, nEndCol);
+ PutInOrder(nStartRow, nEndRow);
+ SCCOL maxCol = nEndCol;
+ if( nEndCol == GetDoc().MaxCol())
+ {
+ // For the same unallocated columns until the end we can change just the default.
+ maxCol = std::max( nStartCol, aCol.size()) - 1;
+ if( maxCol >= 0 )
+ CreateColumnIfNotExists(maxCol); // Allocate needed different columns before changing the default.
+ aDefaultColData.ApplyPatternArea(nStartRow, nEndRow, rAttr, pDataArray, pIsChanged);
+ }
+ for (SCCOL i = nStartCol; i <= maxCol; i++)
+ CreateColumnIfNotExists(i).ApplyPatternArea(nStartRow, nEndRow, rAttr, pDataArray, pIsChanged);
+}
+
+namespace
+{
+ std::vector<ScAttrEntry> duplicateScAttrEntries(ScDocument& rDocument, const std::vector<ScAttrEntry>& rOrigData)
+ {
+ std::vector<ScAttrEntry> aData(rOrigData);
+ for (size_t nIdx = 0; nIdx < aData.size(); ++nIdx)
+ {
+ ScPatternAttr aNewPattern(*aData[nIdx].pPattern);
+ aData[nIdx].pPattern = &rDocument.GetPool()->Put(aNewPattern);
+ }
+ return aData;
+ }
+}
+
+void ScTable::SetAttrEntries( SCCOL nStartCol, SCCOL nEndCol, std::vector<ScAttrEntry> && vNewData)
+{
+ if (!ValidCol(nStartCol) || !ValidCol(nEndCol))
+ return;
+ if ( nEndCol == rDocument.MaxCol() )
+ {
+ if ( nStartCol < aCol.size() )
+ {
+ // If we would like set all columns to same attrs, then change only attrs for not existing columns
+ nEndCol = aCol.size() - 1;
+ for (SCCOL i = nStartCol; i <= nEndCol; i++)
+ aCol[i].SetAttrEntries(duplicateScAttrEntries(rDocument, vNewData));
+ aDefaultColData.SetAttrEntries(std::move(vNewData));
+ }
+ else
+ {
+ CreateColumnIfNotExists( nStartCol - 1 );
+ aDefaultColData.SetAttrEntries(std::move(vNewData));
+ }
+ }
+ else
+ {
+ CreateColumnIfNotExists( nEndCol );
+ for (SCCOL i = nStartCol; i < nEndCol; i++) // all but last need a copy
+ aCol[i].SetAttrEntries(duplicateScAttrEntries(rDocument, vNewData));
+ aCol[nEndCol].SetAttrEntries( std::move(vNewData));
+ }
+}
+
+
+
+void ScTable::ApplyPatternIfNumberformatIncompatible( const ScRange& rRange,
+ const ScPatternAttr& rPattern, SvNumFormatType nNewType )
+{
+ SCCOL nEndCol = rRange.aEnd.Col();
+ for ( SCCOL nCol = rRange.aStart.Col(); nCol <= nEndCol; nCol++ )
+ {
+ aCol[nCol].ApplyPatternIfNumberformatIncompatible( rRange, rPattern, nNewType );
+ }
+}
+
+void ScTable::AddCondFormatData( const ScRangeList& rRangeList, sal_uInt32 nIndex )
+{
+ size_t n = rRangeList.size();
+ for(size_t i = 0; i < n; ++i)
+ {
+ const ScRange & rRange = rRangeList[i];
+ SCCOL nColStart = rRange.aStart.Col();
+ SCCOL nColEnd = rRange.aEnd.Col();
+ SCROW nRowStart = rRange.aStart.Row();
+ SCROW nRowEnd = rRange.aEnd.Row();
+ for(SCCOL nCol = nColStart; nCol <= nColEnd; ++nCol)
+ {
+ CreateColumnIfNotExists(nCol).AddCondFormat(nRowStart, nRowEnd, nIndex);
+ }
+ }
+}
+
+void ScTable::RemoveCondFormatData( const ScRangeList& rRangeList, sal_uInt32 nIndex )
+{
+ size_t n = rRangeList.size();
+ for(size_t i = 0; i < n; ++i)
+ {
+ const ScRange & rRange = rRangeList[i];
+ SCCOL nColStart = rRange.aStart.Col();
+ SCCOL nColEnd = ClampToAllocatedColumns(rRange.aEnd.Col());
+ SCROW nRowStart = rRange.aStart.Row();
+ SCROW nRowEnd = rRange.aEnd.Row();
+ for(SCCOL nCol = nColStart; nCol <= nColEnd; ++nCol)
+ {
+ aCol[nCol].RemoveCondFormat(nRowStart, nRowEnd, nIndex);
+ }
+ }
+}
+
+void ScTable::SetPatternAreaCondFormat( SCCOL nCol, SCROW nStartRow, SCROW nEndRow,
+ const ScPatternAttr& rAttr, const ScCondFormatIndexes& rCondFormatIndexes )
+{
+ CreateColumnIfNotExists(nCol).SetPatternArea( nStartRow, nEndRow, rAttr);
+
+ for (const auto& rIndex : rCondFormatIndexes)
+ {
+ ScConditionalFormat* pCondFormat = mpCondFormatList->GetFormat(rIndex);
+ if (pCondFormat)
+ {
+ ScRangeList aRange = pCondFormat->GetRange();
+ aRange.Join( ScRange( nCol, nStartRow, nTab, nCol, nEndRow, nTab));
+ pCondFormat->SetRange(aRange);
+ }
+ }
+}
+
+void ScTable::ApplyStyle( SCCOL nCol, SCROW nRow, const ScStyleSheet* rStyle )
+{
+ if (ValidColRow(nCol,nRow))
+ // If column not exists then we need to create it
+ CreateColumnIfNotExists( nCol ).ApplyStyle( nRow, rStyle );
+}
+
+void ScTable::ApplyStyleArea( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, const ScStyleSheet& rStyle )
+{
+ if (!(ValidColRow(nStartCol, nStartRow) && ValidColRow(nEndCol, nEndRow)))
+ return;
+
+ PutInOrder(nStartCol, nEndCol);
+ PutInOrder(nStartRow, nEndRow);
+ if ( nEndCol == rDocument.MaxCol() )
+ {
+ if ( nStartCol < aCol.size() )
+ {
+ // If we would like set all columns to specific style, then change only default style for not existing columns
+ nEndCol = aCol.size() - 1;
+ for (SCCOL i = nStartCol; i <= nEndCol; i++)
+ aCol[i].ApplyStyleArea(nStartRow, nEndRow, rStyle);
+ aDefaultColData.ApplyStyleArea(nStartRow, nEndRow, rStyle );
+ }
+ else
+ {
+ CreateColumnIfNotExists( nStartCol - 1 );
+ aDefaultColData.ApplyStyleArea(nStartRow, nEndRow, rStyle );
+ }
+ }
+ else
+ {
+ CreateColumnIfNotExists( nEndCol );
+ for (SCCOL i = nStartCol; i <= nEndCol; i++)
+ aCol[i].ApplyStyleArea(nStartRow, nEndRow, rStyle);
+ }
+}
+
+void ScTable::ApplySelectionStyle(const ScStyleSheet& rStyle, const ScMarkData& rMark)
+{
+ for (SCCOL i=0; i < aCol.size(); i++)
+ aCol[i].ApplySelectionStyle( rStyle, rMark );
+}
+
+void ScTable::ApplySelectionLineStyle( const ScMarkData& rMark,
+ const ::editeng::SvxBorderLine* pLine, bool bColorOnly )
+{
+ if ( bColorOnly && !pLine )
+ return;
+
+ for (SCCOL i=0; i < aCol.size(); i++)
+ aCol[i].ApplySelectionLineStyle( rMark, pLine, bColorOnly );
+}
+
+const ScStyleSheet* ScTable::GetStyle( SCCOL nCol, SCROW nRow ) const
+{
+ if ( !ValidColRow( nCol, nRow ) )
+ return nullptr;
+ return ColumnData(nCol).GetStyle( nRow );
+}
+
+const ScStyleSheet* ScTable::GetSelectionStyle( const ScMarkData& rMark, bool& rFound ) const
+{
+ rFound = false;
+
+ bool bEqual = true;
+ bool bColFound;
+
+ const ScStyleSheet* pStyle = nullptr;
+ const ScStyleSheet* pNewStyle;
+
+ for (SCCOL i=0; i < aCol.size() && bEqual; i++)
+ if (rMark.HasMultiMarks(i))
+ {
+ pNewStyle = aCol[i].GetSelectionStyle( rMark, bColFound );
+ if (bColFound)
+ {
+ rFound = true;
+ if ( !pNewStyle || ( pStyle && pNewStyle != pStyle ) )
+ bEqual = false;
+ pStyle = pNewStyle;
+ }
+ }
+
+ return bEqual ? pStyle : nullptr;
+}
+
+const ScStyleSheet* ScTable::GetAreaStyle( bool& rFound, SCCOL nCol1, SCROW nRow1,
+ SCCOL nCol2, SCROW nRow2 ) const
+{
+ rFound = false;
+
+ bool bEqual = true;
+ bool bColFound;
+
+ const ScStyleSheet* pStyle = nullptr;
+ const ScStyleSheet* pNewStyle;
+ nCol2 = ClampToAllocatedColumns(nCol2);
+ for (SCCOL i=nCol1; i<=nCol2 && bEqual; i++)
+ {
+ pNewStyle = aCol[i].GetAreaStyle(bColFound, nRow1, nRow2);
+ if (bColFound)
+ {
+ rFound = true;
+ if ( !pNewStyle || ( pStyle && pNewStyle != pStyle ) )
+ bEqual = false;
+ pStyle = pNewStyle;
+ }
+ }
+
+ return bEqual ? pStyle : nullptr;
+}
+
+bool ScTable::IsStyleSheetUsed( const ScStyleSheet& rStyle ) const
+{
+ bool bIsUsed = false;
+
+ for ( SCCOL i=0; i < aCol.size(); i++ )
+ {
+ if ( aCol[i].IsStyleSheetUsed( rStyle ) )
+ {
+ bIsUsed = true;
+ }
+ }
+
+ return bIsUsed;
+}
+
+void ScTable::StyleSheetChanged( const SfxStyleSheetBase* pStyleSheet, bool bRemoved,
+ OutputDevice* pDev,
+ double nPPTX, double nPPTY,
+ const Fraction& rZoomX, const Fraction& rZoomY )
+{
+ ScFlatBoolRowSegments aUsedRows(rDocument.MaxRow());
+ for (SCCOL i = 0; i < aCol.size(); ++i)
+ aCol[i].FindStyleSheet(pStyleSheet, aUsedRows, bRemoved);
+
+ sc::RowHeightContext aCxt(rDocument.MaxRow(), nPPTX, nPPTY, rZoomX, rZoomY, pDev);
+ SCROW nRow = 0;
+ while (nRow <= rDocument.MaxRow())
+ {
+ ScFlatBoolRowSegments::RangeData aData;
+ if (!aUsedRows.getRangeData(nRow, aData))
+ // search failed!
+ return;
+
+ SCROW nEndRow = aData.mnRow2;
+ if (aData.mbValue)
+ SetOptimalHeight(aCxt, nRow, nEndRow, true);
+
+ nRow = nEndRow + 1;
+ }
+}
+
+bool ScTable::ApplyFlags( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow,
+ ScMF nFlags )
+{
+ bool bChanged = false;
+ if (ValidColRow(nStartCol, nStartRow) && ValidColRow(nEndCol, nEndRow))
+ for (SCCOL i = nStartCol; i <= nEndCol; i++)
+ bChanged |= CreateColumnIfNotExists(i).ApplyFlags(nStartRow, nEndRow, nFlags);
+ return bChanged;
+}
+
+bool ScTable::RemoveFlags( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow,
+ ScMF nFlags )
+{
+ if (!ValidColRow(nStartCol, nStartRow) || !ValidColRow(nEndCol, nEndRow))
+ return false;
+ bool bChanged = false;
+ nEndCol = ClampToAllocatedColumns(nEndCol);
+ for (SCCOL i = nStartCol; i <= nEndCol; i++)
+ bChanged |= aCol[i].RemoveFlags(nStartRow, nEndRow, nFlags);
+ return bChanged;
+}
+
+void ScTable::SetPattern( const ScAddress& rPos, const ScPatternAttr& rAttr )
+{
+ if (ValidColRow(rPos.Col(),rPos.Row()))
+ CreateColumnIfNotExists(rPos.Col()).SetPattern(rPos.Row(), rAttr);
+}
+
+const ScPatternAttr* ScTable::SetPattern( SCCOL nCol, SCROW nRow, std::unique_ptr<ScPatternAttr> pAttr )
+{
+ if (ValidColRow(nCol,nRow))
+ return CreateColumnIfNotExists(nCol).SetPattern(nRow, std::move(pAttr));
+ return nullptr;
+}
+
+void ScTable::SetPattern( SCCOL nCol, SCROW nRow, const ScPatternAttr& rAttr )
+{
+ if (ValidColRow(nCol,nRow))
+ CreateColumnIfNotExists(nCol).SetPattern(nRow, rAttr);
+}
+
+void ScTable::ApplyAttr( SCCOL nCol, SCROW nRow, const SfxPoolItem& rAttr )
+{
+ if (ValidColRow(nCol,nRow))
+ CreateColumnIfNotExists(nCol).ApplyAttr( nRow, rAttr );
+}
+
+void ScTable::ApplySelectionCache( SfxItemPoolCache* pCache, const ScMarkData& rMark,
+ ScEditDataArray* pDataArray, bool* const pIsChanged )
+{
+ if(!rMark.GetTableSelect(nTab))
+ return;
+ SCCOL lastChangeCol;
+ if( rMark.GetArea().aEnd.Col() == GetDoc().MaxCol())
+ {
+ // For the same unallocated columns until the end we can change just the default.
+ lastChangeCol = rMark.GetStartOfEqualColumns( GetDoc().MaxCol(), aCol.size()) - 1;
+ if( lastChangeCol >= 0 )
+ CreateColumnIfNotExists(lastChangeCol); // Allocate needed different columns before changing the default.
+ aDefaultColData.ApplySelectionCache( pCache, rMark, pDataArray, pIsChanged, GetDoc().MaxCol());
+ }
+ else // need to allocate all columns affected
+ {
+ lastChangeCol = rMark.GetArea().aEnd.Col();
+ CreateColumnIfNotExists(lastChangeCol);
+ }
+
+ for (SCCOL i=0; i <= lastChangeCol; i++)
+ aCol[i].ApplySelectionCache( pCache, rMark, pDataArray, pIsChanged );
+}
+
+void ScTable::ChangeSelectionIndent( bool bIncrement, const ScMarkData& rMark )
+{
+ if(!rMark.GetTableSelect(nTab))
+ return;
+ SCCOL lastChangeCol;
+ if( rMark.GetArea().aEnd.Col() == GetDoc().MaxCol())
+ {
+ // For the same unallocated columns until the end we can change just the default.
+ lastChangeCol = rMark.GetStartOfEqualColumns( GetDoc().MaxCol(), aCol.size()) - 1;
+ if( lastChangeCol >= 0 )
+ CreateColumnIfNotExists(lastChangeCol); // Allocate needed different columns before changing the default.
+ aDefaultColData.ChangeSelectionIndent( bIncrement, rMark, GetDoc().MaxCol());
+ }
+ else
+ {
+ lastChangeCol = rMark.GetArea().aEnd.Col();
+ CreateColumnIfNotExists(lastChangeCol);
+ }
+
+ for (SCCOL i=0; i <= lastChangeCol; i++)
+ aCol[i].ChangeSelectionIndent( bIncrement, rMark );
+}
+
+void ScTable::ClearSelectionItems( const sal_uInt16* pWhich, const ScMarkData& rMark )
+{
+ if(!rMark.GetTableSelect(nTab))
+ return;
+ SCCOL lastChangeCol;
+ if( rMark.GetArea().aEnd.Col() == GetDoc().MaxCol())
+ {
+ // For the same unallocated columns until the end we can change just the default.
+ lastChangeCol = rMark.GetStartOfEqualColumns( GetDoc().MaxCol(), aCol.size()) - 1;
+ if( lastChangeCol >= 0 )
+ CreateColumnIfNotExists(lastChangeCol); // Allocate needed different columns before changing the default.
+ aDefaultColData.ClearSelectionItems( pWhich, rMark, GetDoc().MaxCol());
+ }
+ else
+ {
+ lastChangeCol = rMark.GetArea().aEnd.Col();
+ CreateColumnIfNotExists(lastChangeCol);
+ }
+
+ for (SCCOL i=0; i <= lastChangeCol; i++)
+ aCol[i].ClearSelectionItems( pWhich, rMark );
+}
+
+// Column widths / Row heights
+
+void ScTable::SetColWidth( SCCOL nCol, sal_uInt16 nNewWidth )
+{
+ if (ValidCol(nCol) && mpColWidth)
+ {
+ if (!nNewWidth)
+ {
+ nNewWidth = STD_COL_WIDTH;
+ }
+
+ if ( nNewWidth != mpColWidth->GetValue(nCol) )
+ {
+ mpColWidth->SetValue(nCol, nNewWidth);
+ InvalidatePageBreaks();
+ }
+ }
+ else
+ {
+ OSL_FAIL("Invalid column number or no widths");
+ }
+}
+
+void ScTable::SetColWidthOnly( SCCOL nCol, sal_uInt16 nNewWidth )
+{
+ if (!ValidCol(nCol) || !mpColWidth)
+ return;
+
+ if (!nNewWidth)
+ nNewWidth = STD_COL_WIDTH;
+
+ if (nNewWidth != mpColWidth->GetValue(nCol))
+ mpColWidth->SetValue(nCol, nNewWidth);
+}
+
+void ScTable::SetRowHeight( SCROW nRow, sal_uInt16 nNewHeight )
+{
+ if (ValidRow(nRow) && mpRowHeights)
+ {
+ if (!nNewHeight)
+ {
+ OSL_FAIL("SetRowHeight: Row height zero");
+ nNewHeight = ScGlobal::nStdRowHeight;
+ }
+
+ sal_uInt16 nOldHeight = mpRowHeights->getValue(nRow);
+ if ( nNewHeight != nOldHeight )
+ {
+ mpRowHeights->setValue(nRow, nRow, nNewHeight);
+ InvalidatePageBreaks();
+ }
+ }
+ else
+ {
+ OSL_FAIL("Invalid row number or no heights");
+ }
+}
+
+namespace {
+
+/**
+ * Check if the new pixel size is different from the old size between
+ * specified ranges.
+ */
+bool lcl_pixelSizeChanged(
+ ScFlatUInt16RowSegments& rRowHeights, SCROW nStartRow, SCROW nEndRow,
+ sal_uInt16 nNewHeight, double nPPTY, bool bApi)
+{
+ tools::Long nNewPix = static_cast<tools::Long>(nNewHeight * nPPTY);
+
+ ScFlatUInt16RowSegments::ForwardIterator aFwdIter(rRowHeights);
+ for (SCROW nRow = nStartRow; nRow <= nEndRow; ++nRow)
+ {
+ sal_uInt16 nHeight;
+ if (!aFwdIter.getValue(nRow, nHeight))
+ break;
+
+ if (nHeight != nNewHeight)
+ {
+ tools::Long nOldPix = static_cast<tools::Long>(nHeight * nPPTY);
+
+ // Heuristic: Don't bother when handling interactive input, if changing just one row and
+ // the height will shrink.
+ bool bChanged = (nNewPix != nOldPix) && (bApi || nEndRow - nStartRow > 0 || nNewPix > nOldPix);
+ if (bChanged)
+ return true;
+ }
+
+ // Skip ahead to the last position of the current range.
+ nRow = aFwdIter.getLastPos();
+ }
+ return false;
+}
+
+}
+
+bool ScTable::SetRowHeightRange( SCROW nStartRow, SCROW nEndRow, sal_uInt16 nNewHeight,
+ double nPPTY, bool bApi )
+{
+ bool bChanged = false;
+ if (ValidRow(nStartRow) && ValidRow(nEndRow) && mpRowHeights)
+ {
+ if (!nNewHeight)
+ {
+ OSL_FAIL("SetRowHeight: Row height zero");
+ nNewHeight = ScGlobal::nStdRowHeight;
+ }
+
+ bool bSingle = false; // true = process every row for its own
+ ScDrawLayer* pDrawLayer = rDocument.GetDrawLayer();
+ if (pDrawLayer)
+ if (pDrawLayer->HasObjectsInRows( nTab, nStartRow, nEndRow ))
+ bSingle = true;
+
+ if (bSingle)
+ {
+ ScFlatUInt16RowSegments::RangeData aData;
+ if (mpRowHeights->getRangeData(nStartRow, aData) &&
+ nNewHeight == aData.mnValue && nEndRow <= aData.mnRow2)
+ {
+ bSingle = false; // no difference in this range
+ }
+ }
+
+ // No idea why 20 is used here
+ if (!bSingle || nEndRow - nStartRow < 20)
+ {
+ bChanged = lcl_pixelSizeChanged(*mpRowHeights, nStartRow, nEndRow, nNewHeight, nPPTY, bApi);
+ if (bChanged)
+ mpRowHeights->setValue(nStartRow, nEndRow, nNewHeight);
+ }
+ else
+ {
+ SCROW nMid = (nStartRow + nEndRow) / 2;
+ // No idea why nPPTY is ignored in these recursive calls and instead 1.0 is used
+ if (SetRowHeightRange(nStartRow, nMid, nNewHeight, 1.0, bApi))
+ bChanged = true;
+ if (SetRowHeightRange(nMid + 1, nEndRow, nNewHeight, 1.0, bApi))
+ bChanged = true;
+ }
+
+ if (bChanged)
+ InvalidatePageBreaks();
+ }
+ else
+ {
+ OSL_FAIL("Invalid row number or no heights");
+ }
+
+ return bChanged;
+}
+
+void ScTable::SetRowHeightOnly( SCROW nStartRow, SCROW nEndRow, sal_uInt16 nNewHeight )
+{
+ if (!ValidRow(nStartRow) || !ValidRow(nEndRow) || !mpRowHeights)
+ return;
+
+ if (!nNewHeight)
+ nNewHeight = ScGlobal::nStdRowHeight;
+
+ mpRowHeights->setValue(nStartRow, nEndRow, nNewHeight);
+}
+
+void ScTable::SetManualHeight( SCROW nStartRow, SCROW nEndRow, bool bManual )
+{
+ if (ValidRow(nStartRow) && ValidRow(nEndRow) && pRowFlags)
+ {
+ if (bManual)
+ pRowFlags->OrValue( nStartRow, nEndRow, CRFlags::ManualSize);
+ else
+ pRowFlags->AndValue( nStartRow, nEndRow, ~CRFlags::ManualSize);
+ }
+ else
+ {
+ OSL_FAIL("Invalid row number or no column flags");
+ }
+}
+
+sal_uInt16 ScTable::GetColWidth( SCCOL nCol, bool bHiddenAsZero ) const
+{
+ OSL_ENSURE(ValidCol(nCol),"wrong column number");
+
+ if (ValidCol(nCol) && mpColFlags && mpColWidth)
+ {
+ if (bHiddenAsZero && ColHidden(nCol))
+ return 0;
+ else
+ return mpColWidth->GetValue(nCol);
+ }
+ else
+ return sal_uInt16(STD_COL_WIDTH);
+}
+
+tools::Long ScTable::GetColWidth( SCCOL nStartCol, SCCOL nEndCol ) const
+{
+ if (!ValidCol(nStartCol) || !ValidCol(nEndCol) || nStartCol > nEndCol)
+ return 0;
+
+ tools::Long nW = 0;
+ bool bHidden = false;
+ SCCOL nLastHiddenCol = -1;
+ auto colWidthIt = mpColWidth->begin() + nStartCol;
+ for (SCCOL nCol = nStartCol; nCol <= nEndCol; (++nCol <= nEndCol) ? ++colWidthIt : (void)false)
+ {
+ if (nCol > nLastHiddenCol)
+ bHidden = ColHidden(nCol, nullptr, &nLastHiddenCol);
+
+ if (bHidden)
+ continue;
+
+ nW += *colWidthIt;
+ }
+ return nW;
+}
+
+sal_uInt16 ScTable::GetOriginalWidth( SCCOL nCol ) const // always the set value
+{
+ OSL_ENSURE(ValidCol(nCol),"wrong column number");
+
+ if (ValidCol(nCol) && mpColWidth)
+ return mpColWidth->GetValue(nCol);
+ else
+ return sal_uInt16(STD_COL_WIDTH);
+}
+
+sal_uInt16 ScTable::GetCommonWidth( SCCOL nEndCol ) const
+{
+ // get the width that is used in the largest continuous column range (up to nEndCol)
+
+ if ( !ValidCol(nEndCol) )
+ {
+ OSL_FAIL("wrong column");
+ nEndCol = rDocument.MaxCol();
+ }
+
+ sal_uInt16 nMaxWidth = 0;
+ sal_uInt16 nMaxCount = 0;
+ SCCOL nRangeStart = 0;
+ while ( nRangeStart <= nEndCol )
+ {
+ // skip hidden columns
+ while ( nRangeStart <= nEndCol && ColHidden(nRangeStart) )
+ ++nRangeStart;
+ if ( nRangeStart <= nEndCol )
+ {
+ sal_uInt16 nThisCount = 0;
+ auto colWidthIt = mpColWidth->begin() + nRangeStart;
+ sal_uInt16 nThisWidth = *colWidthIt;
+ SCCOL nRangeEnd = nRangeStart;
+ while ( nRangeEnd <= nEndCol && *colWidthIt == nThisWidth )
+ {
+ ++nThisCount;
+ ++nRangeEnd;
+ ++colWidthIt;
+
+ // skip hidden columns
+ while ( nRangeEnd <= nEndCol && ColHidden(nRangeEnd) )
+ {
+ ++nRangeEnd;
+ ++colWidthIt;
+ }
+ }
+
+ if ( nThisCount > nMaxCount )
+ {
+ nMaxCount = nThisCount;
+ nMaxWidth = nThisWidth;
+ }
+
+ nRangeStart = nRangeEnd; // next range
+ }
+ }
+
+ return nMaxWidth;
+}
+
+sal_uInt16 ScTable::GetRowHeight( SCROW nRow, SCROW* pStartRow, SCROW* pEndRow, bool bHiddenAsZero ) const
+{
+ SAL_WARN_IF(!ValidRow(nRow), "sc", "Invalid row number " << nRow);
+
+ if (ValidRow(nRow) && mpRowHeights)
+ {
+ if (bHiddenAsZero && RowHidden( nRow, pStartRow, pEndRow))
+ return 0;
+ else
+ {
+ ScFlatUInt16RowSegments::RangeData aData;
+ if (!mpRowHeights->getRangeData(nRow, aData))
+ {
+ if (pStartRow)
+ *pStartRow = nRow;
+ if (pEndRow)
+ *pEndRow = nRow;
+ // TODO: What should we return in case the search fails?
+ return 0;
+ }
+
+ // If bHiddenAsZero, pStartRow and pEndRow were initialized to
+ // boundaries of a non-hidden segment. Assume that the previous and
+ // next segment are hidden then and limit the current height
+ // segment.
+ if (pStartRow)
+ *pStartRow = (bHiddenAsZero ? std::max( *pStartRow, aData.mnRow1) : aData.mnRow1);
+ if (pEndRow)
+ *pEndRow = (bHiddenAsZero ? std::min( *pEndRow, aData.mnRow2) : aData.mnRow2);
+ return aData.mnValue;
+ }
+ }
+ else
+ {
+ if (pStartRow)
+ *pStartRow = nRow;
+ if (pEndRow)
+ *pEndRow = nRow;
+ return ScGlobal::nStdRowHeight;
+ }
+}
+
+tools::Long ScTable::GetRowHeight( SCROW nStartRow, SCROW nEndRow, bool bHiddenAsZero ) const
+{
+ OSL_ENSURE(ValidRow(nStartRow) && ValidRow(nEndRow),"wrong row number");
+
+ if (ValidRow(nStartRow) && ValidRow(nEndRow) && mpRowHeights)
+ {
+ tools::Long nHeight = 0;
+ SCROW nRow = nStartRow;
+ while (nRow <= nEndRow)
+ {
+ SCROW nLastRow = -1;
+ if (!( ( RowHidden(nRow, nullptr, &nLastRow) ) && bHiddenAsZero ) )
+ {
+ if (nLastRow > nEndRow)
+ nLastRow = nEndRow;
+ nHeight += mpRowHeights->getSumValue(nRow, nLastRow);
+ }
+ nRow = nLastRow + 1;
+ }
+ return nHeight;
+ }
+ else
+ return (nEndRow - nStartRow + 1) * static_cast<tools::Long>(ScGlobal::nStdRowHeight);
+}
+
+tools::Long ScTable::GetScaledRowHeight( SCROW nStartRow, SCROW nEndRow, double fScale ) const
+{
+ OSL_ENSURE(ValidRow(nStartRow) && ValidRow(nEndRow),"wrong row number");
+
+ if (ValidRow(nStartRow) && ValidRow(nEndRow) && mpRowHeights)
+ {
+ tools::Long nHeight = 0;
+ SCROW nRow = nStartRow;
+ while (nRow <= nEndRow)
+ {
+ SCROW nLastRow = -1;
+ if (!RowHidden(nRow, nullptr, &nLastRow))
+ {
+ if (nLastRow > nEndRow)
+ nLastRow = nEndRow;
+
+ // #i117315# can't use getSumValue, because individual values must be rounded
+ ScFlatUInt16RowSegments::ForwardIterator aSegmentIter(*mpRowHeights);
+ while (nRow <= nLastRow)
+ {
+ sal_uInt16 nRowVal;
+ if (!aSegmentIter.getValue(nRow, nRowVal))
+ return nHeight; // shouldn't happen
+
+ SCROW nSegmentEnd = std::min( nLastRow, aSegmentIter.getLastPos() );
+
+ // round-down a single height value, multiply resulting (pixel) values
+ tools::Long nOneHeight = static_cast<tools::Long>( nRowVal * fScale );
+ nHeight += nOneHeight * ( nSegmentEnd + 1 - nRow );
+
+ nRow = nSegmentEnd + 1;
+ }
+ }
+ nRow = nLastRow + 1;
+ }
+ return nHeight;
+ }
+ else
+ return static_cast<tools::Long>((nEndRow - nStartRow + 1) * ScGlobal::nStdRowHeight * fScale);
+}
+
+sal_uInt16 ScTable::GetOriginalHeight( SCROW nRow ) const // non-0 even if hidden
+{
+ OSL_ENSURE(ValidRow(nRow),"wrong row number");
+
+ if (ValidRow(nRow) && mpRowHeights)
+ return mpRowHeights->getValue(nRow);
+ else
+ return ScGlobal::nStdRowHeight;
+}
+
+// Column/Row -Flags
+
+SCROW ScTable::GetHiddenRowCount( SCROW nRow ) const
+{
+ if (!ValidRow(nRow))
+ return 0;
+
+ SCROW nLastRow = -1;
+ if (!RowHidden(nRow, nullptr, &nLastRow) || !ValidRow(nLastRow))
+ return 0;
+
+ return nLastRow - nRow + 1;
+}
+
+//TODO: combine ShowRows / DBShowRows
+
+void ScTable::ShowCol(SCCOL nCol, bool bShow)
+{
+ if (ValidCol(nCol))
+ {
+ bool bWasVis = !ColHidden(nCol);
+ if (bWasVis != bShow)
+ {
+ SetColHidden(nCol, nCol, !bShow);
+
+ ScChartListenerCollection* pCharts = rDocument.GetChartListenerCollection();
+ if ( pCharts )
+ pCharts->SetRangeDirty(ScRange( nCol, 0, nTab, nCol, rDocument.MaxRow(), nTab ));
+ }
+ }
+ else
+ {
+ OSL_FAIL("Invalid column number or no flags");
+ }
+}
+
+void ScTable::ShowRow(SCROW nRow, bool bShow)
+{
+ if (ValidRow(nRow) && pRowFlags)
+ {
+ bool bWasVis = !RowHidden(nRow);
+ if (bWasVis != bShow)
+ {
+ SetRowHidden(nRow, nRow, !bShow);
+ if (bShow)
+ SetRowFiltered(nRow, nRow, false);
+ ScChartListenerCollection* pCharts = rDocument.GetChartListenerCollection();
+ if ( pCharts )
+ pCharts->SetRangeDirty(ScRange( 0, nRow, nTab, rDocument.MaxCol(), nRow, nTab ));
+
+ InvalidatePageBreaks();
+ }
+ }
+ else
+ {
+ OSL_FAIL("Invalid row number or no flags");
+ }
+}
+
+void ScTable::DBShowRow(SCROW nRow, bool bShow)
+{
+ if (ValidRow(nRow) && pRowFlags)
+ {
+ // Always set filter flag; unchanged when Hidden
+ bool bChanged = SetRowHidden(nRow, nRow, !bShow);
+ SetRowFiltered(nRow, nRow, !bShow);
+
+ if (bChanged)
+ {
+ ScChartListenerCollection* pCharts = rDocument.GetChartListenerCollection();
+ if ( pCharts )
+ pCharts->SetRangeDirty(ScRange( 0, nRow, nTab, rDocument.MaxCol(), nRow, nTab ));
+
+ if (pOutlineTable)
+ UpdateOutlineRow( nRow, nRow, bShow );
+
+ InvalidatePageBreaks();
+ }
+ }
+ else
+ {
+ OSL_FAIL("Invalid row number or no flags");
+ }
+}
+
+void ScTable::DBShowRows(SCROW nRow1, SCROW nRow2, bool bShow)
+{
+ SCROW nStartRow = nRow1;
+ while (nStartRow <= nRow2)
+ {
+ SCROW nEndRow = -1;
+ bool bWasVis = !RowHiddenLeaf(nStartRow, nullptr, &nEndRow);
+ if (nEndRow > nRow2)
+ nEndRow = nRow2;
+
+ bool bChanged = ( bWasVis != bShow );
+
+ SetRowHidden(nStartRow, nEndRow, !bShow);
+ SetRowFiltered(nStartRow, nEndRow, !bShow);
+
+ if ( bChanged )
+ {
+ ScChartListenerCollection* pCharts = rDocument.GetChartListenerCollection();
+ if ( pCharts )
+ pCharts->SetRangeDirty(ScRange( 0, nStartRow, nTab, rDocument.MaxCol(), nEndRow, nTab ));
+ }
+
+ nStartRow = nEndRow + 1;
+ }
+
+ // #i12341# For Show/Hide rows, the outlines are updated separately from the outside.
+ // For filtering, the changes aren't visible to the caller, so UpdateOutlineRow has
+ // to be done here.
+ if (pOutlineTable)
+ UpdateOutlineRow( nRow1, nRow2, bShow );
+}
+
+void ScTable::ShowRows(SCROW nRow1, SCROW nRow2, bool bShow)
+{
+ SCROW nStartRow = nRow1;
+
+ // #i116164# if there are no drawing objects within the row range, a single HeightChanged call is enough
+ ScDrawLayer* pDrawLayer = rDocument.GetDrawLayer();
+ bool bHasObjects = pDrawLayer && pDrawLayer->HasObjectsInRows( nTab, nRow1, nRow2 );
+
+ while (nStartRow <= nRow2)
+ {
+ SCROW nEndRow = -1;
+ bool bWasVis = !RowHiddenLeaf(nStartRow, nullptr, &nEndRow);
+ if (nEndRow > nRow2)
+ nEndRow = nRow2;
+
+ bool bChanged = ( bWasVis != bShow );
+
+ SetRowHidden(nStartRow, nEndRow, !bShow);
+ if (bShow)
+ SetRowFiltered(nStartRow, nEndRow, false);
+
+ if ( bChanged )
+ {
+ ScChartListenerCollection* pCharts = rDocument.GetChartListenerCollection();
+ if ( pCharts )
+ pCharts->SetRangeDirty(ScRange( 0, nStartRow, nTab, rDocument.MaxCol(), nEndRow, nTab ));
+
+ InvalidatePageBreaks();
+ }
+
+ nStartRow = nEndRow + 1;
+ }
+
+ if ( !bHasObjects )
+ {
+ // #i116164# set the flags for the whole range at once
+ SetRowHidden(nRow1, nRow2, !bShow);
+ if (bShow)
+ SetRowFiltered(nRow1, nRow2, false);
+ }
+}
+
+bool ScTable::IsDataFiltered(SCCOL nColStart, SCROW nRowStart, SCCOL nColEnd, SCROW nRowEnd) const
+{
+ assert(nColStart <= nColEnd && nRowStart <= nRowEnd
+ && "range must be normalized to obtain a valid result");
+ for (SCROW i = nRowStart; i <= nRowEnd; ++i)
+ {
+ if (RowHidden(i))
+ return true;
+ }
+ for (SCCOL i = nColStart; i <= nColEnd; ++i)
+ {
+ if (ColHidden(i))
+ return true;
+ }
+ return false;
+}
+
+bool ScTable::IsDataFiltered(const ScRange& rRange) const
+{
+ ScRange aNormalized(rRange.aStart, rRange.aEnd);
+ return IsDataFiltered(aNormalized.aStart.Col(), aNormalized.aStart.Row(),
+ aNormalized.aEnd.Col(), aNormalized.aEnd.Row());
+}
+
+void ScTable::SetRowFlags( SCROW nRow, CRFlags nNewFlags )
+{
+ if (ValidRow(nRow) && pRowFlags)
+ pRowFlags->SetValue( nRow, nNewFlags);
+ else
+ {
+ OSL_FAIL("Invalid row number or no flags");
+ }
+}
+
+void ScTable::SetRowFlags( SCROW nStartRow, SCROW nEndRow, CRFlags nNewFlags )
+{
+ if (ValidRow(nStartRow) && ValidRow(nEndRow) && pRowFlags)
+ pRowFlags->SetValue( nStartRow, nEndRow, nNewFlags);
+ else
+ {
+ OSL_FAIL("Invalid row number(s) or no flags");
+ }
+}
+
+CRFlags ScTable::GetColFlags( SCCOL nCol ) const
+{
+ if (ValidCol(nCol) && mpColFlags)
+ return mpColFlags->GetValue(nCol);
+ else
+ return CRFlags::NONE;
+}
+
+CRFlags ScTable::GetRowFlags( SCROW nRow ) const
+{
+ if (ValidRow(nRow) && pRowFlags)
+ return pRowFlags->GetValue(nRow);
+ else
+ return CRFlags::NONE;
+}
+
+SCROW ScTable::GetLastFlaggedRow() const
+{
+ SCROW nLastFound = 0;
+ if (pRowFlags)
+ {
+ SCROW nRow = pRowFlags->GetLastAnyBitAccess( CRFlags::All );
+ if (ValidRow(nRow))
+ nLastFound = nRow;
+ }
+
+ if (!maRowManualBreaks.empty())
+ nLastFound = ::std::max(nLastFound, *maRowManualBreaks.rbegin());
+
+ if (mpHiddenRows)
+ {
+ SCROW nRow = mpHiddenRows->findLastTrue();
+ if (ValidRow(nRow))
+ nLastFound = ::std::max(nLastFound, nRow);
+ }
+
+ if (mpFilteredRows)
+ {
+ SCROW nRow = mpFilteredRows->findLastTrue();
+ if (ValidRow(nRow))
+ nLastFound = ::std::max(nLastFound, nRow);
+ }
+
+ return nLastFound;
+}
+
+SCCOL ScTable::GetLastChangedColFlagsWidth() const
+{
+ if ( !mpColFlags )
+ return 0;
+
+ SCCOL nLastFound = 0;
+ auto colWidthIt = mpColWidth->begin() + 1;
+ for (SCCOL nCol = 1; nCol <= GetDoc().MaxCol(); (++nCol <= GetDoc().MaxCol()) ? ++colWidthIt : (void)false)
+ if ((mpColFlags->GetValue(nCol) & CRFlags::All) || (*colWidthIt != STD_COL_WIDTH))
+ nLastFound = nCol;
+
+ return nLastFound;
+}
+
+SCROW ScTable::GetLastChangedRowFlagsWidth() const
+{
+ if ( !pRowFlags )
+ return 0;
+
+ SCROW nLastFlags = GetLastFlaggedRow();
+
+ // Find the last row position where the height is NOT the standard row
+ // height.
+ // KOHEI: Test this to make sure it does what it's supposed to.
+ SCROW nLastHeight = mpRowHeights->findLastTrue(ScGlobal::nStdRowHeight);
+ if (!ValidRow(nLastHeight))
+ nLastHeight = 0;
+
+ return std::max( nLastFlags, nLastHeight);
+}
+
+bool ScTable::UpdateOutlineCol( SCCOL nStartCol, SCCOL nEndCol, bool bShow )
+{
+ if (pOutlineTable && mpColFlags)
+ {
+ return pOutlineTable->GetColArray().ManualAction( nStartCol, nEndCol, bShow, *this, true );
+ }
+ else
+ return false;
+}
+
+bool ScTable::UpdateOutlineRow( SCROW nStartRow, SCROW nEndRow, bool bShow )
+{
+ if (pOutlineTable && pRowFlags)
+ return pOutlineTable->GetRowArray().ManualAction( nStartRow, nEndRow, bShow, *this, false );
+ else
+ return false;
+}
+
+void ScTable::ExtendHidden( SCCOL& rX1, SCROW& rY1, SCCOL& rX2, SCROW& rY2 )
+{
+ // Column-wise expansion
+
+ while (rX1 > 0 && ColHidden(rX1-1))
+ --rX1;
+
+ while (rX2 < rDocument.MaxCol() && ColHidden(rX2+1))
+ ++rX2;
+
+ // Row-wise expansion
+
+ if (rY1 > 0)
+ {
+ ScFlatBoolRowSegments::RangeData aData;
+ if (mpHiddenRows->getRangeData(rY1-1, aData) && aData.mbValue)
+ {
+ SCROW nStartRow = aData.mnRow1;
+ if (ValidRow(nStartRow))
+ rY1 = nStartRow;
+ }
+ }
+ if (rY2 < rDocument.MaxRow())
+ {
+ SCROW nEndRow = -1;
+ if (RowHidden(rY2+1, nullptr, &nEndRow) && ValidRow(nEndRow))
+ rY2 = nEndRow;
+ }
+}
+
+void ScTable::StripHidden( SCCOL& rX1, SCROW& rY1, SCCOL& rX2, SCROW& rY2 )
+{
+ while ( rX2>rX1 && ColHidden(rX2) )
+ --rX2;
+ while ( rX2>rX1 && ColHidden(rX1) )
+ ++rX1;
+
+ if (rY1 < rY2)
+ {
+ ScFlatBoolRowSegments::RangeData aData;
+ if (mpHiddenRows->getRangeData(rY2, aData) && aData.mbValue)
+ {
+ SCROW nStartRow = aData.mnRow1;
+ if (ValidRow(nStartRow) && nStartRow >= rY1)
+ rY2 = nStartRow;
+ }
+ }
+
+ if (rY1 < rY2)
+ {
+ SCROW nEndRow = -1;
+ if (RowHidden(rY1, nullptr, &nEndRow) && ValidRow(nEndRow) && nEndRow <= rY2)
+ rY1 = nEndRow;
+ }
+}
+
+// Auto-Outline
+
+template< typename T >
+static short DiffSign( T a, T b )
+{
+ return (a<b) ? -1 :
+ (a>b) ? 1 : 0;
+}
+
+namespace {
+
+class OutlineArrayFinder
+{
+ ScRange maRef;
+ SCCOL mnCol;
+ SCTAB mnTab;
+ ScOutlineArray* mpArray;
+ bool mbSizeChanged;
+
+public:
+ OutlineArrayFinder(const ScRange& rRef, SCCOL nCol, SCTAB nTab, ScOutlineArray* pArray, bool bSizeChanged) :
+ maRef(rRef), mnCol(nCol), mnTab(nTab), mpArray(pArray),
+ mbSizeChanged(bSizeChanged) {}
+
+ bool operator() (size_t nRow, const ScFormulaCell* pCell)
+ {
+ SCROW nRow2 = static_cast<SCROW>(nRow);
+
+ if (!pCell->HasRefListExpressibleAsOneReference(maRef))
+ return false;
+
+ if (maRef.aStart.Row() != nRow2 || maRef.aEnd.Row() != nRow2 ||
+ maRef.aStart.Tab() != mnTab || maRef.aEnd.Tab() != mnTab)
+ return false;
+
+ if (DiffSign(maRef.aStart.Col(), mnCol) != DiffSign(maRef.aEnd.Col(), mnCol))
+ return false;
+
+ return mpArray->Insert(maRef.aStart.Col(), maRef.aEnd.Col(), mbSizeChanged);
+ }
+};
+
+}
+
+void ScTable::DoAutoOutline( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow )
+{
+ typedef mdds::flat_segment_tree<SCROW, bool> UsedRowsType;
+
+ bool bSizeChanged = false;
+
+ SCCOL nCol;
+ SCROW nRow;
+ bool bFound;
+ ScRange aRef;
+
+ nEndCol = ClampToAllocatedColumns(nEndCol);
+
+ StartOutlineTable();
+
+ // Rows
+
+ UsedRowsType aUsed(0, rDocument.MaxRow()+1, false);
+ for (nCol=nStartCol; nCol<=nEndCol; nCol++)
+ aCol[nCol].FindUsed(nStartRow, nEndRow, aUsed);
+ aUsed.build_tree();
+
+ ScOutlineArray& rRowArray = pOutlineTable->GetRowArray();
+ for (nRow=nStartRow; nRow<=nEndRow; nRow++)
+ {
+ bool bUsed = false;
+ SCROW nLastRow = nRow;
+ aUsed.search_tree(nRow, bUsed, nullptr, &nLastRow);
+ if (!bUsed)
+ {
+ nRow = nLastRow;
+ continue;
+ }
+
+ bFound = false;
+ for (nCol=nStartCol; nCol<=nEndCol && !bFound; nCol++)
+ {
+ ScRefCellValue aCell = aCol[nCol].GetCellValue(nRow);
+
+ if (aCell.meType != CELLTYPE_FORMULA)
+ continue;
+
+ if (!aCell.mpFormula->HasRefListExpressibleAsOneReference(aRef))
+ continue;
+
+ if ( aRef.aStart.Col() == nCol && aRef.aEnd.Col() == nCol &&
+ aRef.aStart.Tab() == nTab && aRef.aEnd.Tab() == nTab &&
+ DiffSign( aRef.aStart.Row(), nRow ) ==
+ DiffSign( aRef.aEnd.Row(), nRow ) )
+ {
+ if (rRowArray.Insert( aRef.aStart.Row(), aRef.aEnd.Row(), bSizeChanged ))
+ {
+ bFound = true;
+ }
+ }
+ }
+ }
+
+ // Column
+ ScOutlineArray& rColArray = pOutlineTable->GetColArray();
+ for (nCol=nStartCol; nCol<=nEndCol; nCol++)
+ {
+ if (aCol[nCol].IsEmptyData())
+ continue;
+
+ OutlineArrayFinder aFunc(aRef, nCol, nTab, &rColArray, bSizeChanged);
+ sc::FindFormula(aCol[nCol].maCells, nStartRow, nEndRow, aFunc);
+ }
+}
+
+ // CopyData - for Query in other range
+
+void ScTable::CopyData( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow,
+ SCCOL nDestCol, SCROW nDestRow, SCTAB nDestTab )
+{
+ //TODO: if used for multiple rows, optimize after columns!
+
+ ScAddress aSrc( nStartCol, nStartRow, nTab );
+ ScAddress aDest( nDestCol, nDestRow, nDestTab );
+ ScRange aRange( aSrc, aDest );
+ bool bThisTab = ( nDestTab == nTab );
+ SCROW nDestY = nDestRow;
+ for (SCROW nRow=nStartRow; nRow<=nEndRow; nRow++)
+ {
+ aSrc.SetRow( nRow );
+ aDest.SetRow( nDestY );
+ SCCOL nDestX = nDestCol;
+ for (SCCOL nCol=nStartCol; nCol<=nEndCol; nCol++)
+ {
+ aSrc.SetCol( nCol );
+ aDest.SetCol( nDestX );
+ ScCellValue aCell;
+ aCell.assign(rDocument, ScAddress(nCol, nRow, nTab));
+
+ if (aCell.meType == CELLTYPE_FORMULA)
+ {
+ sc::RefUpdateContext aCxt(rDocument);
+ aCxt.meMode = URM_COPY;
+ aCxt.maRange = aRange;
+ aCxt.mnColDelta = nDestCol - nStartCol;
+ aCxt.mnRowDelta = nDestRow - nStartRow;
+ aCxt.mnTabDelta = nDestTab - nTab;
+ aCell.mpFormula->UpdateReference(aCxt);
+ aCell.mpFormula->aPos = aDest;
+ }
+
+ if (bThisTab)
+ {
+ aCell.release(CreateColumnIfNotExists(nDestX), nDestY);
+ SetPattern( nDestX, nDestY, *GetPattern( nCol, nRow ) );
+ }
+ else
+ {
+ aCell.release(rDocument, aDest);
+ rDocument.SetPattern( aDest, *GetPattern( nCol, nRow ) );
+ }
+
+ ++nDestX;
+ }
+ ++nDestY;
+ }
+}
+
+bool ScTable::RefVisible(const ScFormulaCell* pCell)
+{
+ ScRange aRef;
+
+ if (pCell->HasOneReference(aRef))
+ {
+ if (aRef.aStart.Col()==aRef.aEnd.Col() && aRef.aStart.Tab()==aRef.aEnd.Tab())
+ {
+ SCROW nEndRow;
+ if (!RowFiltered(aRef.aStart.Row(), nullptr, &nEndRow))
+ // row not filtered.
+ nEndRow = ::std::numeric_limits<SCROW>::max();
+
+ if (!ValidRow(nEndRow) || nEndRow < aRef.aEnd.Row())
+ return true; // at least partly visible
+ return false; // completely invisible
+ }
+ }
+
+ return true; // somehow different
+}
+
+OUString ScTable::GetUpperCellString(SCCOL nCol, SCROW nRow)
+{
+ return ScGlobal::getCharClass().uppercase(GetInputString(nCol, nRow).trim());
+}
+
+// Calculate the size of the sheet and set the size on DrawPage
+
+void ScTable::SetDrawPageSize(bool bResetStreamValid, bool bUpdateNoteCaptionPos,
+ ScObjectHandling eObjectHandling)
+{
+ ScDrawLayer* pDrawLayer = rDocument.GetDrawLayer();
+ if( pDrawLayer )
+ {
+ const sal_Int64 nMax = ::std::numeric_limits<tools::Long>::max();
+ // #i113884# Avoid int32 overflow with possible negative results than can cause bad effects.
+ // If the draw page size is smaller than all rows, only the bottom of the sheet is affected.
+ tools::Long x = std::min(o3tl::convert(GetColOffset(rDocument.MaxCol() + 1),
+ o3tl::Length::twip, o3tl::Length::mm100),
+ nMax);
+ tools::Long y = std::min(o3tl::convert(GetRowOffset(rDocument.MaxRow() + 1),
+ o3tl::Length::twip, o3tl::Length::mm100),
+ nMax);
+
+ if ( IsLayoutRTL() ) // IsNegativePage
+ x = -x;
+
+ pDrawLayer->SetPageSize(static_cast<sal_uInt16>(nTab), Size(x, y), bUpdateNoteCaptionPos,
+ eObjectHandling);
+ }
+
+ // #i102616# actions that modify the draw page size count as sheet modification
+ // (exception: InitDrawLayer)
+ if (bResetStreamValid)
+ SetStreamValid(false);
+}
+
+void ScTable::SetRangeName(std::unique_ptr<ScRangeName> pNew)
+{
+ mpRangeName = std::move(pNew);
+
+ //fdo#39792: mark stream as invalid, otherwise new ScRangeName will not be written to file
+ SetStreamValid(false);
+}
+
+ScRangeName* ScTable::GetRangeName() const
+{
+ if (!mpRangeName)
+ mpRangeName.reset(new ScRangeName);
+ return mpRangeName.get();
+}
+
+tools::Long ScTable::GetRowOffset( SCROW nRow, bool bHiddenAsZero ) const
+{
+ tools::Long n = 0;
+ if ( mpHiddenRows && mpRowHeights )
+ {
+ if (nRow == 0)
+ return 0;
+ else if (nRow == 1)
+ return GetRowHeight(0, nullptr, nullptr, bHiddenAsZero );
+
+ n = GetTotalRowHeight(0, nRow-1, bHiddenAsZero);
+#if OSL_DEBUG_LEVEL > 0
+ if (n == ::std::numeric_limits<tools::Long>::max())
+ OSL_FAIL("ScTable::GetRowOffset: row heights overflow");
+#endif
+ }
+ else
+ {
+ OSL_FAIL("GetRowOffset: Data missing");
+ }
+ return n;
+}
+
+SCROW ScTable::GetRowForHeight(tools::Long nHeight) const
+{
+ tools::Long nSum = 0;
+
+ ScFlatBoolRowSegments::RangeData aData;
+
+ ScFlatUInt16RowSegments::RangeData aRowHeightRange;
+ aRowHeightRange.mnRow2 = -1;
+ aRowHeightRange.mnValue = 1; // silence MSVC C4701
+
+ for (SCROW nRow = 0; nRow <= rDocument.MaxRow(); ++nRow)
+ {
+ if (!mpHiddenRows->getRangeData(nRow, aData))
+ // Failed to fetch the range data for whatever reason.
+ break;
+
+ if (aData.mbValue)
+ {
+ // This row is hidden. Skip ahead all hidden rows.
+ nRow = aData.mnRow2;
+ continue;
+ }
+
+ if (aRowHeightRange.mnRow2 < nRow)
+ {
+ if (!mpRowHeights->getRangeData(nRow, aRowHeightRange))
+ // Failed to fetch the range data for whatever reason.
+ break;
+ }
+
+ // find the last common row between hidden & height spans
+ SCROW nLastCommon = std::min(aData.mnRow2, aRowHeightRange.mnRow2);
+ assert (nLastCommon >= nRow);
+ SCROW nCommon = nLastCommon - nRow + 1;
+
+ // how much further to go ?
+ tools::Long nPixelsLeft = nHeight - nSum;
+ tools::Long nCommonPixels = static_cast<tools::Long>(aRowHeightRange.mnValue) * nCommon;
+
+ // are we in the zone ?
+ if (nCommonPixels > nPixelsLeft)
+ {
+ nRow += (nPixelsLeft + aRowHeightRange.mnValue - 1) / aRowHeightRange.mnValue;
+
+ // FIXME: finding this next row is far from elegant,
+ // we have a single caller, which subtracts one as well(!?)
+ if (nRow >= rDocument.MaxRow())
+ return rDocument.MaxRow();
+
+ if (!mpHiddenRows->getRangeData(nRow, aData))
+ // Failed to fetch the range data for whatever reason.
+ break;
+
+ if (aData.mbValue)
+ // These rows are hidden.
+ nRow = aData.mnRow2 + 1;
+
+ return nRow <= rDocument.MaxRow() ? nRow : rDocument.MaxRow();
+ }
+
+ // skip the range and keep hunting
+ nSum += nCommonPixels;
+ nRow = nLastCommon;
+ }
+ return -1;
+}
+
+tools::Long ScTable::GetColOffset( SCCOL nCol, bool bHiddenAsZero ) const
+{
+ tools::Long n = 0;
+ if ( mpColWidth )
+ {
+ auto colWidthIt = mpColWidth->begin();
+ for (SCCOL i = 0; i < nCol; (++i < nCol) ? ++colWidthIt : (void)false)
+ if (!( bHiddenAsZero && ColHidden(i) ))
+ n += *colWidthIt;
+ }
+ else
+ {
+ OSL_FAIL("GetColumnOffset: Data missing");
+ }
+ return n;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/table3.cxx b/sc/source/core/data/table3.cxx
new file mode 100644
index 000000000..c30032582
--- /dev/null
+++ b/sc/source/core/data/table3.cxx
@@ -0,0 +1,3099 @@
+/* -*- 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 <comphelper/random.hxx>
+#include <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+#include <svl/zformat.hxx>
+#include <unotools/collatorwrapper.hxx>
+#include <stdlib.h>
+#include <com/sun/star/i18n/KParseTokens.hpp>
+#include <com/sun/star/i18n/KParseType.hpp>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <refdata.hxx>
+#include <table.hxx>
+#include <scitems.hxx>
+#include <formulacell.hxx>
+#include <document.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <global.hxx>
+#include <stlpool.hxx>
+#include <patattr.hxx>
+#include <subtotal.hxx>
+#include <markdata.hxx>
+#include <rangelst.hxx>
+#include <userlist.hxx>
+#include <progress.hxx>
+#include <queryparam.hxx>
+#include <queryentry.hxx>
+#include <subtotalparam.hxx>
+#include <docpool.hxx>
+#include <cellvalue.hxx>
+#include <tokenarray.hxx>
+#include <mtvcellfunc.hxx>
+#include <columnspanset.hxx>
+#include <fstalgorithm.hxx>
+#include <listenercontext.hxx>
+#include <sharedformula.hxx>
+#include <stlsheet.hxx>
+#include <refhint.hxx>
+#include <listenerquery.hxx>
+#include <bcaslot.hxx>
+#include <reordermap.hxx>
+#include <drwlayer.hxx>
+#include <queryevaluator.hxx>
+#include <scopetools.hxx>
+
+#include <svl/sharedstringpool.hxx>
+
+#include <memory>
+#include <set>
+#include <unordered_set>
+#include <vector>
+#include <mdds/flat_segment_tree.hpp>
+
+using namespace ::com::sun::star;
+
+namespace naturalsort {
+
+using namespace ::com::sun::star::i18n;
+
+/** Splits a given string into three parts: the prefix, number string, and
+ the suffix.
+
+ @param sWhole
+ Original string to be split into pieces
+
+ @param sPrefix
+ Prefix string that consists of the part before the first number token.
+ If no number was found, sPrefix is unchanged.
+
+ @param sSuffix
+ String after the last number token. This may still contain number strings.
+ If no number was found, sSuffix is unchanged.
+
+ @param fNum
+ Number converted from the middle number string
+ If no number was found, fNum is unchanged.
+
+ @return Returns TRUE if a numeral element is found in a given string, or
+ FALSE if no numeral element is found.
+*/
+static bool SplitString( const OUString &sWhole,
+ OUString &sPrefix, OUString &sSuffix, double &fNum )
+{
+ // Get prefix element, search for any digit and stop.
+ sal_Int32 nPos = 0;
+ while (nPos < sWhole.getLength())
+ {
+ const sal_uInt16 nType = ScGlobal::getCharClass().getCharacterType( sWhole, nPos);
+ if (nType & KCharacterType::DIGIT)
+ break;
+ sWhole.iterateCodePoints( &nPos );
+ }
+
+ // Return FALSE if no numeral element is found
+ if ( nPos == sWhole.getLength() )
+ return false;
+
+ // Get numeral element
+ const OUString& sUser = ScGlobal::getLocaleData().getNumDecimalSep();
+ ParseResult aPRNum = ScGlobal::getCharClass().parsePredefinedToken(
+ KParseType::ANY_NUMBER, sWhole, nPos,
+ KParseTokens::ANY_NUMBER, "", KParseTokens::ANY_NUMBER, sUser );
+
+ if ( aPRNum.EndPos == nPos )
+ {
+ SAL_WARN("sc.core","naturalsort::SplitString - digit found but no number parsed, pos " <<
+ nPos << " : " << sWhole);
+ return false;
+ }
+
+ sPrefix = sWhole.copy( 0, nPos );
+ fNum = aPRNum.Value;
+ sSuffix = sWhole.copy( aPRNum.EndPos );
+
+ return true;
+}
+
+/** Naturally compares two given strings.
+
+ This is the main function that should be called externally. It returns
+ either 1, 0, or -1 depending on the comparison result of given two strings.
+
+ @param sInput1
+ Input string 1
+
+ @param sInput2
+ Input string 2
+
+ @param bCaseSens
+ Boolean value for case sensitivity
+
+ @param pData
+ Pointer to user defined sort list
+
+ @param pCW
+ Pointer to collator wrapper for normal string comparison
+
+ @return Returns 1 if sInput1 is greater, 0 if sInput1 == sInput2, and -1 if
+ sInput2 is greater.
+*/
+static short Compare( const OUString &sInput1, const OUString &sInput2,
+ const bool bCaseSens, const ScUserListData* pData, const CollatorWrapper *pCW )
+{
+ OUString sStr1( sInput1 ), sStr2( sInput2 ), sPre1, sSuf1, sPre2, sSuf2;
+
+ do
+ {
+ double nNum1, nNum2;
+ bool bNumFound1 = SplitString( sStr1, sPre1, sSuf1, nNum1 );
+ bool bNumFound2 = SplitString( sStr2, sPre2, sSuf2, nNum2 );
+
+ short nPreRes; // Prefix comparison result
+ if ( pData )
+ {
+ if ( bCaseSens )
+ {
+ if ( !bNumFound1 || !bNumFound2 )
+ return static_cast<short>(pData->Compare( sStr1, sStr2 ));
+ else
+ nPreRes = pData->Compare( sPre1, sPre2 );
+ }
+ else
+ {
+ if ( !bNumFound1 || !bNumFound2 )
+ return static_cast<short>(pData->ICompare( sStr1, sStr2 ));
+ else
+ nPreRes = pData->ICompare( sPre1, sPre2 );
+ }
+ }
+ else
+ {
+ if ( !bNumFound1 || !bNumFound2 )
+ return static_cast<short>(pCW->compareString( sStr1, sStr2 ));
+ else
+ nPreRes = static_cast<short>(pCW->compareString( sPre1, sPre2 ));
+ }
+
+ // Prefix strings differ. Return immediately.
+ if ( nPreRes != 0 ) return nPreRes;
+
+ if ( nNum1 != nNum2 )
+ {
+ if ( nNum1 < nNum2 ) return -1;
+ return (nNum1 > nNum2) ? 1 : 0;
+ }
+
+ // The prefix and the first numerical elements are equal, but the suffix
+ // strings may still differ. Stay in the loop.
+
+ sStr1 = sSuf1;
+ sStr2 = sSuf2;
+
+ } while (true);
+
+ return 0;
+}
+
+}
+
+namespace {
+
+struct ScSortInfo final
+{
+ ScRefCellValue maCell;
+ SCCOLROW nOrg;
+};
+
+}
+
+class ScSortInfoArray
+{
+public:
+
+ struct Cell
+ {
+ ScRefCellValue maCell;
+ const sc::CellTextAttr* mpAttr;
+ const ScPostIt* mpNote;
+ std::vector<SdrObject*> maDrawObjects;
+ const ScPatternAttr* mpPattern;
+
+ Cell() : mpAttr(nullptr), mpNote(nullptr), mpPattern(nullptr) {}
+ };
+
+ struct Row
+ {
+ std::vector<Cell> maCells;
+
+ bool mbHidden:1;
+ bool mbFiltered:1;
+
+ explicit Row( size_t nColSize ) : maCells(nColSize, Cell()), mbHidden(false), mbFiltered(false) {}
+ };
+
+ typedef std::vector<Row> RowsType;
+
+private:
+ std::unique_ptr<RowsType> mpRows; /// row-wise data table for sort by row operation.
+
+ std::vector<std::unique_ptr<ScSortInfo[]>> mvppInfo;
+ SCCOLROW nStart;
+ SCCOLROW mnLastIndex; /// index of last non-empty cell position.
+
+ std::vector<SCCOLROW> maOrderIndices;
+ bool mbKeepQuery;
+ bool mbUpdateRefs;
+
+public:
+ ScSortInfoArray(const ScSortInfoArray&) = delete;
+ const ScSortInfoArray& operator=(const ScSortInfoArray&) = delete;
+
+ ScSortInfoArray( sal_uInt16 nSorts, SCCOLROW nInd1, SCCOLROW nInd2 ) :
+ mvppInfo(nSorts),
+ nStart( nInd1 ),
+ mnLastIndex(nInd2),
+ mbKeepQuery(false),
+ mbUpdateRefs(false)
+ {
+ SCSIZE nCount( nInd2 - nInd1 + 1 );
+ if (nSorts)
+ {
+ for ( sal_uInt16 nSort = 0; nSort < nSorts; nSort++ )
+ {
+ mvppInfo[nSort].reset(new ScSortInfo[nCount]);
+ }
+ }
+
+ for (size_t i = 0; i < nCount; ++i)
+ maOrderIndices.push_back(i+nStart);
+ }
+
+ void SetKeepQuery( bool b ) { mbKeepQuery = b; }
+
+ bool IsKeepQuery() const { return mbKeepQuery; }
+
+ void SetUpdateRefs( bool b ) { mbUpdateRefs = b; }
+
+ bool IsUpdateRefs() const { return mbUpdateRefs; }
+
+ /**
+ * Call this only during normal sorting, not from reordering.
+ */
+ std::unique_ptr<ScSortInfo[]> const & GetFirstArray() const
+ {
+ return mvppInfo[0];
+ }
+
+ /**
+ * Call this only during normal sorting, not from reordering.
+ */
+ ScSortInfo & Get( sal_uInt16 nSort, SCCOLROW nInd )
+ {
+ return mvppInfo[nSort][ nInd - nStart ];
+ }
+
+ /**
+ * Call this only during normal sorting, not from reordering.
+ */
+ void Swap( SCCOLROW nInd1, SCCOLROW nInd2 )
+ {
+ if (nInd1 == nInd2) // avoid self-move-assign
+ return;
+ SCSIZE n1 = static_cast<SCSIZE>(nInd1 - nStart);
+ SCSIZE n2 = static_cast<SCSIZE>(nInd2 - nStart);
+ for ( sal_uInt16 nSort = 0; nSort < static_cast<sal_uInt16>(mvppInfo.size()); nSort++ )
+ {
+ auto & ppInfo = mvppInfo[nSort];
+ std::swap(ppInfo[n1], ppInfo[n2]);
+ }
+
+ std::swap(maOrderIndices[n1], maOrderIndices[n2]);
+
+ if (mpRows)
+ {
+ // Swap rows in data table.
+ RowsType& rRows = *mpRows;
+ std::swap(rRows[n1], rRows[n2]);
+ }
+ }
+
+ void SetOrderIndices( std::vector<SCCOLROW>&& rIndices )
+ {
+ maOrderIndices = std::move(rIndices);
+ }
+
+ /**
+ * @param rIndices indices are actual row positions on the sheet, not an
+ * offset from the top row.
+ */
+ void ReorderByRow( const std::vector<SCCOLROW>& rIndices )
+ {
+ if (!mpRows)
+ return;
+
+ RowsType& rRows = *mpRows;
+
+ std::vector<SCCOLROW> aOrderIndices2;
+ aOrderIndices2.reserve(rIndices.size());
+
+ RowsType aRows2;
+ aRows2.reserve(rRows.size());
+
+ for (const auto& rIndex : rIndices)
+ {
+ size_t nPos = rIndex - nStart; // switch to an offset to top row.
+ aRows2.push_back(rRows[nPos]);
+ aOrderIndices2.push_back(maOrderIndices[nPos]);
+ }
+
+ rRows.swap(aRows2);
+ maOrderIndices.swap(aOrderIndices2);
+ }
+
+ sal_uInt16 GetUsedSorts() const { return mvppInfo.size(); }
+
+ SCCOLROW GetStart() const { return nStart; }
+ SCCOLROW GetLast() const { return mnLastIndex; }
+
+ const std::vector<SCCOLROW>& GetOrderIndices() const { return maOrderIndices; }
+
+ RowsType& InitDataRows( size_t nRowSize, size_t nColSize )
+ {
+ mpRows.reset(new RowsType);
+ mpRows->resize(nRowSize, Row(nColSize));
+ return *mpRows;
+ }
+
+ RowsType* GetDataRows()
+ {
+ return mpRows.get();
+ }
+};
+
+// Assume that we can handle 512MB, which with a ~100 bytes
+// ScSortInfoArray::Cell element for 500MB are about 5 million cells plus
+// overhead in one chunk.
+constexpr sal_Int32 kSortCellsChunk = 500 * 1024 * 1024 / sizeof(ScSortInfoArray::Cell);
+
+namespace {
+
+void initDataRows(
+ ScSortInfoArray& rArray, ScTable& rTab, ScColContainer& rCols,
+ SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ bool bHiddenFiltered, bool bPattern, bool bCellNotes, bool bCellDrawObjects, bool bOnlyDataAreaExtras )
+{
+ // Fill row-wise data table.
+ ScSortInfoArray::RowsType& rRows = rArray.InitDataRows(nRow2-nRow1+1, nCol2-nCol1+1);
+
+ const std::vector<SCCOLROW>& rOrderIndices = rArray.GetOrderIndices();
+ assert(!bOnlyDataAreaExtras || (rOrderIndices.size() == static_cast<size_t>(nRow2 - nRow1 + 1)
+ && nRow1 == rArray.GetStart()));
+
+ ScDrawLayer* pDrawLayer = (bCellDrawObjects ? rTab.GetDoc().GetDrawLayer() : nullptr);
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ {
+ ScColumn& rCol = rCols[nCol];
+
+ // Skip reordering of cell formats if the whole span is on the same pattern entry.
+ bool bUniformPattern = rCol.GetPatternCount(nRow1, nRow2) < 2u;
+
+ sc::ColumnBlockConstPosition aBlockPos;
+ rCol.InitBlockPosition(aBlockPos);
+ std::map<SCROW, std::vector<SdrObject*>> aRowDrawObjects;
+ if (pDrawLayer)
+ aRowDrawObjects = pDrawLayer->GetObjectsAnchoredToRange(rTab.GetTab(), nCol, nRow1, nRow2);
+
+ for (SCROW nR = nRow1; nR <= nRow2; ++nR)
+ {
+ const SCROW nRow = (bOnlyDataAreaExtras ? rOrderIndices[nR - rArray.GetStart()] : nR);
+ ScSortInfoArray::Row& rRow = rRows[nR-nRow1];
+ ScSortInfoArray::Cell& rCell = rRow.maCells[nCol-nCol1];
+ if (!bOnlyDataAreaExtras)
+ {
+ rCell.maCell = rCol.GetCellValue(aBlockPos, nRow);
+ rCell.mpAttr = rCol.GetCellTextAttr(aBlockPos, nRow);
+ }
+ if (bCellNotes)
+ rCell.mpNote = rCol.GetCellNote(aBlockPos, nRow);
+ if (pDrawLayer)
+ rCell.maDrawObjects = aRowDrawObjects[nRow];
+
+ if (!bUniformPattern && bPattern)
+ rCell.mpPattern = rCol.GetPattern(nRow);
+ }
+ }
+
+ if (!bOnlyDataAreaExtras && bHiddenFiltered)
+ {
+ for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
+ {
+ ScSortInfoArray::Row& rRow = rRows[nRow-nRow1];
+ rRow.mbHidden = rTab.RowHidden(nRow);
+ rRow.mbFiltered = rTab.RowFiltered(nRow);
+ }
+ }
+}
+
+}
+
+std::unique_ptr<ScSortInfoArray> ScTable::CreateSortInfoArray( const sc::ReorderParam& rParam )
+{
+ std::unique_ptr<ScSortInfoArray> pArray;
+
+ if (rParam.mbByRow)
+ {
+ // Create a sort info array with just the data table.
+ SCROW nRow1 = rParam.maSortRange.aStart.Row();
+ SCROW nRow2 = rParam.maSortRange.aEnd.Row();
+ SCCOL nCol1 = rParam.maSortRange.aStart.Col();
+ SCCOL nCol2 = rParam.maSortRange.aEnd.Col();
+
+ pArray.reset(new ScSortInfoArray(0, nRow1, nRow2));
+ pArray->SetKeepQuery(rParam.mbHiddenFiltered);
+ pArray->SetUpdateRefs(rParam.mbUpdateRefs);
+
+ CreateColumnIfNotExists(nCol2);
+ initDataRows( *pArray, *this, aCol, nCol1, nRow1, nCol2, nRow2, rParam.mbHiddenFiltered,
+ rParam.maDataAreaExtras.mbCellFormats, true, true, false);
+ }
+ else
+ {
+ SCCOLROW nCol1 = rParam.maSortRange.aStart.Col();
+ SCCOLROW nCol2 = rParam.maSortRange.aEnd.Col();
+
+ pArray.reset(new ScSortInfoArray(0, nCol1, nCol2));
+ pArray->SetKeepQuery(rParam.mbHiddenFiltered);
+ pArray->SetUpdateRefs(rParam.mbUpdateRefs);
+ }
+
+ return pArray;
+}
+
+std::unique_ptr<ScSortInfoArray> ScTable::CreateSortInfoArray(
+ const ScSortParam& rSortParam, SCCOLROW nInd1, SCCOLROW nInd2,
+ bool bKeepQuery, bool bUpdateRefs )
+{
+ sal_uInt16 nUsedSorts = 1;
+ while ( nUsedSorts < rSortParam.GetSortKeyCount() && rSortParam.maKeyState[nUsedSorts].bDoSort )
+ nUsedSorts++;
+ std::unique_ptr<ScSortInfoArray> pArray(new ScSortInfoArray( nUsedSorts, nInd1, nInd2 ));
+ pArray->SetKeepQuery(bKeepQuery);
+ pArray->SetUpdateRefs(bUpdateRefs);
+
+ if ( rSortParam.bByRow )
+ {
+ for ( sal_uInt16 nSort = 0; nSort < nUsedSorts; nSort++ )
+ {
+ SCCOL nCol = static_cast<SCCOL>(rSortParam.maKeyState[nSort].nField);
+ ScColumn* pCol = &aCol[nCol];
+ sc::ColumnBlockConstPosition aBlockPos;
+ pCol->InitBlockPosition(aBlockPos);
+ for ( SCROW nRow = nInd1; nRow <= nInd2; nRow++ )
+ {
+ ScSortInfo & rInfo = pArray->Get( nSort, nRow );
+ rInfo.maCell = pCol->GetCellValue(aBlockPos, nRow);
+ rInfo.nOrg = nRow;
+ }
+ }
+
+ CreateColumnIfNotExists(rSortParam.nCol2);
+ initDataRows( *pArray, *this, aCol, rSortParam.nCol1, nInd1, rSortParam.nCol2, nInd2, bKeepQuery,
+ rSortParam.aDataAreaExtras.mbCellFormats, true, true, false);
+ }
+ else
+ {
+ for ( sal_uInt16 nSort = 0; nSort < nUsedSorts; nSort++ )
+ {
+ SCROW nRow = rSortParam.maKeyState[nSort].nField;
+ for ( SCCOL nCol = static_cast<SCCOL>(nInd1);
+ nCol <= static_cast<SCCOL>(nInd2); nCol++ )
+ {
+ ScSortInfo & rInfo = pArray->Get( nSort, nCol );
+ rInfo.maCell = GetCellValue(nCol, nRow);
+ rInfo.nOrg = nCol;
+ }
+ }
+ }
+ return pArray;
+}
+
+namespace {
+
+struct SortedColumn
+{
+ typedef mdds::flat_segment_tree<SCROW, const ScPatternAttr*> PatRangeType;
+
+ sc::CellStoreType maCells;
+ sc::CellTextAttrStoreType maCellTextAttrs;
+ sc::BroadcasterStoreType maBroadcasters;
+ sc::CellNoteStoreType maCellNotes;
+ std::vector<std::vector<SdrObject*>> maCellDrawObjects;
+
+ PatRangeType maPatterns;
+ PatRangeType::const_iterator miPatternPos;
+
+ SortedColumn(const SortedColumn&) = delete;
+ const SortedColumn operator=(const SortedColumn&) = delete;
+
+ explicit SortedColumn( size_t nTopEmptyRows, const ScSheetLimits& rSheetLimits ) :
+ maCells(nTopEmptyRows),
+ maCellTextAttrs(nTopEmptyRows),
+ maBroadcasters(nTopEmptyRows),
+ maCellNotes(nTopEmptyRows),
+ maPatterns(0, rSheetLimits.GetMaxRowCount(), nullptr),
+ miPatternPos(maPatterns.begin()) {}
+
+ void setPattern( SCROW nRow, const ScPatternAttr* pPat )
+ {
+ miPatternPos = maPatterns.insert(miPatternPos, nRow, nRow+1, pPat).first;
+ }
+};
+
+struct SortedRowFlags
+{
+ typedef mdds::flat_segment_tree<SCROW,bool> FlagsType;
+
+ FlagsType maRowsHidden;
+ FlagsType maRowsFiltered;
+ FlagsType::const_iterator miPosHidden;
+ FlagsType::const_iterator miPosFiltered;
+
+ SortedRowFlags(const ScSheetLimits& rSheetLimits) :
+ maRowsHidden(0, rSheetLimits.GetMaxRowCount(), false),
+ maRowsFiltered(0, rSheetLimits.GetMaxRowCount(), false),
+ miPosHidden(maRowsHidden.begin()),
+ miPosFiltered(maRowsFiltered.begin()) {}
+
+ void setRowHidden( SCROW nRow, bool b )
+ {
+ miPosHidden = maRowsHidden.insert(miPosHidden, nRow, nRow+1, b).first;
+ }
+
+ void setRowFiltered( SCROW nRow, bool b )
+ {
+ miPosFiltered = maRowsFiltered.insert(miPosFiltered, nRow, nRow+1, b).first;
+ }
+
+ void swap( SortedRowFlags& r )
+ {
+ maRowsHidden.swap(r.maRowsHidden);
+ maRowsFiltered.swap(r.maRowsFiltered);
+
+ // Just reset the position hints.
+ miPosHidden = maRowsHidden.begin();
+ miPosFiltered = maRowsFiltered.begin();
+ }
+};
+
+struct PatternSpan
+{
+ SCROW mnRow1;
+ SCROW mnRow2;
+ const ScPatternAttr* mpPattern;
+
+ PatternSpan( SCROW nRow1, SCROW nRow2, const ScPatternAttr* pPat ) :
+ mnRow1(nRow1), mnRow2(nRow2), mpPattern(pPat) {}
+};
+
+}
+
+bool ScTable::IsSortCollatorGlobal() const
+{
+ return pSortCollator == &ScGlobal::GetCollator() ||
+ pSortCollator == &ScGlobal::GetCaseCollator();
+}
+
+void ScTable::InitSortCollator( const ScSortParam& rPar )
+{
+ if ( !rPar.aCollatorLocale.Language.isEmpty() )
+ {
+ if ( !pSortCollator || IsSortCollatorGlobal() )
+ pSortCollator = new CollatorWrapper( comphelper::getProcessComponentContext() );
+ pSortCollator->loadCollatorAlgorithm( rPar.aCollatorAlgorithm,
+ rPar.aCollatorLocale, (rPar.bCaseSens ? 0 : SC_COLLATOR_IGNORES) );
+ }
+ else
+ { // SYSTEM
+ DestroySortCollator();
+ pSortCollator = &ScGlobal::GetCollator(rPar.bCaseSens);
+ }
+}
+
+void ScTable::DestroySortCollator()
+{
+ if ( pSortCollator )
+ {
+ if ( !IsSortCollatorGlobal() )
+ delete pSortCollator;
+ pSortCollator = nullptr;
+ }
+}
+
+namespace {
+
+template<typename Hint, typename ReorderMap, typename Index>
+class ReorderNotifier
+{
+ Hint maHint;
+public:
+ ReorderNotifier( const ReorderMap& rMap, SCTAB nTab, Index nPos1, Index nPos2 ) :
+ maHint(rMap, nTab, nPos1, nPos2) {}
+
+ void operator() ( SvtListener* p )
+ {
+ p->Notify(maHint);
+ }
+};
+
+class FormulaGroupPosCollector
+{
+ sc::RefQueryFormulaGroup& mrQuery;
+
+public:
+ explicit FormulaGroupPosCollector( sc::RefQueryFormulaGroup& rQuery ) : mrQuery(rQuery) {}
+
+ void operator() ( const SvtListener* p )
+ {
+ p->Query(mrQuery);
+ }
+};
+
+void fillSortedColumnArray(
+ std::vector<std::unique_ptr<SortedColumn>>& rSortedCols,
+ SortedRowFlags& rRowFlags,
+ std::vector<SvtListener*>& rCellListeners,
+ ScSortInfoArray* pArray, SCTAB nTab, SCCOL nCol1, SCCOL nCol2, ScProgress* pProgress, const ScTable* pTable,
+ bool bOnlyDataAreaExtras )
+{
+ assert(!bOnlyDataAreaExtras || !pArray->IsUpdateRefs());
+
+ SCROW nRow1 = pArray->GetStart();
+ ScSortInfoArray::RowsType* pRows = pArray->GetDataRows();
+ std::vector<SCCOLROW> aOrderIndices = pArray->GetOrderIndices();
+
+ size_t nColCount = nCol2 - nCol1 + 1;
+ std::vector<std::unique_ptr<SortedColumn>> aSortedCols; // storage for copied cells.
+ SortedRowFlags aRowFlags(pTable->GetDoc().GetSheetLimits());
+ aSortedCols.reserve(nColCount);
+ for (size_t i = 0; i < nColCount; ++i)
+ {
+ // In the sorted column container, element positions and row
+ // positions must match, else formula cells may mis-behave during
+ // grouping.
+ aSortedCols.push_back(std::make_unique<SortedColumn>(nRow1, pTable->GetDoc().GetSheetLimits()));
+ }
+
+ for (size_t i = 0; i < pRows->size(); ++i)
+ {
+ const SCROW nRow = nRow1 + i;
+
+ ScSortInfoArray::Row& rRow = (*pRows)[i];
+ for (size_t j = 0; j < rRow.maCells.size(); ++j)
+ {
+ ScSortInfoArray::Cell& rCell = rRow.maCells[j];
+
+ // If bOnlyDataAreaExtras,
+ // sc::CellStoreType aSortedCols.at(j)->maCells
+ // and
+ // sc::CellTextAttrStoreType aSortedCols.at(j)->maCellTextAttrs
+ // are by definition all empty mdds::multi_type_vector, so nothing
+ // needs to be done to push *all* empty.
+
+ if (!bOnlyDataAreaExtras)
+ {
+ sc::CellStoreType& rCellStore = aSortedCols.at(j)->maCells;
+ switch (rCell.maCell.meType)
+ {
+ case CELLTYPE_STRING:
+ assert(rCell.mpAttr);
+ rCellStore.push_back(*rCell.maCell.mpString);
+ break;
+ case CELLTYPE_VALUE:
+ assert(rCell.mpAttr);
+ rCellStore.push_back(rCell.maCell.mfValue);
+ break;
+ case CELLTYPE_EDIT:
+ assert(rCell.mpAttr);
+ rCellStore.push_back(rCell.maCell.mpEditText->Clone().release());
+ break;
+ case CELLTYPE_FORMULA:
+ {
+ assert(rCell.mpAttr);
+ ScAddress aOldPos = rCell.maCell.mpFormula->aPos;
+
+ const ScAddress aCellPos(nCol1 + j, nRow, nTab);
+ ScFormulaCell* pNew = rCell.maCell.mpFormula->Clone( aCellPos );
+ if (pArray->IsUpdateRefs())
+ {
+ pNew->CopyAllBroadcasters(*rCell.maCell.mpFormula);
+ pNew->GetCode()->AdjustReferenceOnMovedOrigin(aOldPos, aCellPos);
+ }
+ else
+ {
+ pNew->GetCode()->AdjustReferenceOnMovedOriginIfOtherSheet(aOldPos, aCellPos);
+ }
+
+ if (!rCellListeners.empty())
+ {
+ // Original source cells will be deleted during
+ // sc::CellStoreType::transfer(), SvtListener is a base
+ // class, so we need to replace it.
+ auto it( ::std::find( rCellListeners.begin(), rCellListeners.end(), rCell.maCell.mpFormula));
+ if (it != rCellListeners.end())
+ *it = pNew;
+ }
+
+ rCellStore.push_back(pNew);
+ }
+ break;
+ default:
+ //assert(!rCell.mpAttr);
+ // This assert doesn't hold, for example
+ // CopyCellsFromClipHandler may omit copying cells during
+ // PasteSpecial for which CopyTextAttrsFromClipHandler
+ // still copies a CellTextAttr. So if that really is not
+ // expected then fix it there.
+ rCellStore.push_back_empty();
+ }
+
+ sc::CellTextAttrStoreType& rAttrStore = aSortedCols.at(j)->maCellTextAttrs;
+ if (rCell.mpAttr)
+ rAttrStore.push_back(*rCell.mpAttr);
+ else
+ rAttrStore.push_back_empty();
+ }
+
+ if (pArray->IsUpdateRefs())
+ {
+ // At this point each broadcaster instance is managed by 2
+ // containers. We will release those in the original storage
+ // below before transferring them to the document.
+ const SvtBroadcaster* pBroadcaster = pTable->GetBroadcaster( nCol1 + j, aOrderIndices[i]);
+ sc::BroadcasterStoreType& rBCStore = aSortedCols.at(j)->maBroadcasters;
+ if (pBroadcaster)
+ // A const pointer would be implicitly converted to a bool type.
+ rBCStore.push_back(const_cast<SvtBroadcaster*>(pBroadcaster));
+ else
+ rBCStore.push_back_empty();
+ }
+
+ // The same with cell note instances ...
+ sc::CellNoteStoreType& rNoteStore = aSortedCols.at(j)->maCellNotes;
+ if (rCell.mpNote)
+ rNoteStore.push_back(const_cast<ScPostIt*>(rCell.mpNote));
+ else
+ rNoteStore.push_back_empty();
+
+ // Add cell anchored images
+ aSortedCols.at(j)->maCellDrawObjects.push_back(rCell.maDrawObjects);
+
+ if (rCell.mpPattern)
+ aSortedCols.at(j)->setPattern(nRow, rCell.mpPattern);
+ }
+
+ if (!bOnlyDataAreaExtras && pArray->IsKeepQuery())
+ {
+ // Hidden and filtered flags are first converted to segments.
+ aRowFlags.setRowHidden(nRow, rRow.mbHidden);
+ aRowFlags.setRowFiltered(nRow, rRow.mbFiltered);
+ }
+
+ if (pProgress)
+ pProgress->SetStateOnPercent(i);
+ }
+
+ rSortedCols.swap(aSortedCols);
+ rRowFlags.swap(aRowFlags);
+}
+
+void expandRowRange( ScRange& rRange, SCROW nTop, SCROW nBottom )
+{
+ if (nTop < rRange.aStart.Row())
+ rRange.aStart.SetRow(nTop);
+
+ if (rRange.aEnd.Row() < nBottom)
+ rRange.aEnd.SetRow(nBottom);
+}
+
+class FormulaCellCollectAction : public sc::ColumnSpanSet::ColumnAction
+{
+ std::vector<ScFormulaCell*>& mrCells;
+ ScColumn* mpCol;
+
+public:
+ explicit FormulaCellCollectAction( std::vector<ScFormulaCell*>& rCells ) :
+ mrCells(rCells), mpCol(nullptr) {}
+
+ virtual void startColumn( ScColumn* pCol ) override
+ {
+ mpCol = pCol;
+ }
+
+ virtual void execute( SCROW nRow1, SCROW nRow2, bool bVal ) override
+ {
+ assert(mpCol);
+
+ if (!bVal)
+ return;
+
+ mpCol->CollectFormulaCells(mrCells, nRow1, nRow2);
+ }
+};
+
+class ListenerStartAction : public sc::ColumnSpanSet::ColumnAction
+{
+ ScColumn* mpCol;
+
+ std::shared_ptr<sc::ColumnBlockPositionSet> mpPosSet;
+ sc::StartListeningContext maStartCxt;
+ sc::EndListeningContext maEndCxt;
+
+public:
+ explicit ListenerStartAction( ScDocument& rDoc ) :
+ mpCol(nullptr),
+ mpPosSet(std::make_shared<sc::ColumnBlockPositionSet>(rDoc)),
+ maStartCxt(rDoc, mpPosSet),
+ maEndCxt(rDoc, mpPosSet) {}
+
+ virtual void startColumn( ScColumn* pCol ) override
+ {
+ mpCol = pCol;
+ }
+
+ virtual void execute( SCROW nRow1, SCROW nRow2, bool bVal ) override
+ {
+ assert(mpCol);
+
+ if (!bVal)
+ return;
+
+ mpCol->StartListeningFormulaCells(maStartCxt, maEndCxt, nRow1, nRow2);
+ }
+};
+
+}
+
+void ScTable::SortReorderAreaExtrasByRow( ScSortInfoArray* pArray,
+ SCCOL nDataCol1, SCCOL nDataCol2,
+ const ScDataAreaExtras& rDataAreaExtras, ScProgress* pProgress )
+{
+ const SCROW nRow1 = pArray->GetStart();
+ const SCROW nLastRow = pArray->GetLast();
+ const SCCOL nChunkCols = std::max<SCCOL>( 1, kSortCellsChunk / (nLastRow - nRow1 + 1));
+ // Before data area.
+ for (SCCOL nCol = rDataAreaExtras.mnStartCol; nCol < nDataCol1; nCol += nChunkCols)
+ {
+ const SCCOL nEndCol = std::min<SCCOL>( nCol + nChunkCols - 1, nDataCol1 - 1);
+ CreateColumnIfNotExists(nEndCol);
+ initDataRows( *pArray, *this, aCol, nCol, nRow1, nEndCol, nLastRow, false,
+ rDataAreaExtras.mbCellFormats, rDataAreaExtras.mbCellNotes, rDataAreaExtras.mbCellDrawObjects, true);
+ SortReorderByRow( pArray, nCol, nEndCol, pProgress, true);
+ }
+ // Behind data area.
+ for (SCCOL nCol = nDataCol2 + 1; nCol <= rDataAreaExtras.mnEndCol; nCol += nChunkCols)
+ {
+ const SCCOL nEndCol = std::min<SCCOL>( nCol + nChunkCols - 1, rDataAreaExtras.mnEndCol);
+ CreateColumnIfNotExists(nEndCol);
+ initDataRows( *pArray, *this, aCol, nCol, nRow1, nEndCol, nLastRow, false,
+ rDataAreaExtras.mbCellFormats, rDataAreaExtras.mbCellNotes, rDataAreaExtras.mbCellDrawObjects, true);
+ SortReorderByRow( pArray, nCol, nEndCol, pProgress, true);
+ }
+}
+
+void ScTable::SortReorderAreaExtrasByColumn( const ScSortInfoArray* pArray,
+ SCROW nDataRow1, SCROW nDataRow2, const ScDataAreaExtras& rDataAreaExtras, ScProgress* pProgress )
+{
+ const SCCOL nCol1 = static_cast<SCCOL>(pArray->GetStart());
+ const SCCOL nLastCol = static_cast<SCCOL>(pArray->GetLast());
+ const SCROW nChunkRows = std::max<SCROW>( 1, kSortCellsChunk / (nLastCol - nCol1 + 1));
+ // Above data area.
+ for (SCROW nRow = rDataAreaExtras.mnStartRow; nRow < nDataRow1; nRow += nChunkRows)
+ {
+ const SCROW nEndRow = std::min<SCROW>( nRow + nChunkRows - 1, nDataRow1 - 1);
+ SortReorderByColumn( pArray, nRow, nEndRow, rDataAreaExtras.mbCellFormats, pProgress);
+ }
+ // Below data area.
+ for (SCROW nRow = nDataRow2 + 1; nRow <= rDataAreaExtras.mnEndRow; nRow += nChunkRows)
+ {
+ const SCROW nEndRow = std::min<SCROW>( nRow + nChunkRows - 1, rDataAreaExtras.mnEndRow);
+ SortReorderByColumn( pArray, nRow, nEndRow, rDataAreaExtras.mbCellFormats, pProgress);
+ }
+}
+
+void ScTable::SortReorderByColumn(
+ const ScSortInfoArray* pArray, SCROW nRow1, SCROW nRow2, bool bPattern, ScProgress* pProgress )
+{
+ SCCOLROW nStart = pArray->GetStart();
+ SCCOLROW nLast = pArray->GetLast();
+
+ std::vector<SCCOLROW> aIndices = pArray->GetOrderIndices();
+ size_t nCount = aIndices.size();
+
+ // Cut formula grouping at row and reference boundaries before the reordering.
+ ScRange aSortRange(nStart, nRow1, nTab, nLast, nRow2, nTab);
+ for (SCCOL nCol = nStart; nCol <= static_cast<SCCOL>(nLast); ++nCol)
+ aCol[nCol].SplitFormulaGroupByRelativeRef(aSortRange);
+
+ // Collect all listeners of cell broadcasters of sorted range.
+ std::vector<SvtListener*> aCellListeners;
+
+ if (!pArray->IsUpdateRefs())
+ {
+ // Collect listeners of cell broadcasters.
+ for (SCCOL nCol = nStart; nCol <= static_cast<SCCOL>(nLast); ++nCol)
+ aCol[nCol].CollectListeners(aCellListeners, nRow1, nRow2);
+
+ // Remove any duplicate listener entries. We must ensure that we
+ // notify each unique listener only once.
+ std::sort(aCellListeners.begin(), aCellListeners.end());
+ aCellListeners.erase(std::unique(aCellListeners.begin(), aCellListeners.end()), aCellListeners.end());
+
+ // Notify the cells' listeners to stop listening.
+ /* TODO: for performance this could be enhanced to stop and later
+ * restart only listening to within the reordered range and keep
+ * listening to everything outside untouched. */
+ sc::RefStopListeningHint aHint;
+ for (auto const & l : aCellListeners)
+ l->Notify(aHint);
+ }
+
+ // table to keep track of column index to position in the index table.
+ std::vector<SCCOLROW> aPosTable(nCount);
+ for (size_t i = 0; i < nCount; ++i)
+ aPosTable[aIndices[i]-nStart] = i;
+
+ SCCOLROW nDest = nStart;
+ for (size_t i = 0; i < nCount; ++i, ++nDest)
+ {
+ SCCOLROW nSrc = aIndices[i];
+ if (nDest != nSrc)
+ {
+ aCol[nDest].Swap(aCol[nSrc], nRow1, nRow2, bPattern);
+
+ // Update the position of the index that was originally equal to nDest.
+ size_t nPos = aPosTable[nDest-nStart];
+ aIndices[nPos] = nSrc;
+ aPosTable[nSrc-nStart] = nPos;
+ }
+
+ if (pProgress)
+ pProgress->SetStateOnPercent(i);
+ }
+
+ // Reset formula cell positions which became out-of-sync after column reordering.
+ bool bUpdateRefs = pArray->IsUpdateRefs();
+ for (SCCOL nCol = nStart; nCol <= static_cast<SCCOL>(nLast); ++nCol)
+ aCol[nCol].ResetFormulaCellPositions(nRow1, nRow2, bUpdateRefs);
+
+ if (pArray->IsUpdateRefs())
+ {
+ // Set up column reorder map (for later broadcasting of reference updates).
+ sc::ColRowReorderMapType aColMap;
+ const std::vector<SCCOLROW>& rOldIndices = pArray->GetOrderIndices();
+ for (size_t i = 0, n = rOldIndices.size(); i < n; ++i)
+ {
+ SCCOL nNew = i + nStart;
+ SCCOL nOld = rOldIndices[i];
+ aColMap.emplace(nOld, nNew);
+ }
+
+ // Collect all listeners within sorted range ahead of time.
+ std::vector<SvtListener*> aListeners;
+
+ for (SCCOL nCol = nStart; nCol <= static_cast<SCCOL>(nLast); ++nCol)
+ aCol[nCol].CollectListeners(aListeners, nRow1, nRow2);
+
+ // Get all area listeners that listen on one column within the range
+ // and end their listening.
+ ScRange aMoveRange( nStart, nRow1, nTab, nLast, nRow2, nTab);
+ std::vector<sc::AreaListener> aAreaListeners = rDocument.GetBASM()->GetAllListeners(
+ aMoveRange, sc::AreaOverlapType::OneColumnInside);
+ {
+ for (auto& rAreaListener : aAreaListeners)
+ {
+ rDocument.EndListeningArea(rAreaListener.maArea, rAreaListener.mbGroupListening, rAreaListener.mpListener);
+ aListeners.push_back( rAreaListener.mpListener);
+ }
+ }
+
+ // Remove any duplicate listener entries and notify all listeners
+ // afterward. We must ensure that we notify each unique listener only
+ // once.
+ std::sort(aListeners.begin(), aListeners.end());
+ aListeners.erase(std::unique(aListeners.begin(), aListeners.end()), aListeners.end());
+ ReorderNotifier<sc::RefColReorderHint, sc::ColRowReorderMapType, SCCOL> aFunc(aColMap, nTab, nRow1, nRow2);
+ std::for_each(aListeners.begin(), aListeners.end(), aFunc);
+
+ // Re-start area listeners on the reordered columns.
+ {
+ for (auto& rAreaListener : aAreaListeners)
+ {
+ ScRange aNewRange = rAreaListener.maArea;
+ sc::ColRowReorderMapType::const_iterator itCol = aColMap.find( aNewRange.aStart.Col());
+ if (itCol != aColMap.end())
+ {
+ aNewRange.aStart.SetCol( itCol->second);
+ aNewRange.aEnd.SetCol( itCol->second);
+ }
+ rDocument.StartListeningArea(aNewRange, rAreaListener.mbGroupListening, rAreaListener.mpListener);
+ }
+ }
+ }
+ else // !(pArray->IsUpdateRefs())
+ {
+ // Notify the cells' listeners to (re-)start listening.
+ sc::RefStartListeningHint aHint;
+ for (auto const & l : aCellListeners)
+ l->Notify(aHint);
+ }
+
+ // Re-join formulas at row boundaries now that all the references have
+ // been adjusted for column reordering.
+ for (SCCOL nCol = nStart; nCol <= static_cast<SCCOL>(nLast); ++nCol)
+ {
+ sc::CellStoreType& rCells = aCol[nCol].maCells;
+ sc::CellStoreType::position_type aPos = rCells.position(nRow1);
+ sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
+ if (nRow2 < rDocument.MaxRow())
+ {
+ aPos = rCells.position(aPos.first, nRow2+1);
+ sc::SharedFormulaUtil::joinFormulaCellAbove(aPos);
+ }
+ }
+}
+
+void ScTable::SortReorderByRow( ScSortInfoArray* pArray, SCCOL nCol1, SCCOL nCol2,
+ ScProgress* pProgress, bool bOnlyDataAreaExtras )
+{
+ assert(!pArray->IsUpdateRefs());
+
+ if (nCol2 < nCol1)
+ return;
+
+ // bOnlyDataAreaExtras:
+ // Data area extras by definition do not have any cell content so no
+ // formula cells either, so that handling doesn't need to be executed.
+ // However, there may be listeners of formulas listening to broadcasters of
+ // empty cells.
+
+ SCROW nRow1 = pArray->GetStart();
+ SCROW nRow2 = pArray->GetLast();
+
+ // Collect all listeners of cell broadcasters of sorted range.
+ std::vector<SvtListener*> aCellListeners;
+
+ // When the update ref mode is disabled, we need to detach all formula
+ // cells in the sorted range before reordering, and re-start them
+ // afterward.
+ if (!bOnlyDataAreaExtras)
+ {
+ sc::EndListeningContext aCxt(rDocument);
+ DetachFormulaCells(aCxt, nCol1, nRow1, nCol2, nRow2);
+ }
+
+ // Collect listeners of cell broadcasters.
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ aCol[nCol].CollectListeners(aCellListeners, nRow1, nRow2);
+
+ // Remove any duplicate listener entries. We must ensure that we notify
+ // each unique listener only once.
+ std::sort(aCellListeners.begin(), aCellListeners.end());
+ aCellListeners.erase(std::unique(aCellListeners.begin(), aCellListeners.end()), aCellListeners.end());
+
+ // Notify the cells' listeners to stop listening.
+ /* TODO: for performance this could be enhanced to stop and later
+ * restart only listening to within the reordered range and keep
+ * listening to everything outside untouched. */
+ {
+ sc::RefStopListeningHint aHint;
+ for (auto const & l : aCellListeners)
+ l->Notify(aHint);
+ }
+
+ // Split formula groups at the sort range boundaries (if applicable).
+ if (!bOnlyDataAreaExtras)
+ {
+ std::vector<SCROW> aRowBounds
+ {
+ nRow1,
+ nRow2+1
+ };
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ SplitFormulaGroups(nCol, aRowBounds);
+ }
+
+ // Cells in the data rows only reference values in the document. Make
+ // a copy before updating the document.
+ std::vector<std::unique_ptr<SortedColumn>> aSortedCols; // storage for copied cells.
+ SortedRowFlags aRowFlags(GetDoc().GetSheetLimits());
+ fillSortedColumnArray(aSortedCols, aRowFlags, aCellListeners, pArray, nTab, nCol1, nCol2,
+ pProgress, this, bOnlyDataAreaExtras);
+
+ for (size_t i = 0, n = aSortedCols.size(); i < n; ++i)
+ {
+ SCCOL nThisCol = i + nCol1;
+
+ if (!bOnlyDataAreaExtras)
+ {
+ {
+ sc::CellStoreType& rDest = aCol[nThisCol].maCells;
+ sc::CellStoreType& rSrc = aSortedCols[i]->maCells;
+ rSrc.transfer(nRow1, nRow2, rDest, nRow1);
+ }
+
+ {
+ sc::CellTextAttrStoreType& rDest = aCol[nThisCol].maCellTextAttrs;
+ sc::CellTextAttrStoreType& rSrc = aSortedCols[i]->maCellTextAttrs;
+ rSrc.transfer(nRow1, nRow2, rDest, nRow1);
+ }
+ }
+
+ {
+ sc::CellNoteStoreType& rSrc = aSortedCols[i]->maCellNotes;
+ sc::CellNoteStoreType& rDest = aCol[nThisCol].maCellNotes;
+
+ // Do the same as broadcaster storage transfer (to prevent double deletion).
+ rDest.release_range(nRow1, nRow2);
+ rSrc.transfer(nRow1, nRow2, rDest, nRow1);
+ aCol[nThisCol].UpdateNoteCaptions(nRow1, nRow2);
+ }
+
+ // Update draw object positions
+ aCol[nThisCol].UpdateDrawObjects(aSortedCols[i]->maCellDrawObjects, nRow1, nRow2);
+
+ {
+ // Get all row spans where the pattern is not NULL.
+ std::vector<PatternSpan> aSpans =
+ sc::toSpanArrayWithValue<SCROW,const ScPatternAttr*,PatternSpan>(
+ aSortedCols[i]->maPatterns);
+
+ for (const auto& rSpan : aSpans)
+ {
+ assert(rSpan.mpPattern); // should never be NULL.
+ rDocument.GetPool()->Put(*rSpan.mpPattern);
+ }
+
+ for (const auto& rSpan : aSpans)
+ {
+ aCol[nThisCol].SetPatternArea(rSpan.mnRow1, rSpan.mnRow2, *rSpan.mpPattern);
+ rDocument.GetPool()->Remove(*rSpan.mpPattern);
+ }
+ }
+
+ aCol[nThisCol].CellStorageModified();
+ }
+
+ if (!bOnlyDataAreaExtras && pArray->IsKeepQuery())
+ {
+ aRowFlags.maRowsHidden.build_tree();
+ aRowFlags.maRowsFiltered.build_tree();
+
+ // Remove all flags in the range first.
+ SetRowHidden(nRow1, nRow2, false);
+ SetRowFiltered(nRow1, nRow2, false);
+
+ std::vector<sc::RowSpan> aSpans =
+ sc::toSpanArray<SCROW,sc::RowSpan>(aRowFlags.maRowsHidden, nRow1);
+
+ for (const auto& rSpan : aSpans)
+ SetRowHidden(rSpan.mnRow1, rSpan.mnRow2, true);
+
+ aSpans = sc::toSpanArray<SCROW,sc::RowSpan>(aRowFlags.maRowsFiltered, nRow1);
+
+ for (const auto& rSpan : aSpans)
+ SetRowFiltered(rSpan.mnRow1, rSpan.mnRow2, true);
+ }
+
+ // Notify the cells' listeners to (re-)start listening.
+ {
+ sc::RefStartListeningHint aHint;
+ for (auto const & l : aCellListeners)
+ l->Notify(aHint);
+ }
+
+ if (!bOnlyDataAreaExtras)
+ {
+ // Re-group columns in the sorted range too.
+ for (SCCOL i = nCol1; i <= nCol2; ++i)
+ aCol[i].RegroupFormulaCells();
+
+ {
+ sc::StartListeningContext aCxt(rDocument);
+ AttachFormulaCells(aCxt, nCol1, nRow1, nCol2, nRow2);
+ }
+ }
+}
+
+void ScTable::SortReorderByRowRefUpdate(
+ ScSortInfoArray* pArray, SCCOL nCol1, SCCOL nCol2, ScProgress* pProgress )
+{
+ assert(pArray->IsUpdateRefs());
+
+ if (nCol2 < nCol1)
+ return;
+
+ SCROW nRow1 = pArray->GetStart();
+ SCROW nRow2 = pArray->GetLast();
+
+ ScRange aMoveRange( nCol1, nRow1, nTab, nCol2, nRow2, nTab);
+ sc::ColumnSpanSet aGrpListenerRanges;
+
+ {
+ // Get the range of formula group listeners within sorted range (if any).
+ sc::QueryRange aQuery;
+
+ ScBroadcastAreaSlotMachine* pBASM = rDocument.GetBASM();
+ std::vector<sc::AreaListener> aGrpListeners =
+ pBASM->GetAllListeners(
+ aMoveRange, sc::AreaOverlapType::InsideOrOverlap, sc::ListenerGroupType::Group);
+
+ {
+ for (const auto& rGrpListener : aGrpListeners)
+ {
+ assert(rGrpListener.mbGroupListening);
+ SvtListener* pGrpLis = rGrpListener.mpListener;
+ pGrpLis->Query(aQuery);
+ rDocument.EndListeningArea(rGrpListener.maArea, rGrpListener.mbGroupListening, pGrpLis);
+ }
+ }
+
+ ScRangeList aTmp;
+ aQuery.swapRanges(aTmp);
+
+ // If the range is within the sorted range, we need to expand its rows
+ // to the top and bottom of the sorted range, since the formula cells
+ // could be anywhere in the sorted range after reordering.
+ for (size_t i = 0, n = aTmp.size(); i < n; ++i)
+ {
+ ScRange aRange = aTmp[i];
+ if (!aMoveRange.Intersects(aRange))
+ {
+ // Doesn't overlap with the sorted range at all.
+ aGrpListenerRanges.set(GetDoc(), aRange, true);
+ continue;
+ }
+
+ if (aMoveRange.aStart.Col() <= aRange.aStart.Col() && aRange.aEnd.Col() <= aMoveRange.aEnd.Col())
+ {
+ // Its column range is within the column range of the sorted range.
+ expandRowRange(aRange, aMoveRange.aStart.Row(), aMoveRange.aEnd.Row());
+ aGrpListenerRanges.set(GetDoc(), aRange, true);
+ continue;
+ }
+
+ // It intersects with the sorted range, but its column range is
+ // not within the column range of the sorted range. Split it into
+ // 2 ranges.
+ ScRange aR1 = aRange;
+ ScRange aR2 = aRange;
+ if (aRange.aStart.Col() < aMoveRange.aStart.Col())
+ {
+ // Left half is outside the sorted range while the right half is inside.
+ aR1.aEnd.SetCol(aMoveRange.aStart.Col()-1);
+ aR2.aStart.SetCol(aMoveRange.aStart.Col());
+ expandRowRange(aR2, aMoveRange.aStart.Row(), aMoveRange.aEnd.Row());
+ }
+ else
+ {
+ // Left half is inside the sorted range while the right half is outside.
+ aR1.aEnd.SetCol(aMoveRange.aEnd.Col()-1);
+ aR2.aStart.SetCol(aMoveRange.aEnd.Col());
+ expandRowRange(aR1, aMoveRange.aStart.Row(), aMoveRange.aEnd.Row());
+ }
+
+ aGrpListenerRanges.set(GetDoc(), aR1, true);
+ aGrpListenerRanges.set(GetDoc(), aR2, true);
+ }
+ }
+
+ // Split formula groups at the sort range boundaries (if applicable).
+ std::vector<SCROW> aRowBounds
+ {
+ nRow1,
+ nRow2+1
+ };
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ SplitFormulaGroups(nCol, aRowBounds);
+
+ // Cells in the data rows only reference values in the document. Make
+ // a copy before updating the document.
+ std::vector<std::unique_ptr<SortedColumn>> aSortedCols; // storage for copied cells.
+ SortedRowFlags aRowFlags(GetDoc().GetSheetLimits());
+ std::vector<SvtListener*> aListenersDummy;
+ fillSortedColumnArray(aSortedCols, aRowFlags, aListenersDummy, pArray, nTab, nCol1, nCol2, pProgress, this, false);
+
+ for (size_t i = 0, n = aSortedCols.size(); i < n; ++i)
+ {
+ SCCOL nThisCol = i + nCol1;
+
+ {
+ sc::CellStoreType& rDest = aCol[nThisCol].maCells;
+ sc::CellStoreType& rSrc = aSortedCols[i]->maCells;
+ rSrc.transfer(nRow1, nRow2, rDest, nRow1);
+ }
+
+ {
+ sc::CellTextAttrStoreType& rDest = aCol[nThisCol].maCellTextAttrs;
+ sc::CellTextAttrStoreType& rSrc = aSortedCols[i]->maCellTextAttrs;
+ rSrc.transfer(nRow1, nRow2, rDest, nRow1);
+ }
+
+ {
+ sc::BroadcasterStoreType& rSrc = aSortedCols[i]->maBroadcasters;
+ sc::BroadcasterStoreType& rDest = aCol[nThisCol].maBroadcasters;
+
+ // Release current broadcasters first, to prevent them from getting deleted.
+ rDest.release_range(nRow1, nRow2);
+
+ // Transfer sorted broadcaster segment to the document.
+ rSrc.transfer(nRow1, nRow2, rDest, nRow1);
+ }
+
+ {
+ sc::CellNoteStoreType& rSrc = aSortedCols[i]->maCellNotes;
+ sc::CellNoteStoreType& rDest = aCol[nThisCol].maCellNotes;
+
+ // Do the same as broadcaster storage transfer (to prevent double deletion).
+ rDest.release_range(nRow1, nRow2);
+ rSrc.transfer(nRow1, nRow2, rDest, nRow1);
+ aCol[nThisCol].UpdateNoteCaptions(nRow1, nRow2);
+ }
+
+ // Update draw object positions
+ aCol[nThisCol].UpdateDrawObjects(aSortedCols[i]->maCellDrawObjects, nRow1, nRow2);
+
+ {
+ // Get all row spans where the pattern is not NULL.
+ std::vector<PatternSpan> aSpans =
+ sc::toSpanArrayWithValue<SCROW,const ScPatternAttr*,PatternSpan>(
+ aSortedCols[i]->maPatterns);
+
+ for (const auto& rSpan : aSpans)
+ {
+ assert(rSpan.mpPattern); // should never be NULL.
+ rDocument.GetPool()->Put(*rSpan.mpPattern);
+ }
+
+ for (const auto& rSpan : aSpans)
+ {
+ aCol[nThisCol].SetPatternArea(rSpan.mnRow1, rSpan.mnRow2, *rSpan.mpPattern);
+ rDocument.GetPool()->Remove(*rSpan.mpPattern);
+ }
+ }
+
+ aCol[nThisCol].CellStorageModified();
+ }
+
+ if (pArray->IsKeepQuery())
+ {
+ aRowFlags.maRowsHidden.build_tree();
+ aRowFlags.maRowsFiltered.build_tree();
+
+ // Remove all flags in the range first.
+ SetRowHidden(nRow1, nRow2, false);
+ SetRowFiltered(nRow1, nRow2, false);
+
+ std::vector<sc::RowSpan> aSpans =
+ sc::toSpanArray<SCROW,sc::RowSpan>(aRowFlags.maRowsHidden, nRow1);
+
+ for (const auto& rSpan : aSpans)
+ SetRowHidden(rSpan.mnRow1, rSpan.mnRow2, true);
+
+ aSpans = sc::toSpanArray<SCROW,sc::RowSpan>(aRowFlags.maRowsFiltered, nRow1);
+
+ for (const auto& rSpan : aSpans)
+ SetRowFiltered(rSpan.mnRow1, rSpan.mnRow2, true);
+ }
+
+ // Set up row reorder map (for later broadcasting of reference updates).
+ sc::ColRowReorderMapType aRowMap;
+ const std::vector<SCCOLROW>& rOldIndices = pArray->GetOrderIndices();
+ for (size_t i = 0, n = rOldIndices.size(); i < n; ++i)
+ {
+ SCROW nNew = i + nRow1;
+ SCROW nOld = rOldIndices[i];
+ aRowMap.emplace(nOld, nNew);
+ }
+
+ // Collect all listeners within sorted range ahead of time.
+ std::vector<SvtListener*> aListeners;
+
+ // Collect listeners of cell broadcasters.
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ aCol[nCol].CollectListeners(aListeners, nRow1, nRow2);
+
+ // Get all area listeners that listen on one row within the range and end
+ // their listening.
+ std::vector<sc::AreaListener> aAreaListeners = rDocument.GetBASM()->GetAllListeners(
+ aMoveRange, sc::AreaOverlapType::OneRowInside);
+ {
+ for (auto& rAreaListener : aAreaListeners)
+ {
+ rDocument.EndListeningArea(rAreaListener.maArea, rAreaListener.mbGroupListening, rAreaListener.mpListener);
+ aListeners.push_back( rAreaListener.mpListener);
+ }
+ }
+
+ {
+ // Get all formula cells from the former group area listener ranges.
+
+ std::vector<ScFormulaCell*> aFCells;
+ FormulaCellCollectAction aAction(aFCells);
+ aGrpListenerRanges.executeColumnAction(rDocument, aAction);
+
+ aListeners.insert( aListeners.end(), aFCells.begin(), aFCells.end() );
+ }
+
+ // Remove any duplicate listener entries. We must ensure that we notify
+ // each unique listener only once.
+ std::sort(aListeners.begin(), aListeners.end());
+ aListeners.erase(std::unique(aListeners.begin(), aListeners.end()), aListeners.end());
+
+ // Collect positions of all shared formula cells outside the sorted range,
+ // and make them unshared before notifying them.
+ sc::RefQueryFormulaGroup aFormulaGroupPos;
+ aFormulaGroupPos.setSkipRange(ScRange(nCol1, nRow1, nTab, nCol2, nRow2, nTab));
+
+ std::for_each(aListeners.begin(), aListeners.end(), FormulaGroupPosCollector(aFormulaGroupPos));
+ const sc::RefQueryFormulaGroup::TabsType& rGroupTabs = aFormulaGroupPos.getAllPositions();
+ for (const auto& [rTab, rCols] : rGroupTabs)
+ {
+ for (const auto& [nCol, rCol] : rCols)
+ {
+ std::vector<SCROW> aBounds(rCol);
+ rDocument.UnshareFormulaCells(rTab, nCol, aBounds);
+ }
+ }
+
+ // Notify the listeners to update their references.
+ ReorderNotifier<sc::RefRowReorderHint, sc::ColRowReorderMapType, SCROW> aFunc(aRowMap, nTab, nCol1, nCol2);
+ std::for_each(aListeners.begin(), aListeners.end(), aFunc);
+
+ // Re-group formulas in affected columns.
+ for (const auto& [rTab, rCols] : rGroupTabs)
+ {
+ for (const auto& rEntry : rCols)
+ rDocument.RegroupFormulaCells(rTab, rEntry.first);
+ }
+
+ // Re-start area listeners on the reordered rows.
+ for (const auto& rAreaListener : aAreaListeners)
+ {
+ ScRange aNewRange = rAreaListener.maArea;
+ sc::ColRowReorderMapType::const_iterator itRow = aRowMap.find( aNewRange.aStart.Row());
+ if (itRow != aRowMap.end())
+ {
+ aNewRange.aStart.SetRow( itRow->second);
+ aNewRange.aEnd.SetRow( itRow->second);
+ }
+ rDocument.StartListeningArea(aNewRange, rAreaListener.mbGroupListening, rAreaListener.mpListener);
+ }
+
+ // Re-group columns in the sorted range too.
+ for (SCCOL i = nCol1; i <= nCol2; ++i)
+ aCol[i].RegroupFormulaCells();
+
+ {
+ // Re-start area listeners on the old group listener ranges.
+ ListenerStartAction aAction(rDocument);
+ aGrpListenerRanges.executeColumnAction(rDocument, aAction);
+ }
+}
+
+short ScTable::CompareCell(
+ sal_uInt16 nSort,
+ ScRefCellValue& rCell1, SCCOL nCell1Col, SCROW nCell1Row,
+ ScRefCellValue& rCell2, SCCOL nCell2Col, SCROW nCell2Row ) const
+{
+ short nRes = 0;
+
+ CellType eType1 = rCell1.meType, eType2 = rCell2.meType;
+
+ if (!rCell1.isEmpty())
+ {
+ if (!rCell2.isEmpty())
+ {
+ bool bErr1 = false;
+ bool bStr1 = ( eType1 != CELLTYPE_VALUE );
+ if (eType1 == CELLTYPE_FORMULA)
+ {
+ if (rCell1.mpFormula->GetErrCode() != FormulaError::NONE)
+ {
+ bErr1 = true;
+ bStr1 = false;
+ }
+ else if (rCell1.mpFormula->IsValue())
+ {
+ bStr1 = false;
+ }
+ }
+
+ bool bErr2 = false;
+ bool bStr2 = ( eType2 != CELLTYPE_VALUE );
+ if (eType2 == CELLTYPE_FORMULA)
+ {
+ if (rCell2.mpFormula->GetErrCode() != FormulaError::NONE)
+ {
+ bErr2 = true;
+ bStr2 = false;
+ }
+ else if (rCell2.mpFormula->IsValue())
+ {
+ bStr2 = false;
+ }
+ }
+
+ if ( bStr1 && bStr2 ) // only compare strings as strings!
+ {
+ OUString aStr1;
+ OUString aStr2;
+ if (eType1 == CELLTYPE_STRING)
+ aStr1 = rCell1.mpString->getString();
+ else
+ aStr1 = GetString(nCell1Col, nCell1Row);
+ if (eType2 == CELLTYPE_STRING)
+ aStr2 = rCell2.mpString->getString();
+ else
+ aStr2 = GetString(nCell2Col, nCell2Row);
+
+ bool bUserDef = aSortParam.bUserDef; // custom sort order
+ bool bNaturalSort = aSortParam.bNaturalSort; // natural sort
+ bool bCaseSens = aSortParam.bCaseSens; // case sensitivity
+
+ ScUserList* pList = ScGlobal::GetUserList();
+ if (bUserDef && pList && pList->size() > aSortParam.nUserIndex )
+ {
+ const ScUserListData& rData = (*pList)[aSortParam.nUserIndex];
+
+ if ( bNaturalSort )
+ nRes = naturalsort::Compare( aStr1, aStr2, bCaseSens, &rData, pSortCollator );
+ else
+ {
+ if ( bCaseSens )
+ nRes = sal::static_int_cast<short>( rData.Compare(aStr1, aStr2) );
+ else
+ nRes = sal::static_int_cast<short>( rData.ICompare(aStr1, aStr2) );
+ }
+
+ }
+ if (!bUserDef)
+ {
+ if ( bNaturalSort )
+ nRes = naturalsort::Compare( aStr1, aStr2, bCaseSens, nullptr, pSortCollator );
+ else
+ nRes = static_cast<short>( pSortCollator->compareString( aStr1, aStr2 ) );
+ }
+ }
+ else if ( bStr1 ) // String <-> Number or Error
+ {
+ if (bErr2)
+ nRes = -1; // String in front of Error
+ else
+ nRes = 1; // Number in front of String
+ }
+ else if ( bStr2 ) // Number or Error <-> String
+ {
+ if (bErr1)
+ nRes = 1; // String in front of Error
+ else
+ nRes = -1; // Number in front of String
+ }
+ else if (bErr1 && bErr2)
+ {
+ // nothing, two Errors are equal
+ }
+ else if (bErr1) // Error <-> Number
+ {
+ nRes = 1; // Number in front of Error
+ }
+ else if (bErr2) // Number <-> Error
+ {
+ nRes = -1; // Number in front of Error
+ }
+ else // Mixed numbers
+ {
+ double nVal1 = rCell1.getValue();
+ double nVal2 = rCell2.getValue();
+ if (nVal1 < nVal2)
+ nRes = -1;
+ else if (nVal1 > nVal2)
+ nRes = 1;
+ }
+ if ( !aSortParam.maKeyState[nSort].bAscending )
+ nRes = -nRes;
+ }
+ else
+ nRes = -1;
+ }
+ else
+ {
+ if (!rCell2.isEmpty())
+ nRes = 1;
+ else
+ nRes = 0; // both empty
+ }
+ return nRes;
+}
+
+short ScTable::Compare( ScSortInfoArray* pArray, SCCOLROW nIndex1, SCCOLROW nIndex2 ) const
+{
+ short nRes;
+ sal_uInt16 nSort = 0;
+ do
+ {
+ ScSortInfo& rInfo1 = pArray->Get( nSort, nIndex1 );
+ ScSortInfo& rInfo2 = pArray->Get( nSort, nIndex2 );
+ if ( aSortParam.bByRow )
+ nRes = CompareCell( nSort,
+ rInfo1.maCell, static_cast<SCCOL>(aSortParam.maKeyState[nSort].nField), rInfo1.nOrg,
+ rInfo2.maCell, static_cast<SCCOL>(aSortParam.maKeyState[nSort].nField), rInfo2.nOrg );
+ else
+ nRes = CompareCell( nSort,
+ rInfo1.maCell, static_cast<SCCOL>(rInfo1.nOrg), aSortParam.maKeyState[nSort].nField,
+ rInfo2.maCell, static_cast<SCCOL>(rInfo2.nOrg), aSortParam.maKeyState[nSort].nField );
+ } while ( nRes == 0 && ++nSort < pArray->GetUsedSorts() );
+ if( nRes == 0 )
+ {
+ ScSortInfo& rInfo1 = pArray->Get( 0, nIndex1 );
+ ScSortInfo& rInfo2 = pArray->Get( 0, nIndex2 );
+ if( rInfo1.nOrg < rInfo2.nOrg )
+ nRes = -1;
+ else if( rInfo1.nOrg > rInfo2.nOrg )
+ nRes = 1;
+ }
+ return nRes;
+}
+
+void ScTable::QuickSort( ScSortInfoArray* pArray, SCCOLROW nLo, SCCOLROW nHi )
+{
+ if ((nHi - nLo) == 1)
+ {
+ if (Compare(pArray, nLo, nHi) > 0)
+ pArray->Swap( nLo, nHi );
+ }
+ else
+ {
+ SCCOLROW ni = nLo;
+ SCCOLROW nj = nHi;
+ do
+ {
+ while ((ni <= nHi) && (Compare(pArray, ni, nLo)) < 0)
+ ni++;
+ while ((nj >= nLo) && (Compare(pArray, nLo, nj)) < 0)
+ nj--;
+ if (ni <= nj)
+ {
+ if (ni != nj)
+ pArray->Swap( ni, nj );
+ ni++;
+ nj--;
+ }
+ } while (ni < nj);
+ if ((nj - nLo) < (nHi - ni))
+ {
+ if (nLo < nj)
+ QuickSort(pArray, nLo, nj);
+ if (ni < nHi)
+ QuickSort(pArray, ni, nHi);
+ }
+ else
+ {
+ if (ni < nHi)
+ QuickSort(pArray, ni, nHi);
+ if (nLo < nj)
+ QuickSort(pArray, nLo, nj);
+ }
+ }
+}
+
+short ScTable::Compare(SCCOLROW nIndex1, SCCOLROW nIndex2) const
+{
+ short nRes;
+ sal_uInt16 nSort = 0;
+ const sal_uInt32 nMaxSorts = aSortParam.GetSortKeyCount();
+ if (aSortParam.bByRow)
+ {
+ do
+ {
+ SCCOL nCol = static_cast<SCCOL>(aSortParam.maKeyState[nSort].nField);
+ nRes = 0;
+ if(nCol < GetAllocatedColumnsCount())
+ {
+ ScRefCellValue aCell1 = aCol[nCol].GetCellValue(nIndex1);
+ ScRefCellValue aCell2 = aCol[nCol].GetCellValue(nIndex2);
+ nRes = CompareCell(nSort, aCell1, nCol, nIndex1, aCell2, nCol, nIndex2);
+ }
+ } while ( nRes == 0 && ++nSort < nMaxSorts && aSortParam.maKeyState[nSort].bDoSort );
+ }
+ else
+ {
+ do
+ {
+ SCROW nRow = aSortParam.maKeyState[nSort].nField;
+ ScRefCellValue aCell1;
+ ScRefCellValue aCell2;
+ if(nIndex1 < GetAllocatedColumnsCount())
+ aCell1 = aCol[nIndex1].GetCellValue(nRow);
+ if(nIndex2 < GetAllocatedColumnsCount())
+ aCell2 = aCol[nIndex2].GetCellValue(nRow);
+ nRes = CompareCell( nSort, aCell1, static_cast<SCCOL>(nIndex1),
+ nRow, aCell2, static_cast<SCCOL>(nIndex2), nRow );
+ } while ( nRes == 0 && ++nSort < nMaxSorts && aSortParam.maKeyState[nSort].bDoSort );
+ }
+ return nRes;
+}
+
+bool ScTable::IsSorted( SCCOLROW nStart, SCCOLROW nEnd ) const // over aSortParam
+{
+ for (SCCOLROW i=nStart; i<nEnd; i++)
+ {
+ if (Compare( i, i+1 ) > 0)
+ return false;
+ }
+ return true;
+}
+
+void ScTable::DecoladeRow( ScSortInfoArray* pArray, SCROW nRow1, SCROW nRow2 )
+{
+ SCROW nRow;
+ int nMax = nRow2 - nRow1;
+ for (SCROW i = nRow1; (i + 4) <= nRow2; i += 4)
+ {
+ nRow = comphelper::rng::uniform_int_distribution(0, nMax-1);
+ pArray->Swap(i, nRow1 + nRow);
+ }
+}
+
+void ScTable::Sort(
+ const ScSortParam& rSortParam, bool bKeepQuery, bool bUpdateRefs,
+ ScProgress* pProgress, sc::ReorderParam* pUndo )
+{
+ sc::DelayDeletingBroadcasters delayDeletingBroadcasters(GetDoc());
+ InitSortCollator( rSortParam );
+ bGlobalKeepQuery = bKeepQuery;
+
+ if (pUndo)
+ {
+ // Copy over the basic sort parameters.
+ pUndo->maDataAreaExtras = rSortParam.aDataAreaExtras;
+ pUndo->mbByRow = rSortParam.bByRow;
+ pUndo->mbHiddenFiltered = bKeepQuery;
+ pUndo->mbUpdateRefs = bUpdateRefs;
+ pUndo->mbHasHeaders = rSortParam.bHasHeader;
+ }
+
+ // It is assumed that the data area has already been trimmed as necessary.
+
+ aSortParam = rSortParam; // must be assigned before calling IsSorted()
+ if (rSortParam.bByRow)
+ {
+ const SCROW nLastRow = rSortParam.nRow2;
+ const SCROW nRow1 = (rSortParam.bHasHeader ? rSortParam.nRow1 + 1 : rSortParam.nRow1);
+ if (nRow1 < nLastRow && !IsSorted(nRow1, nLastRow))
+ {
+ if(pProgress)
+ pProgress->SetState( 0, nLastRow-nRow1 );
+
+ std::unique_ptr<ScSortInfoArray> pArray( CreateSortInfoArray(
+ aSortParam, nRow1, nLastRow, bKeepQuery, bUpdateRefs));
+
+ if ( nLastRow - nRow1 > 255 )
+ DecoladeRow(pArray.get(), nRow1, nLastRow);
+
+ QuickSort(pArray.get(), nRow1, nLastRow);
+ if (pArray->IsUpdateRefs())
+ SortReorderByRowRefUpdate(pArray.get(), aSortParam.nCol1, aSortParam.nCol2, pProgress);
+ else
+ {
+ SortReorderByRow(pArray.get(), aSortParam.nCol1, aSortParam.nCol2, pProgress, false);
+ if (rSortParam.aDataAreaExtras.anyExtrasWanted())
+ SortReorderAreaExtrasByRow( pArray.get(), aSortParam.nCol1, aSortParam.nCol2,
+ rSortParam.aDataAreaExtras, pProgress);
+ }
+
+ if (pUndo)
+ {
+ // Stored is the first data row without header row.
+ pUndo->maSortRange = ScRange(rSortParam.nCol1, nRow1, nTab, rSortParam.nCol2, nLastRow, nTab);
+ pUndo->maDataAreaExtras.mnStartRow = nRow1;
+ pUndo->maOrderIndices = pArray->GetOrderIndices();
+ }
+ }
+ }
+ else
+ {
+ const SCCOL nLastCol = rSortParam.nCol2;
+ const SCCOL nCol1 = (rSortParam.bHasHeader ? rSortParam.nCol1 + 1 : rSortParam.nCol1);
+ if (nCol1 < nLastCol && !IsSorted(nCol1, nLastCol))
+ {
+ if(pProgress)
+ pProgress->SetState( 0, nLastCol-nCol1 );
+
+ std::unique_ptr<ScSortInfoArray> pArray( CreateSortInfoArray(
+ aSortParam, nCol1, nLastCol, bKeepQuery, bUpdateRefs));
+
+ QuickSort(pArray.get(), nCol1, nLastCol);
+ SortReorderByColumn(pArray.get(), rSortParam.nRow1, rSortParam.nRow2,
+ rSortParam.aDataAreaExtras.mbCellFormats, pProgress);
+ if (rSortParam.aDataAreaExtras.anyExtrasWanted() && !pArray->IsUpdateRefs())
+ SortReorderAreaExtrasByColumn( pArray.get(),
+ rSortParam.nRow1, rSortParam.nRow2, rSortParam.aDataAreaExtras, pProgress);
+
+ if (pUndo)
+ {
+ // Stored is the first data column without header column.
+ pUndo->maSortRange = ScRange(nCol1, aSortParam.nRow1, nTab, nLastCol, aSortParam.nRow2, nTab);
+ pUndo->maDataAreaExtras.mnStartCol = nCol1;
+ pUndo->maOrderIndices = pArray->GetOrderIndices();
+ }
+ }
+ }
+ DestroySortCollator();
+}
+
+void ScTable::Reorder( const sc::ReorderParam& rParam )
+{
+ if (rParam.maOrderIndices.empty())
+ return;
+
+ std::unique_ptr<ScSortInfoArray> pArray(CreateSortInfoArray(rParam));
+ if (!pArray)
+ return;
+
+ if (rParam.mbByRow)
+ {
+ // Re-play sorting from the known sort indices.
+ pArray->ReorderByRow(rParam.maOrderIndices);
+ if (pArray->IsUpdateRefs())
+ SortReorderByRowRefUpdate(
+ pArray.get(), rParam.maSortRange.aStart.Col(), rParam.maSortRange.aEnd.Col(), nullptr);
+ else
+ {
+ SortReorderByRow( pArray.get(),
+ rParam.maSortRange.aStart.Col(), rParam.maSortRange.aEnd.Col(), nullptr, false);
+ if (rParam.maDataAreaExtras.anyExtrasWanted())
+ SortReorderAreaExtrasByRow( pArray.get(),
+ rParam.maSortRange.aStart.Col(), rParam.maSortRange.aEnd.Col(),
+ rParam.maDataAreaExtras, nullptr);
+ }
+ }
+ else
+ {
+ // Ordering by column is much simpler. Just set the order indices and we are done.
+ pArray->SetOrderIndices(std::vector(rParam.maOrderIndices));
+ SortReorderByColumn(
+ pArray.get(), rParam.maSortRange.aStart.Row(), rParam.maSortRange.aEnd.Row(),
+ rParam.maDataAreaExtras.mbCellFormats, nullptr);
+ if (rParam.maDataAreaExtras.anyExtrasWanted() && !pArray->IsUpdateRefs())
+ SortReorderAreaExtrasByColumn( pArray.get(),
+ rParam.maSortRange.aStart.Row(), rParam.maSortRange.aEnd.Row(),
+ rParam.maDataAreaExtras, nullptr);
+ }
+}
+
+namespace {
+
+class SubTotalRowFinder
+{
+ const ScTable& mrTab;
+ const ScSubTotalParam& mrParam;
+
+public:
+ SubTotalRowFinder(const ScTable& rTab, const ScSubTotalParam& rParam) :
+ mrTab(rTab), mrParam(rParam) {}
+
+ bool operator() (size_t nRow, const ScFormulaCell* pCell)
+ {
+ if (!pCell->IsSubTotal())
+ return false;
+
+ SCCOL nStartCol = mrParam.nCol1;
+ SCCOL nEndCol = mrParam.nCol2;
+
+ for (SCCOL nCol : mrTab.GetAllocatedColumnsRange(0, nStartCol - 1))
+ {
+ if (mrTab.HasData(nCol, nRow))
+ return true;
+ }
+ for (SCCOL nCol : mrTab.GetAllocatedColumnsRange(nEndCol + 1, mrTab.GetDoc().MaxCol()))
+ {
+ if (mrTab.HasData(nCol, nRow))
+ return true;
+ }
+ return false;
+ }
+};
+
+}
+
+bool ScTable::TestRemoveSubTotals( const ScSubTotalParam& rParam )
+{
+ SCCOL nStartCol = rParam.nCol1;
+ SCROW nStartRow = rParam.nRow1 + 1; // Header
+ SCCOL nEndCol = ClampToAllocatedColumns(rParam.nCol2);
+ SCROW nEndRow = rParam.nRow2;
+
+ for (SCCOL nCol = nStartCol; nCol <= nEndCol; ++nCol)
+ {
+ const sc::CellStoreType& rCells = aCol[nCol].maCells;
+ SubTotalRowFinder aFunc(*this, rParam);
+ std::pair<sc::CellStoreType::const_iterator,size_t> aPos =
+ sc::FindFormula(rCells, nStartRow, nEndRow, aFunc);
+ if (aPos.first != rCells.end())
+ return true;
+ }
+ return false;
+}
+
+namespace {
+
+struct RemoveSubTotalsHandler
+{
+ std::set<SCROW> aRemoved;
+
+ void operator() (size_t nRow, const ScFormulaCell* p)
+ {
+ if (p->IsSubTotal())
+ aRemoved.insert(nRow);
+ }
+};
+
+}
+
+void ScTable::RemoveSubTotals( ScSubTotalParam& rParam )
+{
+ SCCOL nStartCol = rParam.nCol1;
+ SCROW nStartRow = rParam.nRow1 + 1; // Header
+ SCCOL nEndCol = ClampToAllocatedColumns(rParam.nCol2);
+ SCROW nEndRow = rParam.nRow2; // will change
+
+ RemoveSubTotalsHandler aFunc;
+ for (SCCOL nCol = nStartCol; nCol <= nEndCol; ++nCol)
+ {
+ const sc::CellStoreType& rCells = aCol[nCol].maCells;
+ sc::ParseFormula(rCells.begin(), rCells, nStartRow, nEndRow, aFunc);
+ }
+
+ auto& aRows = aFunc.aRemoved;
+
+ std::for_each(aRows.rbegin(), aRows.rend(), [this](const SCROW nRow) {
+ RemoveRowBreak(nRow+1, false, true);
+ rDocument.DeleteRow(0, nTab, rDocument.MaxCol(), nTab, nRow, 1);
+ });
+
+ rParam.nRow2 -= aRows.size();
+}
+
+// Delete hard number formats (for result formulas)
+
+static void lcl_RemoveNumberFormat( ScTable* pTab, SCCOL nCol, SCROW nRow )
+{
+ const ScPatternAttr* pPattern = pTab->GetPattern( nCol, nRow );
+ if ( pPattern->GetItemSet().GetItemState( ATTR_VALUE_FORMAT, false )
+ == SfxItemState::SET )
+ {
+ auto pNewPattern = std::make_unique<ScPatternAttr>( *pPattern );
+ SfxItemSet& rSet = pNewPattern->GetItemSet();
+ rSet.ClearItem( ATTR_VALUE_FORMAT );
+ rSet.ClearItem( ATTR_LANGUAGE_FORMAT );
+ pTab->SetPattern( nCol, nRow, std::move(pNewPattern) );
+ }
+}
+
+namespace {
+
+struct RowEntry
+{
+ sal_uInt16 nGroupNo;
+ SCROW nSubStartRow;
+ SCROW nDestRow;
+ SCROW nFuncStart;
+ SCROW nFuncEnd;
+};
+
+}
+
+static TranslateId lcl_GetSubTotalStrId(int id)
+{
+ switch ( id )
+ {
+ case SUBTOTAL_FUNC_AVE: return STR_FUN_TEXT_AVG;
+ case SUBTOTAL_FUNC_CNT:
+ case SUBTOTAL_FUNC_CNT2: return STR_FUN_TEXT_COUNT;
+ case SUBTOTAL_FUNC_MAX: return STR_FUN_TEXT_MAX;
+ case SUBTOTAL_FUNC_MIN: return STR_FUN_TEXT_MIN;
+ case SUBTOTAL_FUNC_PROD: return STR_FUN_TEXT_PRODUCT;
+ case SUBTOTAL_FUNC_STD:
+ case SUBTOTAL_FUNC_STDP: return STR_FUN_TEXT_STDDEV;
+ case SUBTOTAL_FUNC_SUM: return STR_FUN_TEXT_SUM;
+ case SUBTOTAL_FUNC_VAR:
+ case SUBTOTAL_FUNC_VARP: return STR_FUN_TEXT_VAR;
+ default:
+ {
+ return STR_EMPTYDATA;
+ // added to avoid warnings
+ }
+ }
+}
+
+// new intermediate results
+// rParam.nRow2 is changed!
+
+bool ScTable::DoSubTotals( ScSubTotalParam& rParam )
+{
+ SCCOL nStartCol = rParam.nCol1;
+ SCROW nStartRow = rParam.nRow1 + 1; // Header
+ SCCOL nEndCol = rParam.nCol2;
+ SCROW nEndRow = rParam.nRow2; // will change
+ sal_uInt16 i;
+
+ // Remove empty rows at the end
+ // so that all exceeding (rDocument.MaxRow()) can be found by InsertRow (#35180#)
+ // If sorted, all empty rows are at the end.
+ SCSIZE nEmpty = GetEmptyLinesInBlock( nStartCol, nStartRow, nEndCol, nEndRow, DIR_BOTTOM );
+ nEndRow -= nEmpty;
+
+ sal_uInt16 nLevelCount = 0; // Number of levels
+ bool bDoThis = true;
+ for (i=0; i<MAXSUBTOTAL && bDoThis; i++)
+ if (rParam.bGroupActive[i])
+ nLevelCount = i+1;
+ else
+ bDoThis = false;
+
+ if (nLevelCount==0) // do nothing
+ return true;
+
+ SCCOL* nGroupCol = rParam.nField; // columns which will be used when grouping
+
+ // With (blank) as a separate category, subtotal rows from
+ // the other columns must always be tested
+ // (previously only when a column occurred more than once)
+ bool bTestPrevSub = ( nLevelCount > 1 );
+
+ OUString aSubString;
+
+ bool bIgnoreCase = !rParam.bCaseSens;
+
+ OUString aCompString[MAXSUBTOTAL];
+
+ //TODO: sort?
+
+ ScStyleSheet* pStyle = static_cast<ScStyleSheet*>(rDocument.GetStyleSheetPool()->Find(
+ ScResId(STR_STYLENAME_RESULT), SfxStyleFamily::Para ));
+
+ bool bSpaceLeft = true; // Success when inserting?
+
+ // For performance reasons collect formula entries so their
+ // references don't have to be tested for updates each time a new row is
+ // inserted
+ RowEntry aRowEntry;
+ ::std::vector< RowEntry > aRowVector;
+
+ for (sal_uInt16 nLevel=0; nLevel<nLevelCount && bSpaceLeft; nLevel++)
+ {
+ aRowEntry.nGroupNo = nLevelCount - nLevel - 1;
+
+ // how many results per level
+ SCCOL nResCount = rParam.nSubTotals[aRowEntry.nGroupNo];
+ // result functions
+ ScSubTotalFunc* pResFunc = rParam.pFunctions[aRowEntry.nGroupNo].get();
+
+ if (nResCount > 0) // otherwise only sort
+ {
+ for (i=0; i<=aRowEntry.nGroupNo; i++)
+ {
+ aSubString = GetString( nGroupCol[i], nStartRow );
+ if ( bIgnoreCase )
+ aCompString[i] = ScGlobal::getCharClass().uppercase( aSubString );
+ else
+ aCompString[i] = aSubString;
+ } // aSubString stays on the last
+
+ bool bBlockVis = false; // group visible?
+ aRowEntry.nSubStartRow = nStartRow;
+ for (SCROW nRow=nStartRow; nRow<=nEndRow+1 && bSpaceLeft; nRow++)
+ {
+ bool bChanged;
+ if (nRow>nEndRow)
+ bChanged = true;
+ else
+ {
+ bChanged = false;
+ OUString aString;
+ for (i=0; i<=aRowEntry.nGroupNo && !bChanged; i++)
+ {
+ aString = GetString( nGroupCol[i], nRow );
+ if (bIgnoreCase)
+ aString = ScGlobal::getCharClass().uppercase(aString);
+ // when sorting, blanks are separate group
+ // otherwise blank cells are allowed below
+ bChanged = ( ( !aString.isEmpty() || rParam.bDoSort ) &&
+ aString != aCompString[i] );
+ }
+ if ( bChanged && bTestPrevSub )
+ {
+ // No group change on rows that will contain subtotal formulas
+ bChanged = std::none_of(aRowVector.begin(), aRowVector.end(),
+ [&nRow](const RowEntry& rEntry) { return rEntry.nDestRow == nRow; });
+ }
+ }
+ if ( bChanged )
+ {
+ aRowEntry.nDestRow = nRow;
+ aRowEntry.nFuncStart = aRowEntry.nSubStartRow;
+ aRowEntry.nFuncEnd = nRow-1;
+
+ bSpaceLeft = rDocument.InsertRow( 0, nTab, rDocument.MaxCol(), nTab,
+ aRowEntry.nDestRow, 1 );
+ DBShowRow( aRowEntry.nDestRow, bBlockVis );
+ if ( rParam.bPagebreak && nRow < rDocument.MaxRow() &&
+ aRowEntry.nSubStartRow != nStartRow && nLevel == 0)
+ SetRowBreak(aRowEntry.nSubStartRow, false, true);
+
+ if (bSpaceLeft)
+ {
+ for ( auto& rRowEntry : aRowVector)
+ {
+ if ( aRowEntry.nDestRow <= rRowEntry.nSubStartRow )
+ ++rRowEntry.nSubStartRow;
+ if ( aRowEntry.nDestRow <= rRowEntry.nDestRow )
+ ++rRowEntry.nDestRow;
+ if ( aRowEntry.nDestRow <= rRowEntry.nFuncStart )
+ ++rRowEntry.nFuncStart;
+ if ( aRowEntry.nDestRow <= rRowEntry.nFuncEnd )
+ ++rRowEntry.nFuncEnd;
+ }
+ // collect formula positions
+ aRowVector.push_back( aRowEntry );
+
+ OUString aOutString = aSubString;
+ if (aOutString.isEmpty())
+ aOutString = ScResId( STR_EMPTYDATA );
+ aOutString += " ";
+ TranslateId pStrId = STR_TABLE_ERGEBNIS;
+ if ( nResCount == 1 )
+ pStrId = lcl_GetSubTotalStrId(pResFunc[0]);
+ aOutString += ScResId(pStrId);
+ SetString( nGroupCol[aRowEntry.nGroupNo], aRowEntry.nDestRow, nTab, aOutString );
+ ApplyStyle( nGroupCol[aRowEntry.nGroupNo], aRowEntry.nDestRow, pStyle );
+
+ ++nRow;
+ ++nEndRow;
+ aRowEntry.nSubStartRow = nRow;
+ for (i=0; i<=aRowEntry.nGroupNo; i++)
+ {
+ aSubString = GetString( nGroupCol[i], nRow );
+ if ( bIgnoreCase )
+ aCompString[i] = ScGlobal::getCharClass().uppercase( aSubString );
+ else
+ aCompString[i] = aSubString;
+ }
+ }
+ }
+ bBlockVis = !RowFiltered(nRow);
+ }
+ }
+ }
+
+ if (!aRowVector.empty())
+ {
+ // generate global total
+ SCROW nGlobalStartRow = aRowVector[0].nSubStartRow;
+ SCROW nGlobalStartFunc = aRowVector[0].nFuncStart;
+ SCROW nGlobalEndRow = 0;
+ SCROW nGlobalEndFunc = 0;
+ for (const auto& rRowEntry : aRowVector)
+ {
+ nGlobalEndRow = (nGlobalEndRow < rRowEntry.nDestRow) ? rRowEntry.nDestRow : nGlobalEndRow;
+ nGlobalEndFunc = (nGlobalEndFunc < rRowEntry.nFuncEnd) ? rRowEntry.nFuncEnd : nGlobalEndRow;
+ }
+
+ for (sal_uInt16 nLevel = 0; nLevel<nLevelCount; nLevel++)
+ {
+ const sal_uInt16 nGroupNo = nLevelCount - nLevel - 1;
+ const ScSubTotalFunc* pResFunc = rParam.pFunctions[nGroupNo].get();
+ if (!pResFunc)
+ {
+ // No subtotal function given for this group => no formula or
+ // label and do not insert a row.
+ continue;
+ }
+
+ // increment end row
+ nGlobalEndRow++;
+
+ // add row entry for formula
+ aRowEntry.nGroupNo = nGroupNo;
+ aRowEntry.nSubStartRow = nGlobalStartRow;
+ aRowEntry.nFuncStart = nGlobalStartFunc;
+ aRowEntry.nDestRow = nGlobalEndRow;
+ aRowEntry.nFuncEnd = nGlobalEndFunc;
+
+ // increment row
+ nGlobalEndFunc++;
+
+ bSpaceLeft = rDocument.InsertRow(0, nTab, rDocument.MaxCol(), nTab, aRowEntry.nDestRow, 1);
+
+ if (bSpaceLeft)
+ {
+ aRowVector.push_back(aRowEntry);
+ nEndRow++;
+ DBShowRow(aRowEntry.nDestRow, true);
+
+ // insert label
+ OUString label = ScResId(STR_TABLE_GRAND) + " " + ScResId(lcl_GetSubTotalStrId(pResFunc[0]));
+ SetString(nGroupCol[aRowEntry.nGroupNo], aRowEntry.nDestRow, nTab, label);
+ ApplyStyle(nGroupCol[aRowEntry.nGroupNo], aRowEntry.nDestRow, pStyle);
+ }
+ }
+ }
+
+ // now insert the formulas
+ ScComplexRefData aRef;
+ aRef.InitFlags();
+ aRef.Ref1.SetAbsTab(nTab);
+ aRef.Ref2.SetAbsTab(nTab);
+ for (const auto& rRowEntry : aRowVector)
+ {
+ SCCOL nResCount = rParam.nSubTotals[rRowEntry.nGroupNo];
+ SCCOL* nResCols = rParam.pSubTotals[rRowEntry.nGroupNo].get();
+ ScSubTotalFunc* pResFunc = rParam.pFunctions[rRowEntry.nGroupNo].get();
+ for ( SCCOL nResult=0; nResult < nResCount; ++nResult )
+ {
+ aRef.Ref1.SetAbsCol(nResCols[nResult]);
+ aRef.Ref1.SetAbsRow(rRowEntry.nFuncStart);
+ aRef.Ref2.SetAbsCol(nResCols[nResult]);
+ aRef.Ref2.SetAbsRow(rRowEntry.nFuncEnd);
+
+ ScTokenArray aArr(rDocument);
+ aArr.AddOpCode( ocSubTotal );
+ aArr.AddOpCode( ocOpen );
+ aArr.AddDouble( static_cast<double>(pResFunc[nResult]) );
+ aArr.AddOpCode( ocSep );
+ aArr.AddDoubleReference( aRef );
+ aArr.AddOpCode( ocClose );
+ aArr.AddOpCode( ocStop );
+ ScFormulaCell* pCell = new ScFormulaCell(
+ rDocument, ScAddress(nResCols[nResult], rRowEntry.nDestRow, nTab), aArr);
+ if ( rParam.bIncludePattern )
+ pCell->SetNeedNumberFormat(true);
+
+ SetFormulaCell(nResCols[nResult], rRowEntry.nDestRow, pCell);
+ if ( nResCols[nResult] != nGroupCol[rRowEntry.nGroupNo] )
+ {
+ ApplyStyle( nResCols[nResult], rRowEntry.nDestRow, pStyle );
+
+ lcl_RemoveNumberFormat( this, nResCols[nResult], rRowEntry.nDestRow );
+ }
+ }
+
+ }
+
+ //TODO: according to setting, shift intermediate-sum rows up?
+
+ //TODO: create Outlines directly?
+
+ if (bSpaceLeft)
+ DoAutoOutline( nStartCol, nStartRow, nEndCol, nEndRow );
+
+ rParam.nRow2 = nEndRow; // new end
+ return bSpaceLeft;
+}
+
+void ScTable::TopTenQuery( ScQueryParam& rParam )
+{
+ bool bSortCollatorInitialized = false;
+ SCSIZE nEntryCount = rParam.GetEntryCount();
+ SCROW nRow1 = (rParam.bHasHeader ? rParam.nRow1 + 1 : rParam.nRow1);
+ SCSIZE nCount = static_cast<SCSIZE>(rParam.nRow2 - nRow1 + 1);
+ for ( SCSIZE i=0; (i<nEntryCount) && (rParam.GetEntry(i).bDoQuery); i++ )
+ {
+ ScQueryEntry& rEntry = rParam.GetEntry(i);
+ ScQueryEntry::QueryItemsType& rItems = rEntry.GetQueryItems();
+
+ for (ScQueryEntry::Item& rItem : rItems)
+ {
+ switch (rEntry.eOp)
+ {
+ case SC_TOPVAL:
+ case SC_BOTVAL:
+ case SC_TOPPERC:
+ case SC_BOTPERC:
+ {
+ ScSortParam aLocalSortParam(rParam, static_cast<SCCOL>(rEntry.nField));
+ aSortParam = aLocalSortParam; // used in CreateSortInfoArray, Compare
+ if (!bSortCollatorInitialized)
+ {
+ bSortCollatorInitialized = true;
+ InitSortCollator(aLocalSortParam);
+ }
+ std::unique_ptr<ScSortInfoArray> pArray(CreateSortInfoArray(aSortParam, nRow1, rParam.nRow2, bGlobalKeepQuery, false));
+ DecoladeRow(pArray.get(), nRow1, rParam.nRow2);
+ QuickSort(pArray.get(), nRow1, rParam.nRow2);
+ std::unique_ptr<ScSortInfo[]> const& ppInfo = pArray->GetFirstArray();
+ SCSIZE nValidCount = nCount;
+ // Don't count note or blank cells, they are sorted to the end
+ while (nValidCount > 0 && ppInfo[nValidCount - 1].maCell.isEmpty())
+ nValidCount--;
+ // Don't count Strings, they are between Value and blank
+ while (nValidCount > 0 && ppInfo[nValidCount - 1].maCell.hasString())
+ nValidCount--;
+ if (nValidCount > 0)
+ {
+ if (rItem.meType == ScQueryEntry::ByString)
+ { // by string ain't going to work
+ rItem.meType = ScQueryEntry::ByValue;
+ rItem.mfVal = 10; // 10 and 10% respectively
+ }
+ SCSIZE nVal = (rItem.mfVal >= 1 ? static_cast<SCSIZE>(rItem.mfVal) : 1);
+ SCSIZE nOffset = 0;
+ switch (rEntry.eOp)
+ {
+ case SC_TOPVAL:
+ {
+ rEntry.eOp = SC_GREATER_EQUAL;
+ if (nVal > nValidCount)
+ nVal = nValidCount;
+ nOffset = nValidCount - nVal; // 1 <= nVal <= nValidCount
+ }
+ break;
+ case SC_BOTVAL:
+ {
+ rEntry.eOp = SC_LESS_EQUAL;
+ if (nVal > nValidCount)
+ nVal = nValidCount;
+ nOffset = nVal - 1; // 1 <= nVal <= nValidCount
+ }
+ break;
+ case SC_TOPPERC:
+ {
+ rEntry.eOp = SC_GREATER_EQUAL;
+ if (nVal > 100)
+ nVal = 100;
+ nOffset = nValidCount - (nValidCount * nVal / 100);
+ if (nOffset >= nValidCount)
+ nOffset = nValidCount - 1;
+ }
+ break;
+ case SC_BOTPERC:
+ {
+ rEntry.eOp = SC_LESS_EQUAL;
+ if (nVal > 100)
+ nVal = 100;
+ nOffset = (nValidCount * nVal / 100);
+ if (nOffset >= nValidCount)
+ nOffset = nValidCount - 1;
+ }
+ break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+ ScRefCellValue aCell = ppInfo[nOffset].maCell;
+ if (aCell.hasNumeric())
+ rItem.mfVal = aCell.getValue();
+ else
+ {
+ OSL_FAIL("TopTenQuery: pCell no ValueData");
+ rEntry.eOp = SC_GREATER_EQUAL;
+ rItem.mfVal = 0;
+ }
+ }
+ else
+ {
+ rEntry.eOp = SC_GREATER_EQUAL;
+ rItem.meType = ScQueryEntry::ByValue;
+ rItem.mfVal = 0;
+ }
+ }
+ break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+ }
+ }
+ if ( bSortCollatorInitialized )
+ DestroySortCollator();
+}
+
+namespace {
+
+bool CanOptimizeQueryStringToNumber( const SvNumberFormatter* pFormatter, sal_uInt32 nFormatIndex, bool& bDateFormat )
+{
+ // tdf#105629: ScQueryEntry::ByValue queries are faster than ScQueryEntry::ByString.
+ // The problem with this optimization is that the autofilter dialog apparently converts
+ // the value to text and then converts that back to a number for filtering.
+ // If that leads to any change of value (such as when time is rounded to seconds),
+ // even matching values will be filtered out. Therefore query by value only for formats
+ // where no such change should occur.
+ if(const SvNumberformat* pEntry = pFormatter->GetEntry(nFormatIndex))
+ {
+ switch(pEntry->GetType())
+ {
+ case SvNumFormatType::NUMBER:
+ case SvNumFormatType::FRACTION:
+ case SvNumFormatType::SCIENTIFIC:
+ return true;
+ case SvNumFormatType::DATE:
+ case SvNumFormatType::DATETIME:
+ bDateFormat = true;
+ break;
+ default:
+ break;
+ }
+ }
+ return false;
+}
+
+class PrepareQueryItem
+{
+ const ScDocument& mrDoc;
+ const bool mbRoundForFilter;
+public:
+ explicit PrepareQueryItem(const ScDocument& rDoc, bool bRoundForFilter) :
+ mrDoc(rDoc), mbRoundForFilter(bRoundForFilter) {}
+
+ void operator() (ScQueryEntry::Item& rItem)
+ {
+ rItem.mbRoundForFilter = mbRoundForFilter;
+
+ if (rItem.meType != ScQueryEntry::ByString && rItem.meType != ScQueryEntry::ByDate)
+ return;
+
+ sal_uInt32 nIndex = 0;
+ bool bNumber = mrDoc.GetFormatTable()->
+ IsNumberFormat(rItem.maString.getString(), nIndex, rItem.mfVal);
+
+ // Advanced Filter creates only ByString queries that need to be
+ // converted to ByValue if appropriate. rItem.mfVal now holds the value
+ // if bNumber==true.
+
+ if (rItem.meType == ScQueryEntry::ByString)
+ {
+ bool bDateFormat = false;
+ if (bNumber && CanOptimizeQueryStringToNumber( mrDoc.GetFormatTable(), nIndex, bDateFormat ))
+ rItem.meType = ScQueryEntry::ByValue;
+ if (!bDateFormat)
+ return;
+ }
+
+ // Double-check if the query by date is really appropriate.
+
+ if (bNumber && ((nIndex % SV_COUNTRY_LANGUAGE_OFFSET) != 0))
+ {
+ const SvNumberformat* pEntry = mrDoc.GetFormatTable()->GetEntry(nIndex);
+ if (pEntry)
+ {
+ SvNumFormatType nNumFmtType = pEntry->GetType();
+ if (!(nNumFmtType & SvNumFormatType::DATE) || (nNumFmtType & SvNumFormatType::TIME))
+ rItem.meType = ScQueryEntry::ByValue; // not a date only
+ else
+ rItem.meType = ScQueryEntry::ByDate; // date only
+ }
+ else
+ rItem.meType = ScQueryEntry::ByValue; // what the ... not a date
+ }
+ else
+ rItem.meType = ScQueryEntry::ByValue; // not a date
+ }
+};
+
+void lcl_PrepareQuery( const ScDocument* pDoc, ScTable* pTab, ScQueryParam& rParam, bool bRoundForFilter )
+{
+ bool bTopTen = false;
+ SCSIZE nEntryCount = rParam.GetEntryCount();
+
+ for ( SCSIZE i = 0; i < nEntryCount; ++i )
+ {
+ ScQueryEntry& rEntry = rParam.GetEntry(i);
+ if (!rEntry.bDoQuery)
+ continue;
+
+ ScQueryEntry::QueryItemsType& rItems = rEntry.GetQueryItems();
+ std::for_each(rItems.begin(), rItems.end(), PrepareQueryItem(*pDoc, bRoundForFilter));
+
+ if ( !bTopTen )
+ {
+ switch ( rEntry.eOp )
+ {
+ case SC_TOPVAL:
+ case SC_BOTVAL:
+ case SC_TOPPERC:
+ case SC_BOTPERC:
+ {
+ bTopTen = true;
+ }
+ break;
+ default:
+ {
+ }
+ }
+ }
+ }
+
+ if ( bTopTen )
+ {
+ pTab->TopTenQuery( rParam );
+ }
+}
+
+}
+
+void ScTable::PrepareQuery( ScQueryParam& rQueryParam )
+{
+ lcl_PrepareQuery(&rDocument, this, rQueryParam, false);
+}
+
+SCSIZE ScTable::Query(const ScQueryParam& rParamOrg, bool bKeepSub)
+{
+ ScQueryParam aParam( rParamOrg );
+ typedef std::unordered_set<OUString> StrSetType;
+ StrSetType aStrSet;
+
+ bool bStarted = false;
+ bool bOldResult = true;
+ SCROW nOldStart = 0;
+ SCROW nOldEnd = 0;
+
+ SCSIZE nCount = 0;
+ SCROW nOutRow = 0;
+ SCROW nHeader = aParam.bHasHeader ? 1 : 0;
+
+ lcl_PrepareQuery(&rDocument, this, aParam, true);
+
+ if (!aParam.bInplace)
+ {
+ nOutRow = aParam.nDestRow + nHeader;
+ if (nHeader > 0)
+ CopyData( aParam.nCol1, aParam.nRow1, aParam.nCol2, aParam.nRow1,
+ aParam.nDestCol, aParam.nDestRow, aParam.nDestTab );
+ }
+
+ sc::TableColumnBlockPositionSet blockPos( GetDoc(), nTab ); // cache mdds access
+ ScQueryEvaluator queryEvaluator(GetDoc(), *this, aParam);
+
+ SCROW nRealRow2 = aParam.nRow2;
+ for (SCROW j = aParam.nRow1 + nHeader; j <= nRealRow2; ++j)
+ {
+ bool bResult; // Filter result
+ bool bValid = queryEvaluator.ValidQuery(j, nullptr, &blockPos);
+ if (!bValid && bKeepSub) // Keep subtotals
+ {
+ for (SCCOL nCol=aParam.nCol1; nCol<=aParam.nCol2 && !bValid; nCol++)
+ {
+ ScRefCellValue aCell = GetCellValue(nCol, j);
+ if (aCell.meType != CELLTYPE_FORMULA)
+ continue;
+
+ if (!aCell.mpFormula->IsSubTotal())
+ continue;
+
+ if (RefVisible(aCell.mpFormula))
+ bValid = true;
+ }
+ }
+ if (bValid)
+ {
+ if (aParam.bDuplicate)
+ bResult = true;
+ else
+ {
+ OUStringBuffer aStr;
+ for (SCCOL k=aParam.nCol1; k <= aParam.nCol2; k++)
+ {
+ OUString aCellStr = GetString(k, j);
+ aStr.append(aCellStr + u"\x0001");
+ }
+
+ bResult = aStrSet.insert(aStr.makeStringAndClear()).second; // unique if inserted.
+ }
+ }
+ else
+ bResult = false;
+
+ if (aParam.bInplace)
+ {
+ if (bResult == bOldResult && bStarted)
+ nOldEnd = j;
+ else
+ {
+ if (bStarted)
+ DBShowRows(nOldStart,nOldEnd, bOldResult);
+ nOldStart = nOldEnd = j;
+ bOldResult = bResult;
+ }
+ bStarted = true;
+ }
+ else
+ {
+ if (bResult)
+ {
+ CopyData( aParam.nCol1,j, aParam.nCol2,j, aParam.nDestCol,nOutRow,aParam.nDestTab );
+ if( nTab == aParam.nDestTab ) // copy to self, changes may invalidate caching position hints
+ blockPos.invalidate();
+ ++nOutRow;
+ }
+ }
+ if (bResult)
+ ++nCount;
+ }
+
+ if (aParam.bInplace && bStarted)
+ DBShowRows(nOldStart,nOldEnd, bOldResult);
+
+ if (aParam.bInplace)
+ SetDrawPageSize();
+
+ return nCount;
+}
+
+bool ScTable::CreateExcelQuery(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, ScQueryParam& rQueryParam)
+{
+ bool bValid = true;
+ std::unique_ptr<SCCOL[]> pFields(new SCCOL[nCol2-nCol1+1]);
+ OUString aCellStr;
+ SCCOL nCol = nCol1;
+ OSL_ENSURE( rQueryParam.nTab != SCTAB_MAX, "rQueryParam.nTab no value, not bad but no good" );
+ SCTAB nDBTab = (rQueryParam.nTab == SCTAB_MAX ? nTab : rQueryParam.nTab);
+ SCROW nDBRow1 = rQueryParam.nRow1;
+ SCCOL nDBCol2 = rQueryParam.nCol2;
+ // First row must be column headers
+ while (bValid && (nCol <= nCol2))
+ {
+ OUString aQueryStr = GetUpperCellString(nCol, nRow1);
+ bool bFound = false;
+ SCCOL i = rQueryParam.nCol1;
+ while (!bFound && (i <= nDBCol2))
+ {
+ if ( nTab == nDBTab )
+ aCellStr = GetUpperCellString(i, nDBRow1);
+ else
+ aCellStr = rDocument.GetUpperCellString(i, nDBRow1, nDBTab);
+ bFound = (aCellStr == aQueryStr);
+ if (!bFound) i++;
+ }
+ if (bFound)
+ pFields[nCol - nCol1] = i;
+ else
+ bValid = false;
+ nCol++;
+ }
+ if (bValid)
+ {
+ sal_uLong nVisible = 0;
+ for ( nCol=nCol1; nCol<=ClampToAllocatedColumns(nCol2); nCol++ )
+ nVisible += aCol[nCol].VisibleCount( nRow1+1, nRow2 );
+
+ if ( nVisible > SCSIZE_MAX / sizeof(void*) )
+ {
+ OSL_FAIL("too many filter criteria");
+ nVisible = 0;
+ }
+
+ SCSIZE nNewEntries = nVisible;
+ rQueryParam.Resize( nNewEntries );
+
+ SCSIZE nIndex = 0;
+ SCROW nRow = nRow1 + 1;
+ svl::SharedStringPool& rPool = rDocument.GetSharedStringPool();
+ while (nRow <= nRow2)
+ {
+ nCol = nCol1;
+ while (nCol <= nCol2)
+ {
+ aCellStr = GetInputString( nCol, nRow );
+ if (!aCellStr.isEmpty())
+ {
+ if (nIndex < nNewEntries)
+ {
+ rQueryParam.GetEntry(nIndex).nField = pFields[nCol - nCol1];
+ rQueryParam.FillInExcelSyntax(rPool, aCellStr, nIndex, nullptr);
+ nIndex++;
+ if (nIndex < nNewEntries)
+ rQueryParam.GetEntry(nIndex).eConnect = SC_AND;
+ }
+ else
+ bValid = false;
+ }
+ nCol++;
+ }
+ nRow++;
+ if (nIndex < nNewEntries)
+ rQueryParam.GetEntry(nIndex).eConnect = SC_OR;
+ }
+ }
+ return bValid;
+}
+
+bool ScTable::CreateStarQuery(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, ScQueryParam& rQueryParam)
+{
+ // 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 (nCol2 - nCol1 < 3)
+ return false;
+
+ bool bValid;
+ OUString aCellStr;
+ SCSIZE nIndex = 0;
+ SCROW nRow = nRow1;
+ OSL_ENSURE( rQueryParam.nTab != SCTAB_MAX, "rQueryParam.nTab no value, not bad but no good" );
+ SCTAB nDBTab = (rQueryParam.nTab == SCTAB_MAX ? nTab : rQueryParam.nTab);
+ SCROW nDBRow1 = rQueryParam.nRow1;
+ SCCOL nDBCol2 = rQueryParam.nCol2;
+
+ SCSIZE nNewEntries = static_cast<SCSIZE>(nRow2-nRow1+1);
+ rQueryParam.Resize( nNewEntries );
+ svl::SharedStringPool& rPool = rDocument.GetSharedStringPool();
+
+ do
+ {
+ ScQueryEntry& rEntry = rQueryParam.GetEntry(nIndex);
+
+ bValid = false;
+ // First column AND/OR
+ if (nIndex > 0)
+ {
+ aCellStr = GetUpperCellString(nCol1, nRow);
+ 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;
+ }
+ }
+ // Second column field name
+ if ((nIndex < 1) || bValid)
+ {
+ bool bFound = false;
+ aCellStr = GetUpperCellString(nCol1 + 1, nRow);
+ for (SCCOL i=rQueryParam.nCol1; (i <= nDBCol2) && (!bFound); i++)
+ {
+ OUString aFieldStr;
+ if ( nTab == nDBTab )
+ aFieldStr = GetUpperCellString(i, nDBRow1);
+ else
+ aFieldStr = rDocument.GetUpperCellString(i, nDBRow1, nDBTab);
+ bFound = (aCellStr == aFieldStr);
+ if (bFound)
+ {
+ rEntry.nField = i;
+ bValid = true;
+ }
+ else
+ bValid = false;
+ }
+ }
+ // Third column operator =<>...
+ if (bValid)
+ {
+ aCellStr = GetUpperCellString(nCol1 + 2, nRow);
+ if (aCellStr.startsWith("<"))
+ {
+ if (aCellStr[1] == '>')
+ rEntry.eOp = SC_NOT_EQUAL;
+ else if (aCellStr[1] == '=')
+ rEntry.eOp = SC_LESS_EQUAL;
+ else
+ rEntry.eOp = SC_LESS;
+ }
+ else if (aCellStr.startsWith(">"))
+ {
+ if (aCellStr[1] == '=')
+ rEntry.eOp = SC_GREATER_EQUAL;
+ else
+ rEntry.eOp = SC_GREATER;
+ }
+ else if (aCellStr.startsWith("="))
+ rEntry.eOp = SC_EQUAL;
+
+ }
+ // Fourth column values
+ if (bValid)
+ {
+ OUString aStr = GetString(nCol1 + 3, nRow);
+ rEntry.GetQueryItem().maString = rPool.intern(aStr);
+ rEntry.bDoQuery = true;
+ }
+ nIndex++;
+ nRow++;
+ }
+ while (bValid && (nRow <= nRow2) /* && (nIndex < MAXQUERY) */ );
+ return bValid;
+}
+
+bool ScTable::CreateQueryParam(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, ScQueryParam& rQueryParam)
+{
+ SCSIZE i, nCount;
+ PutInOrder(nCol1, nCol2);
+ PutInOrder(nRow1, nRow2);
+
+ nCount = rQueryParam.GetEntryCount();
+ for (i=0; i < nCount; i++)
+ rQueryParam.GetEntry(i).Clear();
+
+ // Standard query table
+ bool bValid = CreateStarQuery(nCol1, nRow1, nCol2, nRow2, rQueryParam);
+ // Excel Query table
+ if (!bValid)
+ bValid = CreateExcelQuery(nCol1, nRow1, nCol2, nRow2, rQueryParam);
+
+ SvNumberFormatter* pFormatter = rDocument.GetFormatTable();
+ nCount = rQueryParam.GetEntryCount();
+ if (bValid)
+ {
+ // query type must be set
+ for (i=0; i < nCount; i++)
+ {
+ ScQueryEntry::Item& rItem = rQueryParam.GetEntry(i).GetQueryItem();
+ sal_uInt32 nIndex = 0;
+ bool bNumber = pFormatter->IsNumberFormat(
+ rItem.maString.getString(), nIndex, rItem.mfVal);
+ bool bDateFormat = false;
+ rItem.meType = bNumber && CanOptimizeQueryStringToNumber( pFormatter, nIndex, bDateFormat )
+ ? ScQueryEntry::ByValue : (bDateFormat ? ScQueryEntry::ByDate : ScQueryEntry::ByString);
+ }
+ }
+ else
+ {
+ for (i=0; i < nCount; i++)
+ rQueryParam.GetEntry(i).Clear();
+ }
+ return bValid;
+}
+
+bool ScTable::HasColHeader( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow) const
+{
+ if (nStartRow == nEndRow)
+ // Assume only data.
+ /* XXX NOTE: previous behavior still checked this one row and could
+ * evaluate it has header row, but that doesn't make much sense. */
+ return false;
+
+ if (nStartCol == nEndCol)
+ {
+ CellType eFirstCellType = GetCellType(nStartCol, nStartRow);
+ CellType eSecondCellType = GetCellType(nStartCol, nStartRow+1);
+ return ((eFirstCellType == CELLTYPE_STRING || eFirstCellType == CELLTYPE_EDIT) &&
+ (eSecondCellType != CELLTYPE_STRING && eSecondCellType != CELLTYPE_EDIT));
+ }
+
+ for (SCCOL nCol=nStartCol; nCol<=nEndCol; nCol++)
+ {
+ CellType eType = GetCellType( nCol, nStartRow );
+ // Any non-text cell in first row => not headers.
+ if (eType != CELLTYPE_STRING && eType != CELLTYPE_EDIT)
+ return false;
+ }
+
+ // First row all text cells, any non-text cell in second row => headers.
+ SCROW nTestRow = nStartRow + 1;
+ for (SCCOL nCol=nStartCol; nCol<=nEndCol; nCol++)
+ {
+ CellType eType = GetCellType( nCol, nTestRow );
+ if (eType != CELLTYPE_STRING && eType != CELLTYPE_EDIT)
+ return true;
+ }
+
+ // Also second row all text cells => first row not headers.
+ return false;
+}
+
+bool ScTable::HasRowHeader( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow ) const
+{
+ if (nStartCol == nEndCol)
+ // Assume only data.
+ /* XXX NOTE: previous behavior still checked this one column and could
+ * evaluate it has header column, but that doesn't make much sense. */
+ return false;
+
+ if (nStartRow == nEndRow)
+ {
+ CellType eFirstCellType = GetCellType(nStartCol, nStartRow);
+ CellType eSecondCellType = GetCellType(nStartCol+1, nStartRow);
+ return ((eFirstCellType == CELLTYPE_STRING || eFirstCellType == CELLTYPE_EDIT) &&
+ (eSecondCellType != CELLTYPE_STRING && eSecondCellType != CELLTYPE_EDIT));
+ }
+
+ for (SCROW nRow=nStartRow; nRow<=nEndRow; nRow++)
+ {
+ CellType eType = GetCellType( nStartCol, nRow );
+ // Any non-text cell in first column => not headers.
+ if (eType != CELLTYPE_STRING && eType != CELLTYPE_EDIT)
+ return false;
+ }
+
+ // First column all text cells, any non-text cell in second column => headers.
+ SCCOL nTestCol = nStartCol + 1;
+ for (SCROW nRow=nStartRow; nRow<=nEndRow; nRow++)
+ {
+ CellType eType = GetCellType( nRow, nTestCol );
+ if (eType != CELLTYPE_STRING && eType != CELLTYPE_EDIT)
+ return true;
+ }
+
+ // Also second column all text cells => first column not headers.
+ return false;
+}
+
+void ScTable::GetFilterEntries( SCCOL nCol, SCROW nRow1, SCROW nRow2, ScFilterEntries& rFilterEntries, bool bFiltering )
+{
+ if (nCol >= aCol.size())
+ return;
+
+ sc::ColumnBlockConstPosition aBlockPos;
+ aCol[nCol].InitBlockPosition(aBlockPos);
+ aCol[nCol].GetFilterEntries(aBlockPos, nRow1, nRow2, rFilterEntries, bFiltering);
+}
+
+void ScTable::GetFilteredFilterEntries(
+ SCCOL nCol, SCROW nRow1, SCROW nRow2, const ScQueryParam& rParam, ScFilterEntries& rFilterEntries, bool bFiltering )
+{
+ if (nCol >= aCol.size())
+ return;
+
+ sc::ColumnBlockConstPosition aBlockPos;
+ aCol[nCol].InitBlockPosition(aBlockPos);
+
+ // remove the entry for this column from the query parameter
+ ScQueryParam aParam( rParam );
+ aParam.RemoveEntryByField(nCol);
+
+ lcl_PrepareQuery(&rDocument, this, aParam, true);
+ ScQueryEvaluator queryEvaluator(GetDoc(), *this, aParam);
+ for ( SCROW j = nRow1; j <= nRow2; ++j )
+ {
+ if (queryEvaluator.ValidQuery(j))
+ {
+ aCol[nCol].GetFilterEntries(aBlockPos, j, j, rFilterEntries, bFiltering);
+ }
+ }
+}
+
+bool ScTable::GetDataEntries(SCCOL nCol, SCROW nRow, std::set<ScTypedStrData>& rStrings)
+{
+ if (!ValidCol(nCol) || nCol >= GetAllocatedColumnsCount())
+ return false;
+ return aCol[nCol].GetDataEntries( nRow, rStrings);
+}
+
+sal_uInt64 ScTable::GetCellCount() const
+{
+ sal_uInt64 nCellCount = 0;
+
+ for ( SCCOL nCol=0; nCol < aCol.size(); nCol++ )
+ nCellCount += aCol[nCol].GetCellCount();
+
+ return nCellCount;
+}
+
+sal_uInt64 ScTable::GetWeightedCount() const
+{
+ sal_uInt64 nCellCount = 0;
+
+ for ( SCCOL nCol=0; nCol < aCol.size(); nCol++ )
+ nCellCount += aCol[nCol].GetWeightedCount();
+
+ return nCellCount;
+}
+
+sal_uInt64 ScTable::GetWeightedCount(SCROW nStartRow, SCROW nEndRow) const
+{
+ sal_uInt64 nCellCount = 0;
+
+ for ( SCCOL nCol=0; nCol < aCol.size(); nCol++ )
+ nCellCount += aCol[nCol].GetWeightedCount(nStartRow, nEndRow);
+
+ return nCellCount;
+}
+
+sal_uInt64 ScTable::GetCodeCount() const
+{
+ sal_uInt64 nCodeCount = 0;
+
+ for ( SCCOL nCol=0; nCol < aCol.size(); nCol++ )
+ if ( aCol[nCol].GetCellCount() )
+ nCodeCount += aCol[nCol].GetCodeCount();
+
+ return nCodeCount;
+}
+
+sal_Int32 ScTable::GetMaxStringLen( SCCOL nCol, SCROW nRowStart,
+ SCROW nRowEnd, rtl_TextEncoding eCharSet ) const
+{
+ if ( IsColValid( nCol ) )
+ return aCol[nCol].GetMaxStringLen( nRowStart, nRowEnd, eCharSet );
+ else
+ return 0;
+}
+
+sal_Int32 ScTable::GetMaxNumberStringLen(
+ sal_uInt16& nPrecision, SCCOL nCol, SCROW nRowStart, SCROW nRowEnd ) const
+{
+ if ( IsColValid( nCol ) )
+ return aCol[nCol].GetMaxNumberStringLen( nPrecision, nRowStart, nRowEnd );
+ else
+ return 0;
+}
+
+void ScTable::UpdateSelectionFunction( ScFunctionData& rData, const ScMarkData& rMark )
+{
+ ScRangeList aRanges = rMark.GetMarkedRangesForTab( nTab );
+ ScRange aMarkArea( ScAddress::UNINITIALIZED );
+ if (rMark.IsMultiMarked())
+ aMarkArea = rMark.GetMultiMarkArea();
+ else if (rMark.IsMarked())
+ aMarkArea = rMark.GetMarkArea();
+ else
+ {
+ assert(!"ScTable::UpdateSelectionFunction - called without anything marked");
+ aMarkArea.aStart.SetCol(0);
+ aMarkArea.aEnd.SetCol(rDocument.MaxCol());
+ }
+ const SCCOL nStartCol = aMarkArea.aStart.Col();
+ const SCCOL nEndCol = ClampToAllocatedColumns(aMarkArea.aEnd.Col());
+ for (SCCOL nCol = nStartCol; nCol <= nEndCol && !rData.getError(); ++nCol)
+ {
+ if (mpColFlags && ColHidden(nCol))
+ continue;
+
+ aCol[nCol].UpdateSelectionFunction(aRanges, rData, *mpHiddenRows);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/table4.cxx b/sc/source/core/data/table4.cxx
new file mode 100644
index 000000000..f6f926c86
--- /dev/null
+++ b/sc/source/core/data/table4.cxx
@@ -0,0 +1,2990 @@
+/* -*- 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/string.hxx>
+#include <editeng/boxitem.hxx>
+#include <editeng/editeng.hxx>
+#include <editeng/eeitem.hxx>
+#include <editeng/escapementitem.hxx>
+#include <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+#include <vcl/keycodes.hxx>
+#include <rtl/math.hxx>
+#include <unotools/charclass.hxx>
+#include <osl/diagnose.h>
+
+#include <attrib.hxx>
+#include <patattr.hxx>
+#include <formulacell.hxx>
+#include <table.hxx>
+#include <global.hxx>
+#include <document.hxx>
+#include <autoform.hxx>
+#include <userlist.hxx>
+#include <zforauto.hxx>
+#include <subtotal.hxx>
+#include <formula/errorcodes.hxx>
+#include <docpool.hxx>
+#include <progress.hxx>
+#include <conditio.hxx>
+#include <editutil.hxx>
+#include <listenercontext.hxx>
+#include <scopetools.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <math.h>
+#include <memory>
+#include <list>
+#include <string_view>
+
+#define D_MAX_LONG_ double(0x7fffffff)
+
+namespace {
+
+short lcl_DecompValueString( OUString& rValue, sal_Int32& nVal, sal_uInt16* pMinDigits = nullptr )
+{
+ if ( rValue.isEmpty() )
+ {
+ nVal = 0;
+ return 0;
+ }
+ const sal_Unicode* p = rValue.getStr();
+ sal_Int32 nSign = 0;
+ sal_Int32 nNum = 0;
+ if ( p[nNum] == '-' || p[nNum] == '+' )
+ nNum = nSign = 1;
+ while ( p[nNum] && CharClass::isAsciiNumeric( std::u16string_view(&p[nNum], 1) ) )
+ nNum++;
+
+ sal_Unicode cNext = p[nNum]; // 0 if at the end
+ sal_Unicode cLast = p[rValue.getLength()-1];
+
+ // #i5550# If there are numbers at the beginning and the end,
+ // prefer the one at the beginning only if it's followed by a space.
+ // Otherwise, use the number at the end, to enable things like IP addresses.
+ if ( nNum > nSign && ( cNext == 0 || cNext == ' ' || !CharClass::isAsciiNumeric(std::u16string_view(&cLast, 1)) ) )
+ { // number at the beginning
+ nVal = o3tl::toInt32(rValue.subView( 0, nNum ));
+ // any number with a leading zero sets the minimum number of digits
+ if ( p[nSign] == '0' && pMinDigits && ( nNum - nSign > *pMinDigits ) )
+ *pMinDigits = nNum - nSign;
+ rValue = rValue.copy(nNum);
+ return -1;
+ }
+ else
+ {
+ nSign = 0;
+ sal_Int32 nEnd = nNum = rValue.getLength() - 1;
+ while ( nNum && CharClass::isAsciiNumeric( std::u16string_view(&p[nNum], 1) ) )
+ nNum--;
+ if ( p[nNum] == '-' || p[nNum] == '+' )
+ {
+ nNum--;
+ nSign = 1;
+ }
+ if ( nNum < nEnd - nSign )
+ { // number at the end
+ nVal = o3tl::toInt32(rValue.subView( nNum + 1 ));
+ // any number with a leading zero sets the minimum number of digits
+ if ( p[nNum+1+nSign] == '0' && pMinDigits && ( nEnd - nNum - nSign > *pMinDigits ) )
+ *pMinDigits = nEnd - nNum - nSign;
+ rValue = rValue.copy(0, nNum + 1);
+ if (nSign) // use the return value = 2 to put back the '+'
+ return 2;
+ else
+ return 1;
+ }
+ }
+ nVal = 0;
+ return 0;
+}
+
+OUString lcl_ValueString( sal_Int32 nValue, sal_uInt16 nMinDigits )
+{
+ if ( nMinDigits <= 1 )
+ return OUString::number( nValue ); // simple case...
+ else
+ {
+ OUString aStr = OUString::number( std::abs( nValue ) );
+ if ( aStr.getLength() < nMinDigits )
+ {
+ OUStringBuffer aZero(nMinDigits);
+ comphelper::string::padToLength(aZero, nMinDigits - aStr.getLength(), '0');
+ aStr = aZero.append(aStr).makeStringAndClear();
+ }
+ // nMinDigits doesn't include the '-' sign -> add after inserting zeros
+ if ( nValue < 0 )
+ aStr = "-" + aStr;
+ return aStr;
+ }
+}
+
+void setSuffixCell(
+ ScColumn& rColumn, SCROW nRow, sal_Int32 nValue, sal_uInt16 nDigits,
+ std::u16string_view rSuffix,
+ CellType eCellType, bool bIsOrdinalSuffix )
+{
+ ScDocument& rDoc = rColumn.GetDoc();
+ OUString aValue = lcl_ValueString(nValue, nDigits);
+ if (!bIsOrdinalSuffix)
+ {
+ aValue += rSuffix;
+ rColumn.SetRawString(nRow, aValue);
+ return;
+ }
+
+ OUString aOrdinalSuffix = ScGlobal::GetOrdinalSuffix(nValue);
+ if (eCellType != CELLTYPE_EDIT)
+ {
+ aValue += aOrdinalSuffix;
+ rColumn.SetRawString(nRow, aValue);
+ return;
+ }
+
+ EditEngine aEngine(rDoc.GetEnginePool());
+ aEngine.SetEditTextObjectPool(rDoc.GetEditPool());
+
+ SfxItemSet aAttr = aEngine.GetEmptyItemSet();
+ aAttr.Put( SvxEscapementItem( SvxEscapement::Superscript, EE_CHAR_ESCAPEMENT));
+ aEngine.SetText( aValue );
+ aEngine.QuickInsertText(
+ aOrdinalSuffix,
+ ESelection(0, aValue.getLength(), 0, aValue.getLength() + aOrdinalSuffix.getLength()));
+
+ aEngine.QuickSetAttribs(
+ aAttr,
+ ESelection(0, aValue.getLength(), 0, aValue.getLength() + aOrdinalSuffix.getLength()));
+
+ // Text object instance will be owned by the cell.
+ rColumn.SetEditText(nRow, aEngine.CreateTextObject());
+}
+
+}
+
+namespace {
+/* TODO: move this to rtl::math::approxDiff() ? Though the name is funny, the
+ * approx is expected to be more correct than the raw diff. */
+/** Calculate a-b trying to diminish precision errors such as for 0.11-0.12
+ not return -0.009999999999999995 but -0.01 instead.
+ */
+double approxDiff( double a, double b )
+{
+ if (a == b)
+ return 0.0;
+ if (a == 0.0)
+ return -b;
+ if (b == 0.0)
+ return a;
+ const double c = a - b;
+ const double aa = fabs(a);
+ const double ab = fabs(b);
+ if (aa < 1e-16 || aa > 1e+16 || ab < 1e-16 || ab > 1e+16)
+ // This is going nowhere, live with the result.
+ return c;
+
+ const double q = aa < ab ? b / a : a / b;
+ const double d = (a * q - b * q) / q;
+ if (d == c)
+ // No differing error, live with the result.
+ return c;
+
+ // We now have two subtractions with a similar but not equal error. Obtain
+ // the exponent of the error magnitude and round accordingly.
+ const double e = fabs(d - c);
+ const int nExp = static_cast<int>(floor(log10(e))) + 1;
+ // tdf#129606: Limit precision to the 16th significant digit of the least precise argument.
+ // Cf. mnMaxGeneralPrecision in sc/source/core/data/column3.cxx.
+ const int nExpArg = static_cast<int>(floor(log10(std::max(aa, ab)))) - 15;
+ return rtl::math::round(c, -std::max(nExp, nExpArg));
+}
+
+double approxTimeDiff( double a, double b )
+{
+ // Scale to hours, round to "nanohours" (multiple nanoseconds), scale back.
+ // Get back 0.0416666666666667 instead of 0.041666666700621136 or
+ // 0.041666666664241347 (raw a-b) for one hour, or worse the approxDiff()
+ // 0.041666666659999997 value. Though there is no such correct value,
+ // IEEE-754 nearest values are
+ // 0.041666666666666664353702032030923874117434024810791015625
+ // (0x3FA5555555555555) and
+ // 0.04166666666666667129259593593815225176513195037841796875
+ // (0x3FA5555555555556).
+ // This works also for a diff of seconds, unless corner cases would be
+ // discovered, which would make it necessary to ditch the floating point
+ // and convert to/from time structure values instead.
+ return rtl::math::round((a - b) * 24, 9) / 24;
+}
+
+double approxTypedDiff( double a, double b, bool bTime )
+{
+ return bTime ? approxTimeDiff( a, b) : approxDiff( a, b);
+}
+}
+
+void ScTable::FillAnalyse( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ FillCmd& rCmd, FillDateCmd& rDateCmd,
+ double& rInc, sal_uInt16& rMinDigits,
+ ScUserListData*& rListData, sal_uInt16& rListIndex,
+ bool bHasFiltered, bool& rSkipOverlappedCells,
+ std::vector<sal_Int32>& rNonOverlappedCellIdx)
+{
+ OSL_ENSURE( nCol1==nCol2 || nRow1==nRow2, "FillAnalyse: invalid range" );
+
+ rInc = 0.0;
+ rMinDigits = 0;
+ rListData = nullptr;
+ rCmd = FILL_SIMPLE;
+ rSkipOverlappedCells = false;
+ if ( nScFillModeMouseModifier & KEY_MOD1 )
+ return ; // Ctrl-key: Copy
+
+ SCCOL nAddX;
+ SCROW nAddY;
+ SCSIZE nCount;
+ if (nCol1 == nCol2)
+ {
+ nAddX = 0;
+ nAddY = 1;
+ nCount = static_cast<SCSIZE>(nRow2 - nRow1 + 1);
+ }
+ else
+ {
+ nAddX = 1;
+ nAddY = 0;
+ nCount = static_cast<SCSIZE>(nCol2 - nCol1 + 1);
+ }
+
+ // Try to analyse the merged cells only if there are no filtered rows in the destination area
+ // Else fallback to the old way to avoid regression.
+ // Filling merged cells into an area with filtered (hidden) rows, is a very complex task
+ // that is not implemented, but not even decided how to do, even excel can't handle that well
+ if (!bHasFiltered)
+ {
+ bool bHasOverlappedCells = false;
+ bool bSkipOverlappedCells = true;
+ SCCOL nColCurr = nCol1;
+ SCROW nRowCurr = nRow1;
+
+ // collect cells that are not empty or not overlapped
+ rNonOverlappedCellIdx.resize(nCount);
+ SCSIZE nValueCount = 0;
+ for (SCSIZE i = 0; i < nCount; ++i)
+ {
+ const ScPatternAttr* pPattern = GetPattern(nColCurr, nRowCurr);
+ bool bOverlapped
+ = pPattern->GetItemSet().GetItemState(ATTR_MERGE_FLAG, false) == SfxItemState::SET
+ && pPattern->GetItem(ATTR_MERGE_FLAG).IsOverlapped();
+
+ if (bOverlapped)
+ bHasOverlappedCells = true;
+
+ if (!bOverlapped || GetCellValue(nColCurr, nRowCurr).meType != CELLTYPE_NONE)
+ {
+ rNonOverlappedCellIdx[nValueCount++] = i;
+ // if there is at least 1 non empty overlapped cell, then no cell should be skipped
+ if (bOverlapped)
+ bSkipOverlappedCells = false;
+ }
+
+ nColCurr += nAddX;
+ nRowCurr += nAddY;
+ }
+ rNonOverlappedCellIdx.resize(nValueCount);
+
+ // if all the values are overlapped CELLTYPE_NONE, then there is no need to analyse it.
+ if (nValueCount == 0)
+ return;
+
+ // if there is no overlapped cells, there is nothing to skip
+ if (!bHasOverlappedCells)
+ bSkipOverlappedCells = false;
+
+ if (bSkipOverlappedCells)
+ {
+ nColCurr = nCol1 + rNonOverlappedCellIdx[0] * nAddX;
+ nRowCurr = nRow1 + rNonOverlappedCellIdx[0] * nAddY;
+ ScRefCellValue aPrevCell, aCurrCell;
+ aCurrCell = GetCellValue(nColCurr, nRowCurr);
+ CellType eCellType = aCurrCell.meType;
+ if (eCellType == CELLTYPE_VALUE)
+ {
+ bool bVal = true;
+ double fVal;
+ SvNumFormatType nCurrCellFormatType
+ = rDocument.GetFormatTable()->GetType(GetNumberFormat(nColCurr, nRowCurr));
+ if (nCurrCellFormatType == SvNumFormatType::DATE)
+ {
+ if (nValueCount >= 2)
+ {
+ tools::Long nCmpInc = 0;
+ FillDateCmd eType = FILL_YEAR; // just some temporary default values
+ tools::Long nDDiff = 0, nMDiff = 0, nYDiff = 0; // to avoid warnings
+ Date aNullDate = rDocument.GetFormatTable()->GetNullDate();
+ Date aCurrDate = aNullDate, aPrevDate = aNullDate;
+ aCurrDate.AddDays(aCurrCell.mfValue);
+ for (SCSIZE i = 1; i < nValueCount && bVal; i++)
+ {
+ aPrevCell = aCurrCell;
+ aPrevDate = aCurrDate;
+ nColCurr = nCol1 + rNonOverlappedCellIdx[i] * nAddX;
+ nRowCurr = nRow1 + rNonOverlappedCellIdx[i] * nAddY;
+ aCurrCell = GetCellValue(nColCurr, nRowCurr);
+ if (aCurrCell.meType == CELLTYPE_VALUE)
+ {
+ aCurrDate = aNullDate + static_cast<sal_Int32>(aCurrCell.mfValue);
+ if (eType != FILL_DAY) {
+ nDDiff = aCurrDate.GetDay()
+ - static_cast<tools::Long>(aPrevDate.GetDay());
+ nMDiff = aCurrDate.GetMonth()
+ - static_cast<tools::Long>(aPrevDate.GetMonth());
+ nYDiff = aCurrDate.GetYear()
+ - static_cast<tools::Long>(aPrevDate.GetYear());
+ }
+ if (i == 1)
+ {
+ if (nDDiff != 0)
+ {
+ eType = FILL_DAY;
+ nCmpInc = aCurrDate - aPrevDate;
+ }
+ else
+ {
+ eType = FILL_MONTH;
+ nCmpInc = nMDiff + 12 * nYDiff;
+ }
+ }
+ else if (eType == FILL_DAY)
+ {
+ if (aCurrDate - aPrevDate != nCmpInc)
+ bVal = false;
+ }
+ else
+ {
+ if (nDDiff || (nMDiff + 12 * nYDiff != nCmpInc))
+ bVal = false;
+ }
+ }
+ else
+ bVal = false; // No date is also not ok
+ }
+ if (bVal)
+ {
+ if (eType == FILL_MONTH && (nCmpInc % 12 == 0))
+ {
+ eType = FILL_YEAR;
+ nCmpInc /= 12;
+ }
+ rCmd = FILL_DATE;
+ rDateCmd = eType;
+ rInc = nCmpInc;
+ rSkipOverlappedCells = true;
+ return;
+ }
+ }
+ else
+ {
+ rCmd = FILL_DATE;
+ rDateCmd = FILL_DAY;
+ rInc = 1.0;
+ rSkipOverlappedCells = true;
+ return;
+ }
+ }
+ else if (nCurrCellFormatType == SvNumFormatType::LOGICAL
+ && ((fVal = aCurrCell.mfValue) == 0.0 || fVal == 1.0))
+ {
+ }
+ else if (nValueCount >= 2)
+ {
+ for (SCSIZE i = 1; i < nValueCount && bVal; i++)
+ {
+ aPrevCell = aCurrCell;
+ nColCurr = nCol1 + rNonOverlappedCellIdx[i] * nAddX;
+ nRowCurr = nRow1 + rNonOverlappedCellIdx[i] * nAddY;
+ aCurrCell = GetCellValue(nColCurr, nRowCurr);
+ if (aCurrCell.meType == CELLTYPE_VALUE)
+ {
+ double nDiff = approxTypedDiff(aCurrCell.mfValue, aPrevCell.mfValue,
+ (nCurrCellFormatType == SvNumFormatType::TIME ||
+ nCurrCellFormatType == SvNumFormatType::DATETIME));
+ if (i == 1)
+ rInc = nDiff;
+ if (!::rtl::math::approxEqual(nDiff, rInc, 13))
+ bVal = false;
+ else if ((aCurrCell.mfValue == 0.0 || aCurrCell.mfValue == 1.0)
+ && (rDocument.GetFormatTable()->GetType(
+ GetNumberFormat(nColCurr, nRowCurr))
+ == SvNumFormatType::LOGICAL))
+ bVal = false;
+ }
+ else
+ bVal = false;
+ }
+ if (bVal)
+ {
+ rCmd = FILL_LINEAR;
+ rSkipOverlappedCells = true;
+ return;
+ }
+ }
+ }
+ else if (eCellType == CELLTYPE_STRING || eCellType == CELLTYPE_EDIT)
+ {
+ OUString aStr = GetString(nColCurr, nRowCurr );
+ OUString aStr2;
+
+ rListData = const_cast<ScUserListData*>(ScGlobal::GetUserList()->GetData(aStr));
+ if (rListData)
+ {
+ bool bMatchCase = false;
+ (void)rListData->GetSubIndex(aStr, rListIndex, bMatchCase);
+ size_t nListStrCount = rListData->GetSubCount();
+ sal_uInt16 nPrevListIndex, nInc = 1;
+ for (SCSIZE i = 1; i < nValueCount && rListData; i++)
+ {
+ nColCurr = nCol1 + rNonOverlappedCellIdx[i] * nAddX;
+ nRowCurr = nRow1 + rNonOverlappedCellIdx[i] * nAddY;
+ aStr2 = GetString(nColCurr, nRowCurr);
+
+ nPrevListIndex = rListIndex;
+ if (!rListData->GetSubIndex(aStr2, rListIndex, bMatchCase))
+ rListData = nullptr;
+ else
+ {
+ sal_Int32 nIncCurr = rListIndex - nPrevListIndex;
+ if (nIncCurr < 0)
+ nIncCurr += nListStrCount;
+ if (i == 1)
+ nInc = nIncCurr;
+ else if (nInc != nIncCurr)
+ rListData = nullptr;
+ }
+ }
+ if (rListData) {
+ rInc = nInc;
+ rSkipOverlappedCells = true;
+ return;
+ }
+ }
+ short nFlag1, nFlag2;
+ sal_Int32 nVal1, nVal2;
+ nFlag1 = lcl_DecompValueString(aStr, nVal1, &rMinDigits);
+ if (nFlag1)
+ {
+ bool bVal = true;
+ rInc = 1;
+ for (SCSIZE i = 1; i < nValueCount && bVal; i++)
+ {
+ nColCurr = nCol1 + rNonOverlappedCellIdx[i] * nAddX;
+ nRowCurr = nRow1 + rNonOverlappedCellIdx[i] * nAddY;
+ ScRefCellValue aCell = GetCellValue(nColCurr, nRowCurr);
+ CellType eType = aCell.meType;
+ if (eType == CELLTYPE_STRING || eType == CELLTYPE_EDIT)
+ {
+ aStr2 = aCell.getString(&rDocument);
+ nFlag2 = lcl_DecompValueString(aStr2, nVal2, &rMinDigits);
+ if (nFlag1 == nFlag2 && aStr == aStr2)
+ {
+ double nDiff = approxDiff(nVal2, nVal1);
+ if (i == 1)
+ rInc = nDiff;
+ else if (!::rtl::math::approxEqual(nDiff, rInc, 13))
+ bVal = false;
+ nVal1 = nVal2;
+ }
+ else
+ bVal = false;
+ }
+ else
+ bVal = false;
+ }
+ if (bVal)
+ {
+ rCmd = FILL_LINEAR;
+ rSkipOverlappedCells = true;
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ //if it is not a FILL_LINEAR - CELLTYPE_VALUE - with merged cells [without hidden values]
+ //then do it in the old way
+
+ SCCOL nCol = nCol1;
+ SCROW nRow = nRow1;
+
+ ScRefCellValue aFirstCell = GetCellValue(nCol, nRow);
+ CellType eCellType = aFirstCell.meType;
+
+ if (eCellType == CELLTYPE_VALUE)
+ {
+ double fVal;
+ sal_uInt32 nFormat = GetAttr(nCol,nRow,ATTR_VALUE_FORMAT)->GetValue();
+ const SvNumFormatType nFormatType = rDocument.GetFormatTable()->GetType(nFormat);
+ bool bDate = (nFormatType == SvNumFormatType::DATE); // date without time
+ bool bTime = (nFormatType == SvNumFormatType::TIME || nFormatType == SvNumFormatType::DATETIME);
+ bool bBooleanCell = (nFormatType == SvNumFormatType::LOGICAL);
+ if (bDate)
+ {
+ if (nCount > 1)
+ {
+ double nVal;
+ Date aNullDate = rDocument.GetFormatTable()->GetNullDate();
+ Date aDate1 = aNullDate;
+ nVal = aFirstCell.mfValue;
+ aDate1.AddDays(nVal);
+ Date aDate2 = aNullDate;
+ nVal = GetValue(nCol+nAddX, nRow+nAddY);
+ aDate2.AddDays(nVal);
+ if ( aDate1 != aDate2 )
+ {
+ tools::Long nCmpInc = 0;
+ FillDateCmd eType;
+ tools::Long nDDiff = aDate2.GetDay() - static_cast<tools::Long>(aDate1.GetDay());
+ tools::Long nMDiff = aDate2.GetMonth() - static_cast<tools::Long>(aDate1.GetMonth());
+ tools::Long nYDiff = aDate2.GetYear() - static_cast<tools::Long>(aDate1.GetYear());
+ if (nMDiff && aDate1.IsEndOfMonth() && aDate2.IsEndOfMonth())
+ {
+ eType = FILL_END_OF_MONTH;
+ nCmpInc = nMDiff + 12 * nYDiff;
+ }
+ else if (nDDiff)
+ {
+ eType = FILL_DAY;
+ nCmpInc = aDate2 - aDate1;
+ }
+ else
+ {
+ eType = FILL_MONTH;
+ nCmpInc = nMDiff + 12 * nYDiff;
+ }
+
+ nCol = sal::static_int_cast<SCCOL>( nCol + nAddX );
+ nRow = sal::static_int_cast<SCROW>( nRow + nAddY );
+ bool bVal = true;
+ for (SCSIZE i=1; i<nCount && bVal; i++)
+ {
+ ScRefCellValue aCell = GetCellValue(nCol,nRow);
+ if (aCell.meType == CELLTYPE_VALUE)
+ {
+ nVal = aCell.mfValue;
+ aDate2 = aNullDate + static_cast<sal_Int32>(nVal);
+ if ( eType == FILL_DAY )
+ {
+ if ( aDate2-aDate1 != nCmpInc )
+ bVal = false;
+ }
+ else
+ {
+ nDDiff = aDate2.GetDay() - static_cast<tools::Long>(aDate1.GetDay());
+ nMDiff = aDate2.GetMonth() - static_cast<tools::Long>(aDate1.GetMonth());
+ nYDiff = aDate2.GetYear() - static_cast<tools::Long>(aDate1.GetYear());
+ if ((nDDiff && !aDate1.IsEndOfMonth() && !aDate2.IsEndOfMonth())
+ || (nMDiff + 12 * nYDiff != nCmpInc))
+ bVal = false;
+ }
+ aDate1 = aDate2;
+ nCol = sal::static_int_cast<SCCOL>( nCol + nAddX );
+ nRow = sal::static_int_cast<SCROW>( nRow + nAddY );
+ }
+ else
+ bVal = false; // No date is also not ok
+ }
+ if (bVal)
+ {
+ if ((eType == FILL_MONTH || eType == FILL_END_OF_MONTH)
+ && (nCmpInc % 12 == 0))
+ {
+ eType = FILL_YEAR;
+ nCmpInc /= 12;
+ }
+ rCmd = FILL_DATE;
+ rDateCmd = eType;
+ rInc = nCmpInc;
+ }
+ }
+ else
+ {
+ // tdf#89754 - don't increment non different consecutive date cells
+ rCmd = FILL_DATE;
+ rDateCmd = FILL_DAY;
+ rInc = 0.0;
+ }
+ }
+ else // single date -> increment by days
+ {
+ rCmd = FILL_DATE;
+ rDateCmd = FILL_DAY;
+ rInc = 1.0;
+ }
+ }
+ else if (bBooleanCell && ((fVal = aFirstCell.mfValue) == 0.0 || fVal == 1.0))
+ {
+ // Nothing, rInc stays 0.0, no specific fill mode.
+ }
+ else
+ {
+ if (nCount > 1)
+ {
+ double nVal1 = aFirstCell.mfValue;
+ double nVal2 = GetValue(nCol+nAddX, nRow+nAddY);
+ rInc = approxTypedDiff( nVal2, nVal1, bTime);
+ nCol = sal::static_int_cast<SCCOL>( nCol + nAddX );
+ nRow = sal::static_int_cast<SCROW>( nRow + nAddY );
+ bool bVal = true;
+ for (SCSIZE i=1; i<nCount && bVal; i++)
+ {
+ ScRefCellValue aCell = GetCellValue(nCol,nRow);
+ if (aCell.meType == CELLTYPE_VALUE)
+ {
+ nVal2 = aCell.mfValue;
+ double nDiff = approxTypedDiff( nVal2, nVal1, bTime);
+ if ( !::rtl::math::approxEqual( nDiff, rInc, 13 ) )
+ bVal = false;
+ else if ((nVal2 == 0.0 || nVal2 == 1.0) &&
+ (rDocument.GetFormatTable()->GetType(GetNumberFormat(nCol,nRow)) ==
+ SvNumFormatType::LOGICAL))
+ bVal = false;
+ nVal1 = nVal2;
+ }
+ else
+ bVal = false;
+ nCol = sal::static_int_cast<SCCOL>( nCol + nAddX );
+ nRow = sal::static_int_cast<SCROW>( nRow + nAddY );
+ }
+ if (bVal)
+ rCmd = FILL_LINEAR;
+ }
+ else if(nFormatType == SvNumFormatType::PERCENT)
+ {
+ rInc = 0.01; // tdf#89998 increment by 1% at a time
+ }
+ }
+ }
+ else if (eCellType == CELLTYPE_STRING || eCellType == CELLTYPE_EDIT)
+ {
+ OUString aStr = GetString(nCol, nRow);
+
+ rListData = const_cast<ScUserListData*>(ScGlobal::GetUserList()->GetData(aStr));
+ if (rListData)
+ {
+ bool bMatchCase = false;
+ (void)rListData->GetSubIndex(aStr, rListIndex, bMatchCase);
+ size_t nListStrCount = rListData->GetSubCount();
+ sal_uInt16 nPrevListIndex, nInc = 1;
+ nCol = sal::static_int_cast<SCCOL>( nCol + nAddX );
+ nRow = sal::static_int_cast<SCROW>( nRow + nAddY );
+ for (SCSIZE i=1; i<nCount && rListData; i++)
+ {
+ nPrevListIndex = rListIndex;
+ aStr = GetString(nCol, nRow);
+ if (!rListData->GetSubIndex(aStr, rListIndex, bMatchCase))
+ rListData = nullptr;
+ else
+ {
+ sal_Int32 nIncCurr = rListIndex - nPrevListIndex;
+ if (nIncCurr < 0)
+ nIncCurr += nListStrCount;
+ if (i == 1)
+ nInc = nIncCurr;
+ else if (nInc != nIncCurr)
+ rListData = nullptr;
+ }
+ nCol = sal::static_int_cast<SCCOL>( nCol + nAddX );
+ nRow = sal::static_int_cast<SCROW>( nRow + nAddY );
+ }
+ if (rListData)
+ rInc = nInc;
+ }
+ else if ( nCount > 1 )
+ {
+ // pass rMinDigits to all DecompValueString calls
+ // -> longest number defines rMinDigits
+
+ sal_Int32 nVal1;
+ short nFlag1 = lcl_DecompValueString( aStr, nVal1, &rMinDigits );
+ if ( nFlag1 )
+ {
+ sal_Int32 nVal2;
+ aStr = GetString( nCol+nAddX, nRow+nAddY );
+ short nFlag2 = lcl_DecompValueString( aStr, nVal2, &rMinDigits );
+ if ( nFlag1 == nFlag2 )
+ {
+ rInc = approxDiff( nVal2, nVal1);
+ nCol = sal::static_int_cast<SCCOL>( nCol + nAddX );
+ nRow = sal::static_int_cast<SCROW>( nRow + nAddY );
+ bool bVal = true;
+ for (SCSIZE i=1; i<nCount && bVal; i++)
+ {
+ ScRefCellValue aCell = GetCellValue(nCol, nRow);
+ CellType eType = aCell.meType;
+ if ( eType == CELLTYPE_STRING || eType == CELLTYPE_EDIT )
+ {
+ aStr = aCell.getString(&rDocument);
+ nFlag2 = lcl_DecompValueString( aStr, nVal2, &rMinDigits );
+ if ( nFlag1 == nFlag2 )
+ {
+ double nDiff = approxDiff( nVal2, nVal1);
+ if ( !::rtl::math::approxEqual( nDiff, rInc, 13 ) )
+ bVal = false;
+ nVal1 = nVal2;
+ }
+ else
+ bVal = false;
+ }
+ else
+ bVal = false;
+ nCol = sal::static_int_cast<SCCOL>( nCol + nAddX );
+ nRow = sal::static_int_cast<SCROW>( nRow + nAddY );
+ }
+ if (bVal)
+ rCmd = FILL_LINEAR;
+ }
+ }
+ }
+ else
+ {
+ // call DecompValueString to set rMinDigits
+ sal_Int32 nDummy;
+ lcl_DecompValueString( aStr, nDummy, &rMinDigits );
+ }
+ }
+}
+
+void ScTable::FillFormula(
+ const ScFormulaCell* pSrcCell, SCCOL nDestCol, SCROW nDestRow, bool bLast )
+{
+
+ rDocument.SetNoListening( true ); // still the wrong reference
+ ScAddress aAddr( nDestCol, nDestRow, nTab );
+ ScFormulaCell* pDestCell = new ScFormulaCell( *pSrcCell, rDocument, aAddr );
+ aCol[nDestCol].SetFormulaCell(nDestRow, pDestCell);
+
+ if ( bLast && pDestCell->GetMatrixFlag() != ScMatrixMode::NONE )
+ {
+ ScAddress aOrg;
+ if ( pDestCell->GetMatrixOrigin( GetDoc(), aOrg ) )
+ {
+ if ( nDestCol >= aOrg.Col() && nDestRow >= aOrg.Row() )
+ {
+ ScFormulaCell* pOrgCell = rDocument.GetFormulaCell(aOrg);
+ if (pOrgCell && pOrgCell->GetMatrixFlag() == ScMatrixMode::Formula)
+ {
+ pOrgCell->SetMatColsRows(
+ nDestCol - aOrg.Col() + 1,
+ nDestRow - aOrg.Row() + 1 );
+ }
+ else
+ {
+ OSL_FAIL( "FillFormula: MatrixOrigin no formula cell with ScMatrixMode::Formula" );
+ }
+ }
+ else
+ {
+ OSL_FAIL( "FillFormula: MatrixOrigin bottom right" );
+ }
+ }
+ else
+ {
+ OSL_FAIL( "FillFormula: no MatrixOrigin" );
+ }
+ }
+ rDocument.SetNoListening( false );
+ pDestCell->StartListeningTo( rDocument );
+}
+
+void ScTable::FillAuto( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ sal_uInt64 nFillCount, FillDir eFillDir, ScProgress* pProgress )
+{
+ if ( (nFillCount == 0) || !ValidColRow(nCol1, nRow1) || !ValidColRow(nCol2, nRow2) )
+ return;
+
+ // Detect direction
+
+ bool bVertical = (eFillDir == FILL_TO_BOTTOM || eFillDir == FILL_TO_TOP);
+ bool bPositive = (eFillDir == FILL_TO_BOTTOM || eFillDir == FILL_TO_RIGHT);
+
+ SCCOLROW nCol = 0;
+ SCCOLROW nRow = 0;
+ SCCOLROW& rInner = bVertical ? nRow : nCol; // loop variables
+ SCCOLROW& rOuter = bVertical ? nCol : nRow;
+ SCCOLROW nOStart;
+ SCCOLROW nOEnd;
+ SCCOLROW nIStart;
+ SCCOLROW nIEnd;
+ SCCOLROW nISrcStart;
+ SCCOLROW nISrcEnd;
+ ScRange aFillRange;
+
+ if (bVertical)
+ {
+ nOStart = nCol1;
+ nOEnd = nCol2;
+ if (bPositive)
+ {
+ nISrcStart = nRow1;
+ nISrcEnd = nRow2;
+ nIStart = nRow2 + 1;
+ nIEnd = nRow2 + nFillCount;
+ aFillRange = ScRange(nCol1, nRow2+1, 0, nCol2, nRow2 + nFillCount, 0);
+ }
+ else
+ {
+ nISrcStart = nRow2;
+ nISrcEnd = nRow1;
+ nIStart = nRow1 - 1;
+ nIEnd = nRow1 - nFillCount;
+ aFillRange = ScRange(nCol1, nRow1-1, 0, nCol2, nRow2 - nFillCount, 0);
+ }
+ }
+ else
+ {
+ nOStart = nRow1;
+ nOEnd = nRow2;
+ if (bPositive)
+ {
+ nISrcStart = nCol1;
+ nISrcEnd = nCol2;
+ nIStart = nCol2 + 1;
+ nIEnd = nCol2 + nFillCount;
+ aFillRange = ScRange(nCol2 + 1, nRow1, 0, nCol2 + nFillCount, nRow2, 0);
+ }
+ else
+ {
+ nISrcStart = nCol2;
+ nISrcEnd = nCol1;
+ nIStart = nCol1 - 1;
+ nIEnd = nCol1 - nFillCount;
+ aFillRange = ScRange(nCol1 - 1, nRow1, 0, nCol1 - nFillCount, nRow2, 0);
+ }
+ }
+ sal_uInt64 nIMin = nIStart;
+ sal_uInt64 nIMax = nIEnd;
+ PutInOrder(nIMin,nIMax);
+ bool bHasFiltered = IsDataFiltered(aFillRange);
+
+ if (!bHasFiltered)
+ {
+ if (bVertical)
+ DeleteArea(nCol1, static_cast<SCROW>(nIMin), nCol2, static_cast<SCROW>(nIMax), InsertDeleteFlags::AUTOFILL);
+ else
+ DeleteArea(static_cast<SCCOL>(nIMin), nRow1, static_cast<SCCOL>(nIMax), nRow2, InsertDeleteFlags::AUTOFILL);
+ }
+
+ sal_uInt64 nProgress = 0;
+ if (pProgress)
+ nProgress = pProgress->GetState();
+
+ // Avoid possible repeated calls to StartListeningFormulaCells() (tdf#132165).
+ std::list< sc::DelayStartListeningFormulaCells > delayStartListening;
+ SCCOL delayStartColumn, delayEndColumn;
+ if(bVertical)
+ {
+ delayStartColumn = std::min( nOStart, nOEnd );
+ delayEndColumn = std::max( nOStart, nOEnd );
+ }
+ else
+ {
+ delayStartColumn = std::min( nIStart, nIEnd );
+ delayEndColumn = std::max( nIStart, nIEnd );
+ }
+ for( SCROW col = delayStartColumn; col <= delayEndColumn; ++col )
+ {
+ if( ScColumn* column = FetchColumn( col ))
+ delayStartListening.emplace_back( *column, true );
+ }
+
+ // execute
+
+ sal_uInt64 nActFormCnt = 0;
+ for (rOuter = nOStart; rOuter <= nOEnd; rOuter++)
+ {
+ sal_uInt64 nMaxFormCnt = 0; // for formulas
+
+ // transfer attributes
+
+ const ScPatternAttr* pSrcPattern = nullptr;
+ const ScStyleSheet* pStyleSheet = nullptr;
+ SCCOLROW nAtSrc = nISrcStart;
+ std::unique_ptr<ScPatternAttr> pNewPattern;
+ bool bGetPattern = true;
+ rInner = nIStart;
+ while (true) // #i53728# with "for (;;)" old solaris/x86 compiler mis-optimizes
+ {
+ if (!ColHidden(nCol) && !RowHidden(nRow))
+ {
+ if ( bGetPattern )
+ {
+ if (bVertical) // rInner&:=nRow, rOuter&:=nCol
+ pSrcPattern = GetColumnData(nCol).GetPattern(static_cast<SCROW>(nAtSrc));
+ else // rInner&:=nCol, rOuter&:=nRow
+ pSrcPattern = GetColumnData(nAtSrc).GetPattern(static_cast<SCROW>(nRow));
+ bGetPattern = false;
+ pStyleSheet = pSrcPattern->GetStyleSheet();
+ // do transfer ATTR_MERGE / ATTR_MERGE_FLAG
+ //
+ // Note: ATTR_MERGE is an attribute of the top left cell of a merged area
+ // containing the size of the area. ATTR_MERGE_FLAGs are attributes of the
+ // other cells of a merged area, containing the information about also
+ // overlapping, i.e. visibility of their content.
+ //
+ // TODO: extend the similar incomplete selections to a bounding rectangle to
+ // avoid incomplete fill, where not all AUTO_MERGE_FLAGs are synchronized with
+ // the copied ATTR_MERGE, resulting broken grid and visibility during run-time.
+ //
+ // +--+ +--+--+
+ // | | | | |
+ // +--+--+ +--+--+
+ // | | -> | |
+ // +--+--+ +--+--+
+ // | | | | |
+ // +--+ +--+--+
+ //
+ // TODO: protect incompatible merged cells of the destination area, for example
+ // by skipping the fill operation.
+ //
+ // TODO: by dragging the fill handle select only the multiples of the height
+ // of the originally selected area which is merged vertically to avoid of
+ // incomplete fill.
+ //
+ // +--+ +--+
+ // |XX| |XX|
+ // +XX+ +XX+
+ // |XX| -> |XX|
+ // +--+ +--+
+ // | | | |
+ // +--+ +--+
+ // | |
+ // +--+
+ //
+ // Other things stored in ATTR_MERGE_FLAG, like autofilter button, will be
+ // deleted now, but may need to be repaired later, like at ScDocument::Fill.
+ const SfxItemSet& rSet = pSrcPattern->GetItemSet();
+ if ( rSet.GetItemState(ATTR_MERGE_FLAG, false) == SfxItemState::SET )
+ {
+ ScMF nOldValue = pSrcPattern->GetItem(ATTR_MERGE_FLAG).GetValue();
+ ScMF nOldValueMerge = nOldValue & (ScMF::Hor | ScMF::Ver);
+ // keep only the merge flags
+ if ( nOldValue != nOldValueMerge )
+ {
+ pNewPattern.reset(new ScPatternAttr(*pSrcPattern));
+ SfxItemSet& rNewSet = pNewPattern->GetItemSet();
+ if ( nOldValueMerge == ScMF::NONE )
+ rNewSet.ClearItem(ATTR_MERGE_FLAG);
+ else
+ rNewSet.Put(ScMergeFlagAttr(nOldValueMerge));
+ }
+ else
+ pNewPattern.reset();
+ }
+ else
+ pNewPattern.reset();
+ }
+
+ const ScCondFormatItem& rCondFormatItem = pSrcPattern->GetItem(ATTR_CONDITIONAL);
+ const ScCondFormatIndexes& rCondFormatIndex = rCondFormatItem.GetCondFormatData();
+
+ if ( bVertical && nISrcStart == nISrcEnd && !bHasFiltered )
+ {
+ // set all attributes at once (en bloc)
+ if (pNewPattern || pSrcPattern != rDocument.GetDefPattern())
+ {
+ // Default is already present (DeleteArea)
+ SCROW nY1 = static_cast<SCROW>(std::min( nIStart, nIEnd ));
+ SCROW nY2 = static_cast<SCROW>(std::max( nIStart, nIEnd ));
+ if ( pStyleSheet )
+ aCol[nCol].ApplyStyleArea( nY1, nY2, *pStyleSheet );
+ if ( pNewPattern )
+ aCol[nCol].ApplyPatternArea( nY1, nY2, *pNewPattern );
+ else
+ aCol[nCol].ApplyPatternArea( nY1, nY2, *pSrcPattern );
+
+ for(const auto& rIndex : rCondFormatIndex)
+ {
+ ScConditionalFormat* pCondFormat = mpCondFormatList->GetFormat(rIndex);
+ if (pCondFormat)
+ {
+ ScRangeList aRange = pCondFormat->GetRange();
+ aRange.Join(ScRange(nCol, nY1, nTab, nCol, nY2, nTab));
+ pCondFormat->SetRange(aRange);
+ }
+ }
+ }
+
+ break;
+ }
+
+ if ( bHasFiltered )
+ DeleteArea(static_cast<SCCOL>(nCol), static_cast<SCROW>(nRow),
+ static_cast<SCCOL>(nCol), static_cast<SCROW>(nRow), InsertDeleteFlags::AUTOFILL);
+
+ if ( pSrcPattern != aCol[nCol].GetPattern( static_cast<SCROW>(nRow) ) )
+ {
+ // Transfer template too
+ //TODO: Merge ApplyPattern to AttrArray ??
+ if ( pStyleSheet )
+ aCol[nCol].ApplyStyle( static_cast<SCROW>(nRow), pStyleSheet );
+
+ // Use ApplyPattern instead of SetPattern to keep old MergeFlags
+ if ( pNewPattern )
+ aCol[nCol].ApplyPattern( static_cast<SCROW>(nRow), *pNewPattern );
+ else
+ aCol[nCol].ApplyPattern( static_cast<SCROW>(nRow), *pSrcPattern );
+
+ for(const auto& rIndex : rCondFormatIndex)
+ {
+ ScConditionalFormat* pCondFormat = mpCondFormatList->GetFormat(rIndex);
+ if (pCondFormat)
+ {
+ ScRangeList aRange = pCondFormat->GetRange();
+ aRange.Join(ScRange(nCol, nRow, nTab, nCol, nRow, nTab));
+ pCondFormat->SetRange(aRange);
+ }
+ }
+ }
+
+ if (nAtSrc==nISrcEnd)
+ {
+ if ( nAtSrc != nISrcStart )
+ { // More than one source cell
+ nAtSrc = nISrcStart;
+ bGetPattern = true;
+ }
+ }
+ else if (bPositive)
+ {
+ ++nAtSrc;
+ bGetPattern = true;
+ }
+ else
+ {
+ --nAtSrc;
+ bGetPattern = true;
+ }
+ }
+
+ if (rInner == nIEnd) break;
+ if (bPositive) ++rInner; else --rInner;
+ }
+ pNewPattern.reset();
+
+ // Analyse
+
+ FillCmd eFillCmd;
+ FillDateCmd eDateCmd = {};
+ double nInc;
+ sal_uInt16 nMinDigits;
+ ScUserListData* pListData = nullptr;
+ sal_uInt16 nListIndex;
+ bool bSkipOverlappedCells;
+ std::vector<sal_Int32> aNonOverlappedCellIdx;
+ if (bVertical)
+ FillAnalyse(static_cast<SCCOL>(nCol),nRow1,
+ static_cast<SCCOL>(nCol),nRow2, eFillCmd,eDateCmd,
+ nInc, nMinDigits, pListData, nListIndex,
+ bHasFiltered, bSkipOverlappedCells, aNonOverlappedCellIdx);
+ else
+ FillAnalyse(nCol1,static_cast<SCROW>(nRow),
+ nCol2,static_cast<SCROW>(nRow), eFillCmd,eDateCmd,
+ nInc, nMinDigits, pListData, nListIndex,
+ bHasFiltered, bSkipOverlappedCells, aNonOverlappedCellIdx);
+
+ if (pListData)
+ {
+ sal_uInt16 nListCount = pListData->GetSubCount();
+ if (bSkipOverlappedCells)
+ {
+ int nFillerCount = 1 + ( nISrcEnd - nISrcStart ) * (bPositive ? 1 : -1);
+ std::vector<bool> aIsNonEmptyCell(nFillerCount, false);
+ SCCOLROW nLastValueIdx;
+ if (bPositive)
+ {
+ nLastValueIdx = nISrcEnd - (nFillerCount - 1 - aNonOverlappedCellIdx.back());
+ for (auto i : aNonOverlappedCellIdx)
+ aIsNonEmptyCell[i] = true;
+ }
+ else
+ {
+ nLastValueIdx = nISrcEnd + aNonOverlappedCellIdx[0];
+ for (auto i : aNonOverlappedCellIdx)
+ aIsNonEmptyCell[nFillerCount - 1 - i] = true;
+ }
+
+ OUString aStr;
+ if (bVertical)
+ aStr = GetString(rOuter, nLastValueIdx);
+ else
+ aStr = GetString(nLastValueIdx, rOuter);
+
+ bool bMatchCase = false;
+ (void)pListData->GetSubIndex(aStr, nListIndex, bMatchCase);
+
+ sal_Int32 nFillerIdx = 0;
+ rInner = nIStart;
+ while (true)
+ {
+ if (aIsNonEmptyCell[nFillerIdx])
+ {
+ if (bPositive)
+ {
+ nListIndex += nInc;
+ if (nListIndex >= nListCount) nListIndex -= nListCount;
+ }
+ else
+ {
+ if (nListIndex < nInc) nListIndex += nListCount;
+ nListIndex -= nInc;
+ }
+ aCol[nCol].SetRawString(static_cast<SCROW>(nRow), pListData->GetSubStr(nListIndex));
+
+ }
+ if (rInner == nIEnd) break;
+ nFillerIdx = (nFillerIdx + 1) % nFillerCount;
+ if (bPositive)
+ ++rInner;
+ else
+ --rInner;
+ }
+ }
+ else
+ {
+ if (!bPositive)
+ {
+ // nListIndex of FillAnalyse points to the last entry -> adjust
+ sal_Int64 nAdjust = nListIndex - (nISrcStart - nISrcEnd) * nInc;
+ nAdjust = nAdjust % nListCount;
+ if (nAdjust < 0)
+ nAdjust += nListCount;
+ nListIndex = nAdjust;
+ }
+
+ rInner = nIStart;
+ while (true) // #i53728# with "for (;;)" old solaris/x86 compiler mis-optimizes
+ {
+ if (!ColHidden(nCol) && !RowHidden(nRow))
+ {
+ if (bPositive)
+ {
+ nListIndex += nInc;
+ if (nListIndex >= nListCount) nListIndex -= nListCount;
+ }
+ else
+ {
+ if (nListIndex < nInc) nListIndex += nListCount;
+ nListIndex -= nInc;
+ }
+ aCol[nCol].SetRawString(static_cast<SCROW>(nRow), pListData->GetSubStr(nListIndex));
+ }
+
+ if (rInner == nIEnd) break;
+ if (bPositive) ++rInner; else --rInner;
+ }
+ }
+ if(pProgress)
+ {
+ nProgress += nIMax - nIMin + 1;
+ pProgress->SetStateOnPercent( nProgress );
+ }
+ }
+ else if (eFillCmd == FILL_SIMPLE) // fill with pattern/sample
+ {
+ FillAutoSimple(
+ nISrcStart, nISrcEnd, nIStart, nIEnd, rInner, nCol, nRow,
+ nActFormCnt, nMaxFormCnt, bHasFiltered, bVertical, bPositive, pProgress, nProgress);
+ }
+ else
+ {
+ if (!bPositive)
+ nInc = -nInc;
+ double nEndVal = (nInc>=0.0) ? MAXDOUBLE : -MAXDOUBLE;
+ if (bVertical)
+ FillSeries( static_cast<SCCOL>(nCol), nRow1,
+ static_cast<SCCOL>(nCol), nRow2, nFillCount, eFillDir,
+ eFillCmd, eDateCmd, nInc, nEndVal, nMinDigits, false,
+ pProgress, bSkipOverlappedCells, &aNonOverlappedCellIdx);
+ else
+ FillSeries( nCol1, static_cast<SCROW>(nRow), nCol2,
+ static_cast<SCROW>(nRow), nFillCount, eFillDir,
+ eFillCmd, eDateCmd, nInc, nEndVal, nMinDigits, false,
+ pProgress, bSkipOverlappedCells, &aNonOverlappedCellIdx);
+ if (pProgress)
+ nProgress = pProgress->GetState();
+ }
+
+ if (bVertical)
+ FillSparkline(bVertical, nCol, nRow1, nRow2, nIStart, nIEnd);
+ else
+ FillSparkline(bVertical, nRow, nCol1, nCol2, nIStart, nIEnd);
+
+ nActFormCnt += nMaxFormCnt;
+ }
+}
+
+void ScTable::FillSparkline(bool bVertical, SCCOLROW nFixed,
+ SCCOLROW nStart, SCCOLROW nEnd,
+ SCCOLROW nFillStart, SCCOLROW nFillEnd)
+{
+ bool bHasSparklines = false;
+ std::vector<std::shared_ptr<sc::Sparkline>> aSparklineSeries;
+
+ for (SCROW nCurrent = nStart; nCurrent <= nEnd; nCurrent++)
+ {
+ auto pSparkline = bVertical ? GetSparkline(nFixed, nCurrent) : GetSparkline(nCurrent, nFixed);
+ bHasSparklines = bHasSparklines || bool(pSparkline);
+ aSparklineSeries.push_back(pSparkline);
+ }
+
+ if (bHasSparklines)
+ {
+ for (SCCOLROW nCurrent = nFillStart; nCurrent <= nFillEnd; nCurrent++)
+ {
+ size_t nIndex = size_t(nFillStart - nCurrent) % aSparklineSeries.size();
+ if (auto& rpSparkline = aSparklineSeries[nIndex])
+ {
+ auto pGroup = rpSparkline->getSparklineGroup();
+
+ auto* pNewSparkline = bVertical ? CreateSparkline(nFixed, nCurrent, pGroup)
+ : CreateSparkline(nCurrent, nFixed, pGroup);
+ if (pNewSparkline)
+ {
+ SCCOLROW nPosition = bVertical ? rpSparkline->getRow()
+ : rpSparkline->getColumn();
+ SCCOLROW nDelta = nCurrent - nPosition;
+ ScRangeList aRangeList(rpSparkline->getInputRange());
+ for (ScRange& rRange : aRangeList)
+ {
+ if (bVertical)
+ {
+ rRange.aStart.IncRow(nDelta);
+ rRange.aEnd.IncRow(nDelta);
+ }
+ else
+ {
+ rRange.aStart.IncCol(nDelta);
+ rRange.aEnd.IncCol(nDelta);
+ }
+ }
+ pNewSparkline->setInputRange(aRangeList);
+ }
+ }
+ }
+ }
+}
+
+OUString ScTable::GetAutoFillPreview( const ScRange& rSource, SCCOL nEndX, SCROW nEndY )
+{
+ OUString aValue;
+
+ SCCOL nCol1 = rSource.aStart.Col();
+ SCROW nRow1 = rSource.aStart.Row();
+ SCCOL nCol2 = rSource.aEnd.Col();
+ SCROW nRow2 = rSource.aEnd.Row();
+ bool bOk = true;
+ tools::Long nIndex = 0;
+ sal_uInt64 nSrcCount = 0;
+ FillDir eFillDir = FILL_TO_BOTTOM;
+ if ( nEndX == nCol2 && nEndY == nRow2 ) // empty
+ bOk = false;
+ else if ( nEndX == nCol2 ) // to up / down
+ {
+ nCol2 = nCol1; // use only first column
+ nSrcCount = nRow2 - nRow1 + 1;
+ nIndex = static_cast<tools::Long>(nEndY) - nRow1; // can be negative
+ if ( nEndY >= nRow1 )
+ eFillDir = FILL_TO_BOTTOM;
+ else
+ eFillDir = FILL_TO_TOP;
+ }
+ else if ( nEndY == nRow2 ) // to left / right
+ {
+ nEndY = nRow2 = nRow1; // use only first row
+ nSrcCount = nCol2 - nCol1 + 1;
+ nIndex = static_cast<tools::Long>(nEndX) - nCol1; // can be negative
+ if ( nEndX >= nCol1 )
+ eFillDir = FILL_TO_RIGHT;
+ else
+ eFillDir = FILL_TO_LEFT;
+ }
+ else // direction not clear
+ bOk = false;
+
+ if ( bOk )
+ {
+ tools::Long nBegin = 0;
+ tools::Long nEnd = 0;
+ tools::Long nHidden = 0;
+ if (eFillDir == FILL_TO_BOTTOM || eFillDir == FILL_TO_TOP)
+ {
+ if (nEndY > nRow1)
+ {
+ nBegin = nRow2+1;
+ nEnd = nEndY;
+ }
+ else
+ {
+ nBegin = nEndY;
+ nEnd = nRow1 -1;
+ }
+
+ tools::Long nVisible = CountVisibleRows(nBegin, nEnd);
+ nHidden = nEnd + 1 - nBegin - nVisible;
+ }
+ else
+ {
+ if (nEndX > nCol1)
+ {
+ nBegin = nCol2+1;
+ nEnd = nEndX;
+ }
+ else
+ {
+ nBegin = nEndX;
+ nEnd = nCol1 -1;
+ }
+
+ tools::Long nVisible = CountVisibleCols(nBegin, nEnd);
+ nHidden = nEnd + 1 - nBegin - nVisible;
+ }
+ if (nHidden)
+ {
+ if (nIndex > 0)
+ nIndex = nIndex - nHidden;
+ else
+ nIndex = nIndex + nHidden;
+ }
+
+ FillCmd eFillCmd;
+ FillDateCmd eDateCmd;
+ double nInc;
+ sal_uInt16 nMinDigits;
+ ScUserListData* pListData = nullptr;
+ sal_uInt16 nListIndex;
+ bool bSkipOverlappedCells;
+ std::vector<sal_Int32> aNonOverlappedCellIdx;
+
+ // Todo: update this function to calculate with merged cell fills,
+ // after FillAnalyse / FillSeries fully handle them.
+ // Now FillAnalyse called as if there are filtered rows, so it will work in the old way.
+ FillAnalyse(nCol1, nRow1, nCol2, nRow2, eFillCmd, eDateCmd,
+ nInc, nMinDigits, pListData, nListIndex,
+ true, bSkipOverlappedCells, aNonOverlappedCellIdx);
+
+ if ( pListData ) // user defined list
+ {
+ sal_uInt16 nListCount = pListData->GetSubCount();
+ if ( nListCount )
+ {
+ sal_uInt64 nSub = nSrcCount - 1; // nListIndex is from last source entry
+ while ( nIndex < sal::static_int_cast<tools::Long>(nSub) )
+ nIndex += nListCount;
+ sal_uInt64 nPos = ( nListIndex + nIndex - nSub ) % nListCount;
+ aValue = pListData->GetSubStr(sal::static_int_cast<sal_uInt16>(nPos));
+ }
+ }
+ else if ( eFillCmd == FILL_SIMPLE ) // fill with pattern/sample
+ {
+ tools::Long nPosIndex = nIndex;
+ while ( nPosIndex < 0 )
+ nPosIndex += nSrcCount;
+ sal_uInt64 nPos = nPosIndex % nSrcCount;
+ SCCOL nSrcX = nCol1;
+ SCROW nSrcY = nRow1;
+ if ( eFillDir == FILL_TO_TOP || eFillDir == FILL_TO_BOTTOM )
+ nSrcY = sal::static_int_cast<SCROW>( nSrcY + static_cast<SCROW>(nPos) );
+ else
+ nSrcX = sal::static_int_cast<SCCOL>( nSrcX + static_cast<SCCOL>(nPos) );
+
+ ScRefCellValue aCell = GetCellValue(nSrcX, nSrcY);
+ if (!aCell.isEmpty())
+ {
+ sal_Int32 nDelta;
+ if (nIndex >= 0)
+ nDelta = nIndex / nSrcCount;
+ else
+ nDelta = ( nIndex - nSrcCount + 1 ) / nSrcCount; // -1 -> -1
+
+ CellType eType = aCell.meType;
+ switch ( eType )
+ {
+ case CELLTYPE_STRING:
+ case CELLTYPE_EDIT:
+ {
+ aValue = aCell.getString(&rDocument);
+
+ if ( !(nScFillModeMouseModifier & KEY_MOD1) )
+ {
+ sal_Int32 nVal;
+ sal_uInt16 nCellDigits = 0; // look at each source cell individually
+ short nFlag = lcl_DecompValueString( aValue, nVal, &nCellDigits );
+ if ( nFlag < 0 )
+ {
+ if (aValue == ScGlobal::GetOrdinalSuffix( nVal))
+ aValue = ScGlobal::GetOrdinalSuffix( nVal + nDelta);
+ aValue = lcl_ValueString( nVal + nDelta, nCellDigits ) + aValue;
+ }
+ else if ( nFlag > 0 )
+ {
+ sal_Int32 nNextValue;
+ if ( nVal < 0 )
+ nNextValue = nVal - nDelta;
+ else
+ nNextValue = nVal + nDelta;
+ if ( nFlag == 2 && nNextValue >= 0 ) // Put back the '+'
+ aValue += "+";
+ aValue += lcl_ValueString( nNextValue, nCellDigits );
+ }
+ }
+ }
+ break;
+ case CELLTYPE_VALUE:
+ {
+ sal_uInt32 nNumFmt = GetNumberFormat( nSrcX, nSrcY );
+ // overflow is possible...
+ double nVal = aCell.mfValue;
+ if ( !(nScFillModeMouseModifier & KEY_MOD1) )
+ {
+ const SvNumFormatType nFormatType = rDocument.GetFormatTable()->GetType(nNumFmt);
+ bool bPercentCell = (nFormatType == SvNumFormatType::PERCENT);
+ if (bPercentCell)
+ {
+ // tdf#89998 increment by 1% at a time
+ nVal += static_cast<double>(nDelta) * 0.01;
+ }
+ else if (nVal == 0.0 || nVal == 1.0)
+ {
+ bool bBooleanCell = (nFormatType == SvNumFormatType::LOGICAL);
+ if (!bBooleanCell)
+ nVal += static_cast<double>(nDelta);
+ }
+ else
+ {
+ nVal += static_cast<double>(nDelta);
+ }
+ }
+
+ const Color* pColor;
+ rDocument.GetFormatTable()->GetOutputString( nVal, nNumFmt, aValue, &pColor );
+ }
+ break;
+ // not for formulas
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+ }
+ }
+ else if ( eFillCmd == FILL_LINEAR || eFillCmd == FILL_DATE ) // values
+ {
+ bool bValueOk;
+ double nStart;
+ sal_Int32 nVal = 0;
+ short nHeadNoneTail = 0;
+ ScRefCellValue aCell = GetCellValue(nCol1, nRow1);
+ if (!aCell.isEmpty())
+ {
+ CellType eType = aCell.meType;
+ switch ( eType )
+ {
+ case CELLTYPE_STRING:
+ case CELLTYPE_EDIT:
+ {
+ aValue = aCell.getString(&rDocument);
+ nHeadNoneTail = lcl_DecompValueString( aValue, nVal );
+ if ( nHeadNoneTail )
+ nStart = static_cast<double>(nVal);
+ else
+ nStart = 0.0;
+ }
+ break;
+ case CELLTYPE_VALUE:
+ nStart = aCell.mfValue;
+ break;
+ case CELLTYPE_FORMULA:
+ nStart = aCell.mpFormula->GetValue();
+ break;
+ default:
+ nStart = 0.0;
+ }
+ }
+ else
+ nStart = 0.0;
+ if ( eFillCmd == FILL_LINEAR )
+ {
+ double nAdd = nInc;
+ bValueOk = ( SubTotal::SafeMult( nAdd, static_cast<double>(nIndex) ) &&
+ SubTotal::SafePlus( nStart, nAdd ) );
+ }
+ else // date
+ {
+ bValueOk = true;
+ sal_uInt16 nDayOfMonth = 0;
+ if ( nIndex < 0 )
+ {
+ nIndex = -nIndex;
+ nInc = -nInc;
+ }
+ for (tools::Long i=0; i<nIndex; i++)
+ IncDate( nStart, nDayOfMonth, nInc, eDateCmd );
+ }
+
+ if (bValueOk)
+ {
+ if ( nHeadNoneTail )
+ {
+ if ( nHeadNoneTail < 0 )
+ {
+ if (aValue == ScGlobal::GetOrdinalSuffix( nVal))
+ aValue = ScGlobal::GetOrdinalSuffix( static_cast<sal_Int32>(nStart) );
+
+ aValue = lcl_ValueString( static_cast<sal_Int32>(nStart), nMinDigits ) + aValue;
+ }
+ else
+ {
+ if ( nHeadNoneTail == 2 && nStart >= 0 ) // Put back the '+'
+ aValue += "+";
+ aValue += lcl_ValueString( static_cast<sal_Int32>(nStart), nMinDigits );
+ }
+ }
+ else
+ {
+ //TODO: get number format according to Index?
+ const Color* pColor;
+ sal_uInt32 nNumFmt = GetNumberFormat( nCol1, nRow1 );
+ rDocument.GetFormatTable()->GetOutputString( nStart, nNumFmt, aValue, &pColor );
+ }
+ }
+ }
+ else
+ {
+ OSL_FAIL("GetAutoFillPreview: invalid mode");
+ }
+ }
+
+ return aValue;
+}
+
+void ScTable::IncDate(double& rVal, sal_uInt16& nDayOfMonth, double nStep, FillDateCmd eCmd)
+{
+ if (eCmd == FILL_DAY)
+ {
+ rVal += nStep;
+ return;
+ }
+
+ // class Date limits
+ const sal_uInt16 nMinYear = 1583;
+ const sal_uInt16 nMaxYear = 9956;
+
+ tools::Long nInc = static_cast<tools::Long>(nStep); // upper/lower limits ?
+ Date aNullDate = rDocument.GetFormatTable()->GetNullDate();
+ Date aDate = aNullDate;
+ aDate.AddDays(rVal);
+ switch (eCmd)
+ {
+ case FILL_WEEKDAY:
+ {
+ aDate.AddDays(nInc);
+ DayOfWeek eWeekDay = aDate.GetDayOfWeek();
+ if (nInc >= 0)
+ {
+ if (eWeekDay == SATURDAY)
+ aDate.AddDays(2);
+ else if (eWeekDay == SUNDAY)
+ aDate.AddDays(1);
+ }
+ else
+ {
+ if (eWeekDay == SATURDAY)
+ aDate.AddDays(-1);
+ else if (eWeekDay == SUNDAY)
+ aDate.AddDays(-2);
+ }
+ }
+ break;
+ case FILL_MONTH:
+ case FILL_END_OF_MONTH:
+ {
+ if ( nDayOfMonth == 0 )
+ nDayOfMonth = aDate.GetDay(); // init
+ tools::Long nMonth = aDate.GetMonth();
+ tools::Long nYear = aDate.GetYear();
+
+ nMonth += nInc;
+
+ if (nInc >= 0)
+ {
+ if (nMonth > 12)
+ {
+ tools::Long nYAdd = (nMonth-1) / 12;
+ nMonth -= nYAdd * 12;
+ nYear += nYAdd;
+ }
+ }
+ else
+ {
+ if (nMonth < 1)
+ {
+ tools::Long nYAdd = 1 - nMonth / 12; // positive
+ nMonth += nYAdd * 12;
+ nYear -= nYAdd;
+ }
+ }
+
+ if ( nYear < nMinYear )
+ aDate = Date( 1,1, nMinYear );
+ else if ( nYear > nMaxYear )
+ aDate = Date( 31,12, nMaxYear );
+ else
+ {
+ aDate.SetMonth(static_cast<sal_uInt16>(nMonth));
+ aDate.SetYear(static_cast<sal_uInt16>(nYear));
+ if (eCmd == FILL_END_OF_MONTH)
+ {
+ aDate.SetDay(Date::GetDaysInMonth(nMonth, nYear));
+ }
+ else
+ {
+ aDate.SetDay(std::min(Date::GetDaysInMonth(nMonth, nYear), nDayOfMonth));
+ }
+ }
+ }
+ break;
+ case FILL_YEAR:
+ {
+ tools::Long nYear = aDate.GetYear();
+ nYear += nInc;
+ if ( nYear < nMinYear )
+ aDate = Date( 1,1, nMinYear );
+ else if ( nYear > nMaxYear )
+ aDate = Date( 31,12, nMaxYear );
+ else
+ aDate.SetYear(static_cast<sal_uInt16>(nYear));
+ }
+ break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+
+ rVal = aDate - aNullDate;
+}
+
+namespace {
+
+bool HiddenRowColumn(const ScTable* pTable, SCCOLROW nRowColumn, bool bVertical, SCCOLROW& rLastPos)
+{
+ bool bHidden = false;
+ if(bVertical)
+ {
+ SCROW nLast;
+ bHidden = pTable->RowHidden(nRowColumn, nullptr, &nLast);
+ rLastPos = nLast;
+ }
+ else
+ {
+ SCCOL nLast;
+ bHidden = pTable->ColHidden(static_cast<SCCOL>(nRowColumn), nullptr, &nLast);
+ rLastPos = nLast;
+ }
+ return bHidden;
+}
+
+}
+
+void ScTable::FillFormulaVertical(
+ const ScFormulaCell& rSrcCell,
+ SCCOLROW& rInner, SCCOL nCol, SCROW nRow1, SCROW nRow2,
+ ScProgress* pProgress, sal_uInt64& rProgress )
+{
+ // rInner is the row position when filling vertically. Also, when filling
+ // across hidden regions, it may create multiple dis-jointed spans of
+ // formula cells.
+
+ bool bHidden = false;
+ SCCOLROW nHiddenLast = -1;
+
+ SCCOLROW nRowStart = -1, nRowEnd = -1;
+ std::vector<sc::RowSpan> aSpans;
+ PutInOrder(nRow1, nRow2);
+ for (rInner = nRow1; rInner <= nRow2; ++rInner)
+ {
+ if (rInner > nHiddenLast)
+ bHidden = HiddenRowColumn(this, rInner, true, nHiddenLast);
+
+ if (bHidden)
+ {
+ if (nRowStart >= 0)
+ {
+ nRowEnd = rInner - 1;
+ aSpans.emplace_back(nRowStart, nRowEnd);
+ nRowStart = -1;
+ }
+ rInner = nHiddenLast;
+ continue;
+ }
+
+ if (nRowStart < 0)
+ nRowStart = rInner;
+ }
+
+ if (nRowStart >= 0)
+ {
+ nRowEnd = rInner - 1;
+ aSpans.emplace_back(nRowStart, nRowEnd);
+ }
+
+ if (aSpans.empty())
+ return;
+
+ aCol[nCol].DeleteRanges(aSpans, InsertDeleteFlags::VALUE | InsertDeleteFlags::DATETIME | InsertDeleteFlags::STRING | InsertDeleteFlags::FORMULA | InsertDeleteFlags::OUTLINE);
+ aCol[nCol].CloneFormulaCell(rSrcCell, sc::CellTextAttr(), aSpans);
+
+ auto pSet = std::make_shared<sc::ColumnBlockPositionSet>(rDocument);
+ sc::StartListeningContext aStartCxt(rDocument, pSet);
+ sc::EndListeningContext aEndCxt(rDocument, pSet);
+
+ SCROW nStartRow = aSpans.front().mnRow1;
+ SCROW nEndRow = aSpans.back().mnRow2;
+ aCol[nCol].EndListeningFormulaCells(aEndCxt, nStartRow, nEndRow, &nStartRow, &nEndRow);
+ aCol[nCol].StartListeningFormulaCells(aStartCxt, aEndCxt, nStartRow, nEndRow);
+
+ for (const auto& rSpan : aSpans)
+ aCol[nCol].SetDirty(rSpan.mnRow1, rSpan.mnRow2, ScColumn::BROADCAST_NONE);
+
+ rProgress += nRow2 - nRow1 + 1;
+ if (pProgress)
+ pProgress->SetStateOnPercent(rProgress);
+}
+
+void ScTable::FillSeriesSimple(
+ const ScCellValue& rSrcCell, SCCOLROW& rInner, SCCOLROW nIMin, SCCOLROW nIMax,
+ const SCCOLROW& rCol, const SCCOLROW& rRow, bool bVertical, ScProgress* pProgress, sal_uInt64& rProgress )
+{
+ bool bHidden = false;
+ SCCOLROW nHiddenLast = -1;
+
+ if (bVertical)
+ {
+ switch (rSrcCell.meType)
+ {
+ case CELLTYPE_FORMULA:
+ {
+ FillFormulaVertical(
+ *rSrcCell.mpFormula, rInner, rCol, nIMin, nIMax, pProgress, rProgress);
+ }
+ break;
+ default:
+ {
+ for (rInner = nIMin; rInner <= nIMax; ++rInner)
+ {
+ if (rInner > nHiddenLast)
+ bHidden = HiddenRowColumn(this, rInner, bVertical, nHiddenLast);
+
+ if (bHidden)
+ {
+ rInner = nHiddenLast;
+ continue;
+ }
+
+ ScAddress aDestPos(rCol, rRow, nTab);
+ rSrcCell.commit(aCol[rCol], aDestPos.Row());
+ }
+ rProgress += nIMax - nIMin + 1;
+ if (pProgress)
+ pProgress->SetStateOnPercent(rProgress);
+ }
+ }
+ }
+ else
+ {
+ switch (rSrcCell.meType)
+ {
+ case CELLTYPE_FORMULA:
+ {
+ for (rInner = nIMin; rInner <= nIMax; ++rInner)
+ {
+ if (rInner > nHiddenLast)
+ bHidden = HiddenRowColumn(this, rInner, bVertical, nHiddenLast);
+
+ if (bHidden)
+ continue;
+
+ FillFormula(rSrcCell.mpFormula, rCol, rRow, (rInner == nIMax));
+ if (pProgress)
+ pProgress->SetStateOnPercent(++rProgress);
+ }
+ }
+ break;
+ default:
+ {
+ for (rInner = nIMin; rInner <= nIMax; ++rInner)
+ {
+ if (rInner > nHiddenLast)
+ bHidden = HiddenRowColumn(this, rInner, bVertical, nHiddenLast);
+
+ if (bHidden)
+ continue;
+
+ ScAddress aDestPos(rCol, rRow, nTab);
+ rSrcCell.commit(aCol[rCol], aDestPos.Row());
+ }
+ rProgress += nIMax - nIMin + 1;
+ if (pProgress)
+ pProgress->SetStateOnPercent(rProgress);
+ }
+ }
+ }
+}
+
+void ScTable::FillAutoSimple(
+ SCCOLROW nISrcStart, SCCOLROW nISrcEnd, SCCOLROW nIStart, SCCOLROW nIEnd,
+ SCCOLROW& rInner, const SCCOLROW& rCol, const SCCOLROW& rRow, sal_uInt64 nActFormCnt,
+ sal_uInt64 nMaxFormCnt, bool bHasFiltered, bool bVertical, bool bPositive,
+ ScProgress* pProgress, sal_uInt64& rProgress )
+{
+ SCCOLROW nSource = nISrcStart;
+ double nDelta;
+ if ( nScFillModeMouseModifier & KEY_MOD1 )
+ nDelta = 0.0;
+ else if ( bPositive )
+ nDelta = 1.0;
+ else
+ nDelta = -1.0;
+ sal_uInt64 nFormulaCounter = nActFormCnt;
+ bool bGetCell = true;
+ bool bBooleanCell = false;
+ bool bPercentCell = false;
+ sal_uInt16 nCellDigits = 0;
+ short nHeadNoneTail = 0;
+ sal_Int32 nStringValue = 0;
+ OUString aValue;
+ ScCellValue aSrcCell;
+ bool bIsOrdinalSuffix = false;
+
+ bool bColHidden = false, bRowHidden = false;
+ SCCOL nColHiddenFirst = rDocument.MaxCol();
+ SCCOL nColHiddenLast = -1;
+ SCROW nRowHiddenFirst = rDocument.MaxRow();
+ SCROW nRowHiddenLast = -1;
+
+ rInner = nIStart;
+ while (true) // #i53728# with "for (;;)" old solaris/x86 compiler mis-optimizes
+ {
+ if (bPositive)
+ {
+ if (rCol > nColHiddenLast)
+ bColHidden = ColHidden(rCol, nullptr, &nColHiddenLast);
+ if (rRow > nRowHiddenLast)
+ bRowHidden = RowHidden(rRow, nullptr, &nRowHiddenLast);
+ }
+ else
+ {
+ if (rCol < nColHiddenFirst)
+ bColHidden = ColHidden(rCol, &nColHiddenFirst);
+ if (rRow < nRowHiddenFirst)
+ bRowHidden = RowHidden(rRow, &nRowHiddenFirst);
+ }
+
+ if (!bColHidden && !bRowHidden)
+ {
+ if ( bGetCell )
+ {
+ if (bVertical) // rInner&:=nRow, rOuter&:=nCol
+ {
+ aSrcCell = GetCellValue(rCol, nSource);
+ if (nISrcStart == nISrcEnd && aSrcCell.meType == CELLTYPE_FORMULA)
+ {
+ FillFormulaVertical(*aSrcCell.mpFormula, rInner, rCol, nIStart, nIEnd, pProgress, rProgress);
+ return;
+ }
+ const SvNumFormatType nFormatType = rDocument.GetFormatTable()->GetType(
+ GetColumnData(rCol).GetNumberFormat( rDocument.GetNonThreadedContext(), nSource));
+ bBooleanCell = (nFormatType == SvNumFormatType::LOGICAL);
+ bPercentCell = (nFormatType == SvNumFormatType::PERCENT);
+
+ }
+ else // rInner&:=nCol, rOuter&:=nRow
+ {
+ aSrcCell = GetCellValue(nSource, rRow);
+ const SvNumFormatType nFormatType = rDocument.GetFormatTable()->GetType(
+ GetColumnData(nSource).GetNumberFormat( rDocument.GetNonThreadedContext(), rRow));
+ bBooleanCell = (nFormatType == SvNumFormatType::LOGICAL);
+ bPercentCell = (nFormatType == SvNumFormatType::PERCENT);
+ }
+
+ bGetCell = false;
+ if (!aSrcCell.isEmpty())
+ {
+ switch (aSrcCell.meType)
+ {
+ case CELLTYPE_STRING:
+ case CELLTYPE_EDIT:
+ if (aSrcCell.meType == CELLTYPE_STRING)
+ aValue = aSrcCell.mpString->getString();
+ else
+ aValue = ScEditUtil::GetString(*aSrcCell.mpEditText, &rDocument);
+ if ( !(nScFillModeMouseModifier & KEY_MOD1) && !bHasFiltered )
+ {
+ nCellDigits = 0; // look at each source cell individually
+ nHeadNoneTail = lcl_DecompValueString(
+ aValue, nStringValue, &nCellDigits );
+
+ bIsOrdinalSuffix = aValue ==
+ ScGlobal::GetOrdinalSuffix(nStringValue);
+ }
+ break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+ }
+ }
+
+ switch (aSrcCell.meType)
+ {
+ case CELLTYPE_VALUE:
+ {
+ double fVal;
+ if (bBooleanCell && ((fVal = aSrcCell.mfValue) == 0.0 || fVal == 1.0))
+ aCol[rCol].SetValue(rRow, aSrcCell.mfValue);
+ else if(bPercentCell)
+ aCol[rCol].SetValue(rRow, aSrcCell.mfValue + nDelta * 0.01); // tdf#89998 increment by 1% at a time
+ else
+ aCol[rCol].SetValue(rRow, aSrcCell.mfValue + nDelta);
+ }
+ break;
+ case CELLTYPE_STRING:
+ case CELLTYPE_EDIT:
+ if ( nHeadNoneTail )
+ {
+ sal_Int32 nNextValue;
+ if (nStringValue < 0)
+ nNextValue = nStringValue - static_cast<sal_Int32>(nDelta);
+ else
+ nNextValue = nStringValue + static_cast<sal_Int32>(nDelta);
+
+ if ( nHeadNoneTail < 0 )
+ {
+ setSuffixCell(
+ aCol[rCol], rRow,
+ nNextValue, nCellDigits, aValue,
+ aSrcCell.meType, bIsOrdinalSuffix);
+ }
+ else
+ {
+ OUString aStr;
+ if (nHeadNoneTail == 2 && nNextValue >= 0) // Put back the '+'
+ aStr = aValue + "+" + lcl_ValueString(nNextValue, nCellDigits);
+ else
+ aStr = aValue + lcl_ValueString(nNextValue, nCellDigits);
+
+ aCol[rCol].SetRawString(rRow, aStr);
+ }
+ }
+ else
+ aSrcCell.commit(aCol[rCol], rRow);
+
+ break;
+ case CELLTYPE_FORMULA :
+ FillFormula(
+ aSrcCell.mpFormula, rCol, rRow, (rInner == nIEnd));
+ if (nFormulaCounter - nActFormCnt > nMaxFormCnt)
+ nMaxFormCnt = nFormulaCounter - nActFormCnt;
+ break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+
+ if (nSource == nISrcEnd)
+ {
+ if ( nSource != nISrcStart )
+ { // More than one source cell
+ nSource = nISrcStart;
+ bGetCell = true;
+ }
+ if ( !(nScFillModeMouseModifier & KEY_MOD1) )
+ {
+ if ( bPositive )
+ nDelta += 1.0;
+ else
+ nDelta -= 1.0;
+ }
+ nFormulaCounter = nActFormCnt;
+ }
+ else if (bPositive)
+ {
+ ++nSource;
+ bGetCell = true;
+ }
+ else
+ {
+ --nSource;
+ bGetCell = true;
+ }
+ }
+
+ if (rInner == nIEnd)
+ break;
+ if (bPositive)
+ ++rInner;
+ else
+ --rInner;
+
+ // Progress in inner loop only for expensive cells,
+ // and even then not individually for each one
+
+ ++rProgress;
+ if ( pProgress && (aSrcCell.meType == CELLTYPE_FORMULA || aSrcCell.meType == CELLTYPE_EDIT) )
+ pProgress->SetStateOnPercent( rProgress );
+
+ }
+ if (pProgress)
+ pProgress->SetStateOnPercent( rProgress );
+}
+
+namespace
+{
+// Target value exceeded?
+inline bool isOverflow( const double& rVal, const double& rMax, const double& rStep,
+ const double& rStartVal, FillCmd eFillCmd )
+{
+ switch (eFillCmd)
+ {
+ case FILL_LINEAR:
+ case FILL_DATE:
+ if (rStep >= 0.0)
+ return rVal > rMax;
+ else
+ return rVal < rMax;
+ case FILL_GROWTH:
+ if (rStep > 0.0)
+ {
+ if (rStep >= 1.0)
+ {
+ // Growing away from zero, including zero growth (1.0).
+ if (rVal >= 0.0)
+ return rVal > rMax;
+ else
+ return rVal < rMax;
+ }
+ else
+ {
+ // Shrinking towards zero.
+ if (rVal >= 0.0)
+ return rVal < rMax;
+ else
+ return rVal > rMax;
+ }
+ }
+ else if (rStep < 0.0)
+ {
+ // Alternating positive and negative values.
+ if (rStep <= -1.0)
+ {
+ // Growing away from zero, including zero growth (-1.0).
+ if (rVal >= 0.0)
+ {
+ if (rMax >= 0.0)
+ return rVal > rMax;
+ else
+ // Regard negative rMax as lower limit, which will
+ // be reached only by a negative rVal.
+ return false;
+ }
+ else
+ {
+ if (rMax <= 0.0)
+ return rVal < rMax;
+ else
+ // Regard positive rMax as upper limit, which will
+ // be reached only by a positive rVal.
+ return false;
+ }
+ }
+ else
+ {
+ // Shrinking towards zero.
+ if (rVal >= 0.0)
+ return rVal < rMax;
+ else
+ return rVal > rMax;
+ }
+ }
+ else // if (rStep == 0.0)
+ {
+ // All values become zero.
+ // Corresponds with bEntireArea in FillSeries().
+ if (rMax > 0.0)
+ return rMax < rStartVal;
+ else if (rMax < 0.0)
+ return rStartVal < rMax;
+ }
+ break;
+ default:
+ assert(!"eFillCmd");
+ }
+ return false;
+}
+}
+
+void ScTable::FillSeries( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ sal_uInt64 nFillCount, FillDir eFillDir, FillCmd eFillCmd, FillDateCmd eFillDateCmd,
+ double nStepValue, double nMaxValue, sal_uInt16 nArgMinDigits,
+ bool bAttribs, ScProgress* pProgress,
+ bool bSkipOverlappedCells, std::vector<sal_Int32>* pNonOverlappedCellIdx )
+{
+ // The term 'inner' here refers to the loop in the filling direction i.e.
+ // when filling vertically, the inner position is the row position whereas
+ // when filling horizontally the column position becomes the inner
+ // position. The term 'outer' refers to the column position when filling
+ // vertically, or the row position when filling horizontally. The fill is
+ // performed once in each 'outer' position e.g. when filling vertically,
+ // we perform the fill once in each column.
+
+ // Detect direction
+
+ bool bVertical = (eFillDir == FILL_TO_BOTTOM || eFillDir == FILL_TO_TOP);
+ bool bPositive = (eFillDir == FILL_TO_BOTTOM || eFillDir == FILL_TO_RIGHT);
+
+ SCCOLROW nCol = 0;
+ SCCOLROW nRow = 0;
+ SCCOLROW& rInner = bVertical ? nRow : nCol; // loop variables
+ SCCOLROW& rOuter = bVertical ? nCol : nRow;
+ SCCOLROW nOStart;
+ SCCOLROW nOEnd;
+ SCCOLROW nIStart;
+ SCCOLROW nIEnd;
+ SCCOLROW nISource;
+ ScRange aFillRange;
+ sal_uInt64 nFillerCount;
+ std::vector<bool> aIsNonEmptyCell;
+
+ if (bVertical)
+ {
+ nFillerCount = (nRow2 - nRow1) + 1;
+ nFillCount += (nRow2 - nRow1);
+ if (nFillCount == 0)
+ return;
+ nOStart = nCol1;
+ nOEnd = nCol2;
+ if (bPositive)
+ {
+ // downward fill
+ nISource = nRow1; // top row of the source range.
+ nIStart = nRow1 + 1; // first row where we start filling.
+ nIEnd = nRow1 + nFillCount;
+ aFillRange = ScRange(nCol1, nRow1 + 1, nTab, nCol2, nRow1 + nFillCount, nTab);
+ }
+ else
+ {
+ // upward fill
+ nISource = nRow2;
+ nIStart = nRow2 - 1;
+ nIEnd = nRow2 - nFillCount;
+ aFillRange = ScRange(nCol1, nRow2 -1, nTab, nCol2, nRow2 - nFillCount, nTab);
+ }
+ }
+ else
+ {
+ nFillerCount = (nCol2 - nCol1) + 1;
+ nFillCount += (nCol2 - nCol1);
+ if (nFillCount == 0)
+ return;
+ nOStart = nRow1;
+ nOEnd = nRow2;
+ if (bPositive)
+ {
+ // to the right
+ nISource = nCol1;
+ nIStart = nCol1 + 1;
+ nIEnd = nCol1 + nFillCount;
+ aFillRange = ScRange(nCol1 + 1, nRow1, nTab, nCol1 + nFillCount, nRow2, nTab);
+ }
+ else
+ {
+ // to the left
+ nISource = nCol2;
+ nIStart = nCol2 - 1;
+ nIEnd = nCol2 - nFillCount;
+ aFillRange = ScRange(nCol2 - 1, nRow1, nTab, nCol2 - nFillCount, nRow2, nTab);
+ }
+ }
+
+ SCCOLROW nIMin = nIStart;
+ SCCOLROW nIMax = nIEnd;
+ PutInOrder(nIMin,nIMax);
+
+ const bool bIsFiltered = IsDataFiltered(aFillRange);
+ bool bEntireArea = (!bIsFiltered && eFillCmd == FILL_SIMPLE);
+ if (!bIsFiltered && !bEntireArea && (eFillCmd == FILL_LINEAR || eFillCmd == FILL_GROWTH)
+ && (nOEnd - nOStart == 0))
+ {
+ // For the usual case of one col/row determine if a numeric series is
+ // at least as long as the area to be filled and does not end earlier,
+ // so we can treat it as entire area for performance reasons at least
+ // in the vertical case.
+ // This is not exact in case of merged cell fills with skipping overlapped parts, but
+ // it is still a good upper estimation.
+ ScCellValue aSrcCell;
+ if (bVertical)
+ aSrcCell = GetCellValue(static_cast<SCCOL>(nOStart), static_cast<SCROW>(nISource));
+ else
+ aSrcCell = GetCellValue(static_cast<SCCOL>(nISource), static_cast<SCROW>(nOStart));
+ // Same logic as for the actual series.
+ if (!aSrcCell.isEmpty() && (aSrcCell.meType == CELLTYPE_VALUE || aSrcCell.meType == CELLTYPE_FORMULA))
+ {
+ double nStartVal;
+ if (aSrcCell.meType == CELLTYPE_VALUE)
+ nStartVal = aSrcCell.mfValue;
+ else
+ nStartVal = aSrcCell.mpFormula->GetValue();
+ if (eFillCmd == FILL_LINEAR)
+ {
+ if (nStepValue == 0.0)
+ bEntireArea = (nStartVal <= nMaxValue); // fill with same value
+ else if (((nMaxValue - nStartVal) / nStepValue) >= nFillCount)
+ bEntireArea = true;
+ }
+ else if (eFillCmd == FILL_GROWTH)
+ {
+ if (nStepValue == 1.0)
+ bEntireArea = (nStartVal <= nMaxValue); // fill with same value
+ else if (nStepValue == -1.0)
+ bEntireArea = (fabs(nStartVal) <= fabs(nMaxValue)); // fill with alternating value
+ else if (nStepValue == 0.0)
+ bEntireArea = (nStartVal == 0.0
+ || (nStartVal < 0.0 && nMaxValue >= 0.0)
+ || (nStartVal > 0.0 && nMaxValue <= 0.0)); // fill with 0.0
+ }
+ }
+ }
+ if (bEntireArea)
+ {
+ InsertDeleteFlags nDel = (bAttribs ? InsertDeleteFlags::AUTOFILL :
+ (InsertDeleteFlags::AUTOFILL & InsertDeleteFlags::CONTENTS));
+ if (bVertical)
+ DeleteArea(nCol1, static_cast<SCROW>(nIMin), nCol2, static_cast<SCROW>(nIMax), nDel);
+ else
+ DeleteArea(static_cast<SCCOL>(nIMin), nRow1, static_cast<SCCOL>(nIMax), nRow2, nDel);
+ }
+
+ sal_uInt64 nProgress = 0;
+ if (pProgress)
+ nProgress = pProgress->GetState();
+
+ // Perform the fill once per each 'outer' position i.e. one per column
+ // when filling vertically.
+
+ for (rOuter = nOStart; rOuter <= nOEnd; rOuter++)
+ {
+ rInner = nISource;
+
+ CreateColumnIfNotExists(nCol);
+
+ // Source cell value. We need to clone the value since it may be inserted repeatedly.
+ ScCellValue aSrcCell = GetCellValue(nCol, static_cast<SCROW>(nRow));
+
+ // Maybe another source cell need to be searched, if the fill is going through merged cells,
+ // where overlapped parts does not contain any information, so they can be skipped.
+ if (bSkipOverlappedCells)
+ {
+ // create a vector to make it easier to decide if a cell need to be filled, or skipped.
+ aIsNonEmptyCell.resize(nFillerCount, false);
+
+ SCCOLROW nFirstValueIdx;
+ if (bPositive)
+ {
+ nFirstValueIdx = nISource + (*pNonOverlappedCellIdx)[0];
+ for (auto i : (*pNonOverlappedCellIdx))
+ aIsNonEmptyCell[i] = true;
+ }
+ else
+ {
+ nFirstValueIdx = nISource - (nFillerCount - 1 - (*pNonOverlappedCellIdx).back());
+ for (auto i : (*pNonOverlappedCellIdx))
+ aIsNonEmptyCell[nFillerCount - 1 - i] = true;
+ }
+
+ //Set the real source cell
+ if (bVertical)
+ aSrcCell = GetCellValue(nOStart, static_cast<SCROW>(nFirstValueIdx));
+ else
+ aSrcCell = GetCellValue(nFirstValueIdx, static_cast<SCROW>(nOStart));
+ }
+
+ const ScPatternAttr* pSrcPattern = aCol[nCol].GetPattern(static_cast<SCROW>(nRow));
+ const ScCondFormatItem& rCondFormatItem = pSrcPattern->GetItem(ATTR_CONDITIONAL);
+ const ScCondFormatIndexes& rCondFormatIndex = rCondFormatItem.GetCondFormatData();
+
+ if (bAttribs)
+ {
+ if (bVertical)
+ {
+ // If entire area (not filtered and simple fill) use the faster
+ // method, else hidden cols/rows should be skipped and series
+ // fill needs to determine the end row dynamically.
+ if (bEntireArea)
+ {
+ SetPatternAreaCondFormat( nCol, static_cast<SCROW>(nIMin),
+ static_cast<SCROW>(nIMax), *pSrcPattern, rCondFormatIndex);
+ }
+ else if (eFillCmd == FILL_SIMPLE)
+ {
+ assert(bIsFiltered);
+ for(SCROW nAtRow = static_cast<SCROW>(nIMin); nAtRow <= static_cast<SCROW>(nIMax); ++nAtRow)
+ {
+ if(!RowHidden(nAtRow))
+ {
+ SetPatternAreaCondFormat( nCol, nAtRow, nAtRow, *pSrcPattern, rCondFormatIndex);
+ }
+ }
+
+ }
+ }
+ else if (bEntireArea || eFillCmd == FILL_SIMPLE)
+ {
+ for (SCCOL nAtCol = static_cast<SCCOL>(nIMin); nAtCol <= sal::static_int_cast<SCCOL>(nIMax); nAtCol++)
+ {
+ if(!ColHidden(nAtCol))
+ {
+ SetPatternAreaCondFormat( nAtCol, nRow, nRow, *pSrcPattern, rCondFormatIndex);
+ }
+ }
+ }
+ }
+
+ if (!aSrcCell.isEmpty())
+ {
+ CellType eCellType = aSrcCell.meType;
+
+ if (eFillCmd == FILL_SIMPLE) // copy
+ {
+ FillSeriesSimple(aSrcCell, rInner, nIMin, nIMax, nCol, nRow, bVertical, pProgress, nProgress);
+ }
+ else if (eCellType == CELLTYPE_VALUE || eCellType == CELLTYPE_FORMULA)
+ {
+ const double nStartVal = (eCellType == CELLTYPE_VALUE ? aSrcCell.mfValue :
+ aSrcCell.mpFormula->GetValue());
+ double nVal = nStartVal;
+ tools::Long nIndex = 0;
+
+ bool bError = false;
+ bool bOverflow = false;
+ bool bNonEmpty = true;
+
+ sal_uInt16 nDayOfMonth = 0;
+ sal_Int32 nFillerIdx = 0;
+ if (bSkipOverlappedCells && !aIsNonEmptyCell[0])
+ --nIndex;
+ rInner = nIStart;
+ while (true)
+ {
+ if (bSkipOverlappedCells)
+ {
+ nFillerIdx = (nFillerIdx + 1) % nFillerCount;
+ bNonEmpty = aIsNonEmptyCell[nFillerIdx];
+ }
+
+ if(!ColHidden(nCol) && !RowHidden(nRow))
+ {
+ if (!bError && bNonEmpty)
+ {
+ switch (eFillCmd)
+ {
+ case FILL_LINEAR:
+ {
+ // use multiplication instead of repeated addition
+ // to avoid accumulating rounding errors
+ nVal = nStartVal;
+ double nAdd = nStepValue;
+ if ( !SubTotal::SafeMult( nAdd, static_cast<double>(++nIndex) ) ||
+ !SubTotal::SafePlus( nVal, nAdd ) )
+ bError = true;
+ }
+ break;
+ case FILL_GROWTH:
+ if (!SubTotal::SafeMult(nVal, nStepValue))
+ bError = true;
+ break;
+ case FILL_DATE:
+ if (fabs(nVal) > D_MAX_LONG_)
+ bError = true;
+ else
+ IncDate(nVal, nDayOfMonth, nStepValue, eFillDateCmd);
+ break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+
+ if (!bError)
+ bOverflow = isOverflow( nVal, nMaxValue, nStepValue, nStartVal, eFillCmd);
+ }
+
+ CreateColumnIfNotExists(nCol);
+ if (bError)
+ aCol[nCol].SetError(static_cast<SCROW>(nRow), FormulaError::NoValue);
+ else if (!bOverflow && bNonEmpty)
+ aCol[nCol].SetValue(static_cast<SCROW>(nRow), nVal);
+
+ if (bAttribs && !bEntireArea && !bOverflow)
+ SetPatternAreaCondFormat( nCol, nRow, nRow, *pSrcPattern, rCondFormatIndex);
+ }
+
+ if (rInner == nIEnd || bOverflow)
+ break;
+ if (bPositive)
+ {
+ ++rInner;
+ }
+ else
+ {
+ --rInner;
+ }
+ }
+ nProgress += nIMax - nIMin + 1;
+ if(pProgress)
+ pProgress->SetStateOnPercent( nProgress );
+ }
+ else if (eCellType == CELLTYPE_STRING || eCellType == CELLTYPE_EDIT)
+ {
+ if ( nStepValue >= 0 )
+ {
+ if ( nMaxValue >= double(LONG_MAX) )
+ nMaxValue = double(LONG_MAX) - 1;
+ }
+ else
+ {
+ if ( nMaxValue <= double(LONG_MIN) )
+ nMaxValue = double(LONG_MIN) + 1;
+ }
+ OUString aValue;
+ if (eCellType == CELLTYPE_STRING)
+ aValue = aSrcCell.mpString->getString();
+ else
+ aValue = ScEditUtil::GetString(*aSrcCell.mpEditText, &rDocument);
+ sal_Int32 nStringValue;
+ sal_uInt16 nMinDigits = nArgMinDigits;
+ short nHeadNoneTail = lcl_DecompValueString( aValue, nStringValue, &nMinDigits );
+ if ( nHeadNoneTail )
+ {
+ const double nStartVal = static_cast<double>(nStringValue);
+ double nVal = nStartVal;
+ tools::Long nIndex = 0;
+ bool bError = false;
+ bool bOverflow = false;
+ bool bNonEmpty = true;
+
+ bool bIsOrdinalSuffix = aValue == ScGlobal::GetOrdinalSuffix(
+ static_cast<sal_Int32>(nStartVal));
+
+ sal_Int32 nFillerIdx = 0;
+ if (bSkipOverlappedCells && !aIsNonEmptyCell[0])
+ --nIndex;
+ rInner = nIStart;
+ while (true)
+ {
+ if (bSkipOverlappedCells)
+ {
+ nFillerIdx = (nFillerIdx + 1) % nFillerCount;
+ bNonEmpty = aIsNonEmptyCell[nFillerIdx];
+ }
+ if(!ColHidden(nCol) && !RowHidden(nRow))
+ {
+ if (!bError && bNonEmpty)
+ {
+ switch (eFillCmd)
+ {
+ case FILL_LINEAR:
+ {
+ // use multiplication instead of repeated addition
+ // to avoid accumulating rounding errors
+ nVal = nStartVal;
+ double nAdd = nStepValue;
+ if ( !SubTotal::SafeMult( nAdd, static_cast<double>(++nIndex) ) ||
+ !SubTotal::SafePlus( nVal, nAdd ) )
+ bError = true;
+ }
+ break;
+ case FILL_GROWTH:
+ if (!SubTotal::SafeMult(nVal, nStepValue))
+ bError = true;
+ break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+
+ if (!bError)
+ bOverflow = isOverflow( nVal, nMaxValue, nStepValue, nStartVal, eFillCmd);
+ }
+
+ if (bError)
+ aCol[nCol].SetError(static_cast<SCROW>(nRow), FormulaError::NoValue);
+ else if (!bOverflow && bNonEmpty)
+ {
+ nStringValue = static_cast<sal_Int32>(nVal);
+ if ( nHeadNoneTail < 0 )
+ {
+ setSuffixCell(
+ aCol[nCol], static_cast<SCROW>(nRow),
+ nStringValue, nMinDigits, aValue,
+ eCellType, bIsOrdinalSuffix);
+ }
+ else
+ {
+ OUString aStr;
+ if (nHeadNoneTail == 2 && nStringValue >= 0) // Put back the '+'
+ aStr = aValue + "+";
+ else
+ aStr = aValue;
+ aStr += lcl_ValueString( nStringValue, nMinDigits );
+ aCol[nCol].SetRawString(static_cast<SCROW>(nRow), aStr);
+ }
+ }
+
+ if (bAttribs && !bEntireArea && !bOverflow)
+ SetPatternAreaCondFormat( nCol, nRow, nRow, *pSrcPattern, rCondFormatIndex);
+ }
+
+ if (rInner == nIEnd || bOverflow)
+ break;
+ if (bPositive)
+ ++rInner;
+ else
+ --rInner;
+ }
+ }
+ if(pProgress)
+ {
+ nProgress += nIMax - nIMin + 1;
+ pProgress->SetStateOnPercent( nProgress );
+ }
+ }
+ }
+ else if(pProgress)
+ {
+ nProgress += nIMax - nIMin + 1;
+ pProgress->SetStateOnPercent( nProgress );
+ }
+ }
+}
+
+void ScTable::Fill( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
+ sal_uInt64 nFillCount, FillDir eFillDir, FillCmd eFillCmd, FillDateCmd eFillDateCmd,
+ double nStepValue, double nMaxValue, ScProgress* pProgress)
+{
+ if (eFillCmd == FILL_AUTO)
+ FillAuto(nCol1, nRow1, nCol2, nRow2, nFillCount, eFillDir, pProgress);
+ else
+ FillSeries(nCol1, nRow1, nCol2, nRow2, nFillCount, eFillDir,
+ eFillCmd, eFillDateCmd, nStepValue, nMaxValue, 0, true, pProgress);
+}
+
+void ScTable::AutoFormatArea(SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow,
+ const ScPatternAttr& rAttr, sal_uInt16 nFormatNo)
+{
+ ScAutoFormat& rFormat = *ScGlobal::GetOrCreateAutoFormat();
+ ScAutoFormatData* pData = rFormat.findByIndex(nFormatNo);
+ if (pData)
+ {
+ ApplyPatternArea(nStartCol, nStartRow, nEndCol, nEndRow, rAttr);
+ }
+}
+
+void ScTable::AutoFormat( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow,
+ sal_uInt16 nFormatNo )
+{
+ if (!(ValidColRow(nStartCol, nStartRow) && ValidColRow(nEndCol, nEndRow)))
+ return;
+
+ ScAutoFormat& rFormat = *ScGlobal::GetOrCreateAutoFormat();
+ ScAutoFormatData* pData = rFormat.findByIndex(nFormatNo);
+ if (!pData)
+ return;
+
+ std::unique_ptr<ScPatternAttr> pPatternAttrs[16];
+ for (sal_uInt8 i = 0; i < 16; ++i)
+ {
+ pPatternAttrs[i].reset(new ScPatternAttr(rDocument.GetPool()));
+ pData->FillToItemSet(i, pPatternAttrs[i]->GetItemSet(), rDocument);
+ }
+
+ SCCOL nCol = nStartCol;
+ SCROW nRow = nStartRow;
+ sal_uInt16 nIndex = 0;
+ // Left top corner
+ AutoFormatArea(nCol, nRow, nCol, nRow, *pPatternAttrs[nIndex], nFormatNo);
+ // Left column
+ if (pData->IsEqualData(4, 8))
+ AutoFormatArea(nStartCol, nStartRow + 1, nStartCol, nEndRow - 1, *pPatternAttrs[4], nFormatNo);
+ else
+ {
+ nIndex = 4;
+ for (nRow = nStartRow + 1; nRow < nEndRow; nRow++)
+ {
+ AutoFormatArea(nCol, nRow, nCol, nRow, *pPatternAttrs[nIndex], nFormatNo);
+ if (nIndex == 4)
+ nIndex = 8;
+ else
+ nIndex = 4;
+ }
+ }
+ // Left bottom corner
+ nRow = nEndRow;
+ nIndex = 12;
+ AutoFormatArea(nCol, nRow, nCol, nRow, *pPatternAttrs[nIndex], nFormatNo);
+ // Right top corner
+ nCol = nEndCol;
+ nRow = nStartRow;
+ nIndex = 3;
+ AutoFormatArea(nCol, nRow, nCol, nRow, *pPatternAttrs[nIndex], nFormatNo);
+ // Right column
+ if (pData->IsEqualData(7, 11))
+ AutoFormatArea(nEndCol, nStartRow + 1, nEndCol, nEndRow - 1, *pPatternAttrs[7], nFormatNo);
+ else
+ {
+ nIndex = 7;
+ for (nRow = nStartRow + 1; nRow < nEndRow; nRow++)
+ {
+ AutoFormatArea(nCol, nRow, nCol, nRow, *pPatternAttrs[nIndex], nFormatNo);
+ if (nIndex == 7)
+ nIndex = 11;
+ else
+ nIndex = 7;
+ }
+ }
+ // Right bottom corner
+ nRow = nEndRow;
+ nIndex = 15;
+ AutoFormatArea(nCol, nRow, nCol, nRow, *pPatternAttrs[nIndex], nFormatNo);
+ nRow = nStartRow;
+ nIndex = 1;
+ for (nCol = nStartCol + 1; nCol < nEndCol; nCol++)
+ {
+ AutoFormatArea(nCol, nRow, nCol, nRow, *pPatternAttrs[nIndex], nFormatNo);
+ if (nIndex == 1)
+ nIndex = 2;
+ else
+ nIndex = 1;
+ }
+ // Bottom row
+ nRow = nEndRow;
+ nIndex = 13;
+ for (nCol = nStartCol + 1; nCol < nEndCol; nCol++)
+ {
+ AutoFormatArea(nCol, nRow, nCol, nRow, *pPatternAttrs[nIndex], nFormatNo);
+ if (nIndex == 13)
+ nIndex = 14;
+ else
+ nIndex = 13;
+ }
+ // Body
+ if ((pData->IsEqualData(5, 6)) && (pData->IsEqualData(9, 10)) && (pData->IsEqualData(5, 9)))
+ AutoFormatArea(nStartCol + 1, nStartRow + 1, nEndCol-1, nEndRow - 1, *pPatternAttrs[5], nFormatNo);
+ else
+ {
+ if ((pData->IsEqualData(5, 9)) && (pData->IsEqualData(6, 10)))
+ {
+ nIndex = 5;
+ for (nCol = nStartCol + 1; nCol < nEndCol; nCol++)
+ {
+ AutoFormatArea(nCol, nStartRow + 1, nCol, nEndRow - 1, *pPatternAttrs[nIndex], nFormatNo);
+ if (nIndex == 5)
+ nIndex = 6;
+ else
+ nIndex = 5;
+ }
+ }
+ else
+ {
+ nIndex = 5;
+ for (nCol = nStartCol + 1; nCol < nEndCol; nCol++)
+ {
+ for (nRow = nStartRow + 1; nRow < nEndRow; nRow++)
+ {
+ AutoFormatArea(nCol, nRow, nCol, nRow, *pPatternAttrs[nIndex], nFormatNo);
+ if ((nIndex == 5) || (nIndex == 9))
+ {
+ if (nIndex == 5)
+ nIndex = 9;
+ else
+ nIndex = 5;
+ }
+ else
+ {
+ if (nIndex == 6)
+ nIndex = 10;
+ else
+ nIndex = 6;
+ }
+ } // for nRow
+ if ((nIndex == 5) || (nIndex == 9))
+ nIndex = 6;
+ else
+ nIndex = 5;
+ } // for nCol
+ } // if not equal Column
+ } // if not all equal
+}
+
+void ScTable::GetAutoFormatAttr(SCCOL nCol, SCROW nRow, sal_uInt16 nIndex, ScAutoFormatData& rData)
+{
+ sal_uInt32 nFormatIndex = GetNumberFormat( nCol, nRow );
+ ScNumFormatAbbrev aNumFormat( nFormatIndex, *rDocument.GetFormatTable() );
+ rData.GetFromItemSet( nIndex, GetPattern( nCol, nRow )->GetItemSet(), aNumFormat );
+}
+
+#define LF_LEFT 1
+#define LF_TOP 2
+#define LF_RIGHT 4
+#define LF_BOTTOM 8
+#define LF_ALL (LF_LEFT | LF_TOP | LF_RIGHT | LF_BOTTOM)
+
+void ScTable::GetAutoFormatFrame(SCCOL nCol, SCROW nRow, sal_uInt16 nFlags, sal_uInt16 nIndex, ScAutoFormatData& rData)
+{
+ const SvxBoxItem* pTheBox = GetAttr(nCol, nRow, ATTR_BORDER);
+ const SvxBoxItem* pLeftBox = GetAttr(nCol - 1, nRow, ATTR_BORDER);
+ const SvxBoxItem* pTopBox = GetAttr(nCol, nRow - 1, ATTR_BORDER);
+ const SvxBoxItem* pRightBox = GetAttr(nCol + 1, nRow, ATTR_BORDER);
+ const SvxBoxItem* pBottomBox = GetAttr(nCol, nRow + 1, ATTR_BORDER);
+
+ SvxBoxItem aBox( ATTR_BORDER );
+ if (nFlags & LF_LEFT)
+ {
+ if (pLeftBox)
+ {
+ if (ScHasPriority(pTheBox->GetLeft(), pLeftBox->GetRight()))
+ aBox.SetLine(pTheBox->GetLeft(), SvxBoxItemLine::LEFT);
+ else
+ aBox.SetLine(pLeftBox->GetRight(), SvxBoxItemLine::LEFT);
+ }
+ else
+ aBox.SetLine(pTheBox->GetLeft(), SvxBoxItemLine::LEFT);
+ }
+ if (nFlags & LF_TOP)
+ {
+ if (pTopBox)
+ {
+ if (ScHasPriority(pTheBox->GetTop(), pTopBox->GetBottom()))
+ aBox.SetLine(pTheBox->GetTop(), SvxBoxItemLine::TOP);
+ else
+ aBox.SetLine(pTopBox->GetBottom(), SvxBoxItemLine::TOP);
+ }
+ else
+ aBox.SetLine(pTheBox->GetTop(), SvxBoxItemLine::TOP);
+ }
+ if (nFlags & LF_RIGHT)
+ {
+ if (pRightBox)
+ {
+ if (ScHasPriority(pTheBox->GetRight(), pRightBox->GetLeft()))
+ aBox.SetLine(pTheBox->GetRight(), SvxBoxItemLine::RIGHT);
+ else
+ aBox.SetLine(pRightBox->GetLeft(), SvxBoxItemLine::RIGHT);
+ }
+ else
+ aBox.SetLine(pTheBox->GetRight(), SvxBoxItemLine::RIGHT);
+ }
+ if (nFlags & LF_BOTTOM)
+ {
+ if (pBottomBox)
+ {
+ if (ScHasPriority(pTheBox->GetBottom(), pBottomBox->GetTop()))
+ aBox.SetLine(pTheBox->GetBottom(), SvxBoxItemLine::BOTTOM);
+ else
+ aBox.SetLine(pBottomBox->GetTop(), SvxBoxItemLine::BOTTOM);
+ }
+ else
+ aBox.SetLine(pTheBox->GetBottom(), SvxBoxItemLine::BOTTOM);
+ }
+ rData.PutItem( nIndex, aBox );
+}
+
+void ScTable::GetAutoFormatData(SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, ScAutoFormatData& rData)
+{
+ if (!(ValidColRow(nStartCol, nStartRow) && ValidColRow(nEndCol, nEndRow)))
+ return;
+
+ if ((nEndCol - nStartCol < 3) || (nEndRow - nStartRow < 3))
+ return;
+
+ // Left top corner
+ GetAutoFormatAttr(nStartCol, nStartRow, 0, rData);
+ GetAutoFormatFrame(nStartCol, nStartRow, LF_ALL, 0, rData);
+ // Left column
+ GetAutoFormatAttr(nStartCol, nStartRow + 1, 4, rData);
+ GetAutoFormatAttr(nStartCol, nStartRow + 2, 8, rData);
+ GetAutoFormatFrame(nStartCol, nStartRow + 1, LF_LEFT | LF_RIGHT | LF_BOTTOM, 4, rData);
+ if (nEndRow - nStartRow >= 4)
+ GetAutoFormatFrame(nStartCol, nStartRow + 2, LF_LEFT | LF_RIGHT | LF_BOTTOM, 8, rData);
+ else
+ rData.CopyItem( 8, 4, ATTR_BORDER );
+ // Left bottom corner
+ GetAutoFormatAttr(nStartCol, nEndRow, 12, rData);
+ GetAutoFormatFrame(nStartCol, nEndRow, LF_ALL, 12, rData);
+ // Right top corner
+ GetAutoFormatAttr(nEndCol, nStartRow, 3, rData);
+ GetAutoFormatFrame(nEndCol, nStartRow, LF_ALL, 3, rData);
+ // Right column
+ GetAutoFormatAttr(nEndCol, nStartRow + 1, 7, rData);
+ GetAutoFormatAttr(nEndCol, nStartRow + 2, 11, rData);
+ GetAutoFormatFrame(nEndCol, nStartRow + 1, LF_LEFT | LF_RIGHT | LF_BOTTOM, 7, rData);
+ if (nEndRow - nStartRow >= 4)
+ GetAutoFormatFrame(nEndCol, nStartRow + 2, LF_LEFT | LF_RIGHT | LF_BOTTOM, 11, rData);
+ else
+ rData.CopyItem( 11, 7, ATTR_BORDER );
+ // Right bottom corner
+ GetAutoFormatAttr(nEndCol, nEndRow, 15, rData);
+ GetAutoFormatFrame(nEndCol, nEndRow, LF_ALL, 15, rData);
+ // Top row
+ GetAutoFormatAttr(nStartCol + 1, nStartRow, 1, rData);
+ GetAutoFormatAttr(nStartCol + 2, nStartRow, 2, rData);
+ GetAutoFormatFrame(nStartCol + 1, nStartRow, LF_TOP | LF_BOTTOM | LF_RIGHT, 1, rData);
+ if (nEndCol - nStartCol >= 4)
+ GetAutoFormatFrame(nStartCol + 2, nStartRow, LF_TOP | LF_BOTTOM | LF_RIGHT, 2, rData);
+ else
+ rData.CopyItem( 2, 1, ATTR_BORDER );
+ // Bottom row
+ GetAutoFormatAttr(nStartCol + 1, nEndRow, 13, rData);
+ GetAutoFormatAttr(nStartCol + 2, nEndRow, 14, rData);
+ GetAutoFormatFrame(nStartCol + 1, nEndRow, LF_TOP | LF_BOTTOM | LF_RIGHT, 13, rData);
+ if (nEndCol - nStartCol >= 4)
+ GetAutoFormatFrame(nStartCol + 2, nEndRow, LF_TOP | LF_BOTTOM | LF_RIGHT, 14, rData);
+ else
+ rData.CopyItem( 14, 13, ATTR_BORDER );
+ // Body
+ GetAutoFormatAttr(nStartCol + 1, nStartRow + 1, 5, rData);
+ GetAutoFormatAttr(nStartCol + 2, nStartRow + 1, 6, rData);
+ GetAutoFormatAttr(nStartCol + 1, nStartRow + 2, 9, rData);
+ GetAutoFormatAttr(nStartCol + 2, nStartRow + 2, 10, rData);
+ GetAutoFormatFrame(nStartCol + 1, nStartRow + 1, LF_RIGHT | LF_BOTTOM, 5, rData);
+ if ((nEndCol - nStartCol >= 4) && (nEndRow - nStartRow >= 4))
+ {
+ GetAutoFormatFrame(nStartCol + 2, nStartRow + 1, LF_RIGHT | LF_BOTTOM, 6, rData);
+ GetAutoFormatFrame(nStartCol + 1, nStartRow + 2, LF_RIGHT | LF_BOTTOM, 9, rData);
+ GetAutoFormatFrame(nStartCol + 2, nStartRow + 2, LF_RIGHT | LF_BOTTOM, 10, rData);
+ }
+ else
+ {
+ rData.CopyItem( 6, 5, ATTR_BORDER );
+ rData.CopyItem( 9, 5, ATTR_BORDER );
+ rData.CopyItem( 10, 5, ATTR_BORDER );
+ }
+}
+
+void ScTable::SetError( SCCOL nCol, SCROW nRow, FormulaError nError)
+{
+ if (ValidColRow(nCol, nRow))
+ aCol[nCol].SetError( nRow, nError );
+}
+
+void ScTable::UpdateInsertTabAbs(SCTAB nTable)
+{
+ for (SCCOL i=0; i < aCol.size(); i++)
+ aCol[i].UpdateInsertTabAbs(nTable);
+}
+
+bool ScTable::GetNextSpellingCell(SCCOL& rCol, SCROW& rRow, bool bInSel,
+ const ScMarkData& rMark) const
+{
+ if (rRow == rDocument.MaxRow()+2) // end of table
+ {
+ rRow = 0;
+ rCol = 0;
+ }
+ else
+ {
+ rRow++;
+ if (rRow == rDocument.MaxRow()+1)
+ {
+ rCol++;
+ rRow = 0;
+ }
+ }
+ if (rCol == rDocument.MaxCol()+1)
+ return true;
+ for (;;)
+ {
+ if (!ValidCol(rCol))
+ return true;
+ if (rCol >= GetAllocatedColumnsCount())
+ return true;
+ if (aCol[rCol].GetNextSpellingCell(rRow, bInSel, rMark))
+ return true;
+ /*else (rRow == rDocument.MaxRow()+1) */
+ rCol++;
+ rRow = 0;
+ }
+}
+
+void ScTable::TestTabRefAbs(SCTAB nTable) const
+{
+ for (SCCOL i=0; i < aCol.size(); i++)
+ if (aCol[i].TestTabRefAbs(nTable))
+ return;
+}
+
+void ScTable::CompileDBFormula( sc::CompileFormulaContext& rCxt )
+{
+ for (SCCOL i = 0; i < aCol.size(); ++i)
+ aCol[i].CompileDBFormula(rCxt);
+}
+
+void ScTable::CompileColRowNameFormula( sc::CompileFormulaContext& rCxt )
+{
+ for (SCCOL i = 0; i < aCol.size(); ++i)
+ aCol[i].CompileColRowNameFormula(rCxt);
+}
+
+SCSIZE ScTable::GetPatternCount( SCCOL nCol ) const
+{
+ if( ValidCol( nCol ) )
+ return aCol[nCol].GetPatternCount();
+ else
+ return 0;
+}
+
+SCSIZE ScTable::GetPatternCount( SCCOL nCol, SCROW nRow1, SCROW nRow2 ) const
+{
+ if( ValidCol( nCol ) && ValidRow( nRow1 ) && ValidRow( nRow2 ) )
+ return aCol[nCol].GetPatternCount( nRow1, nRow2 );
+ else
+ return 0;
+}
+
+bool ScTable::ReservePatternCount( SCCOL nCol, SCSIZE nReserve )
+{
+ if( ValidCol( nCol ) )
+ return aCol[nCol].ReservePatternCount( nReserve );
+ else
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/table5.cxx b/sc/source/core/data/table5.cxx
new file mode 100644
index 000000000..cddb28e6e
--- /dev/null
+++ b/sc/source/core/data/table5.cxx
@@ -0,0 +1,1295 @@
+/* -*- 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 <attrib.hxx>
+#include <formulacell.hxx>
+#include <table.hxx>
+#include <column.hxx>
+#include <document.hxx>
+#include <drwlayer.hxx>
+#include <global.hxx>
+#include <stlpool.hxx>
+#include <tabprotection.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <segmenttree.hxx>
+#include <columniterator.hxx>
+#include <globalnames.hxx>
+#include <scmod.hxx>
+#include <printopt.hxx>
+#include <bcaslot.hxx>
+#include <compressedarray.hxx>
+#include <userdat.hxx>
+
+#include <com/sun/star/sheet/TablePageBreakData.hpp>
+
+#include <osl/diagnose.h>
+
+#include <algorithm>
+#include <limits>
+
+using ::com::sun::star::uno::Sequence;
+using ::com::sun::star::sheet::TablePageBreakData;
+using ::std::set;
+
+void ScTable::UpdatePageBreaks(const ScRange* pUserArea)
+{
+ if (rDocument.IsImportingXML())
+ return;
+
+ // pUserArea != NULL -> print area is specified. We need to force-update
+ // the page breaks.
+
+ if (!pUserArea)
+ {
+ if (!bPageSizeValid)
+ return;
+
+ // Always update breaks if force breaks option has changed
+ if (mbPageBreaksValid && mbForceBreaks == SC_MOD()->GetPrintOptions().GetForceBreaks())
+ return;
+ }
+
+ SfxStyleSheetBase* pStyle
+ = rDocument.GetStyleSheetPool()->Find(aPageStyle, SfxStyleFamily::Page);
+ if (!pStyle)
+ {
+ OSL_FAIL("UpdatePageBreaks: Style not found");
+ return;
+ }
+ SfxItemSet* pStyleSet = &pStyle->GetItemSet();
+
+ SCCOL nStartCol = 0;
+ SCROW nStartRow = 0;
+ SCCOL nEndCol = rDocument.MaxCol();
+ SCROW nEndRow = rDocument.MaxRow();
+ if (pUserArea)
+ {
+ nStartCol = pUserArea->aStart.Col();
+ nStartRow = pUserArea->aStart.Row();
+ nEndCol = pUserArea->aEnd.Col();
+ nEndRow = pUserArea->aEnd.Row();
+ }
+ else
+ {
+ sal_uInt16 nAreaCount = GetPrintRangeCount();
+ if (nAreaCount > 1)
+ {
+ // Show nothing, when multiple ranges
+
+ for (SCCOL nX : GetColumnsRange(0, rDocument.MaxCol()))
+ RemoveColBreak(nX, true, false);
+
+ RemoveRowPageBreaks(0, rDocument.MaxRow() - 1);
+
+ return;
+ }
+ else if (nAreaCount == 1)
+ {
+ const ScRange* pArea = GetPrintRange(0);
+ if (pArea)
+ {
+ nStartCol = pArea->aStart.Col();
+ nStartRow = pArea->aStart.Row();
+ nEndCol = pArea->aEnd.Col();
+ nEndRow = pArea->aEnd.Row();
+ }
+ } // otherwise show everything
+ }
+
+ // get bSkipColBreaks/bSkipRowBreaks flags:
+ // fdo#40788 - print range scale settings can cause manual breaks to be
+ // ignored (see below). This behaviour may now be set by the user.
+ mbForceBreaks = SC_MOD()->GetPrintOptions().GetForceBreaks();
+ bool bSkipColBreaks = false;
+ bool bSkipRowBreaks = false;
+
+ if (!mbForceBreaks)
+ {
+ if (const SfxUInt16Item* pItem = pStyleSet->GetItemIfSet(ATTR_PAGE_SCALETOPAGES, false))
+ {
+ bSkipColBreaks = bSkipRowBreaks = pItem->GetValue() > 0;
+ }
+
+ const ScPageScaleToItem* pScaleToItem;
+ if (!bSkipColBreaks && (pScaleToItem = pStyleSet->GetItemIfSet(ATTR_PAGE_SCALETO, false)))
+ {
+ // #i54993# when fitting to width or height, ignore only manual breaks in that direction
+ if (pScaleToItem->GetWidth() > 0)
+ bSkipColBreaks = true;
+ if (pScaleToItem->GetHeight() > 0)
+ bSkipRowBreaks = true;
+ }
+ }
+
+ tools::Long nPageSizeX = aPageSizeTwips.Width();
+ tools::Long nPageSizeY = aPageSizeTwips.Height();
+
+ // Beginning: Remove breaks
+
+ for (SCCOL nX : GetColumnsRange(0, nStartCol - 1))
+ RemoveColBreak(nX, true, false);
+ RemoveRowPageBreaks(0, nStartRow - 1);
+
+ if (nStartCol > 0)
+ SetColBreak(nStartCol, true, false); // AREABREAK
+ if (nStartRow > 0)
+ SetRowBreak(nStartRow, true, false); // AREABREAK
+
+ // Middle part: Distribute breaks
+
+ bool bRepeatCol = (nRepeatStartX != SCCOL_REPEAT_NONE);
+ bool bColFound = false;
+ tools::Long nSizeX = 0;
+ for (SCCOL nX = nStartCol; nX <= nEndCol; nX++)
+ {
+ bool bStartOfPage = false;
+ tools::Long nThisX = ColHidden(nX) ? 0 : mpColWidth->GetValue(nX);
+ bool bManualBreak = HasColManualBreak(nX);
+ if ((nSizeX + nThisX > nPageSizeX) || (bManualBreak && !bSkipColBreaks))
+ {
+ SetColBreak(nX, true, false);
+ nSizeX = 0;
+ bStartOfPage = true;
+ }
+ else if (nX != nStartCol)
+ RemoveColBreak(nX, true, false);
+ else
+ bStartOfPage = true;
+
+ if (bStartOfPage && bRepeatCol && nX > nRepeatStartX && !bColFound)
+ {
+ // subtract size of repeat columns from page size
+ for (SCCOL i = nRepeatStartX; i <= nRepeatEndX; i++)
+ nPageSizeX -= ColHidden(i) ? 0 : mpColWidth->GetValue(i);
+ while (nX <= nRepeatEndX)
+ RemoveColBreak(++nX, true, false);
+ bColFound = true;
+ }
+
+ nSizeX += nThisX;
+ }
+
+ // Remove all page breaks in range.
+ RemoveRowPageBreaks(nStartRow + 1, nEndRow);
+
+ // And set new page breaks.
+ bool bRepeatRow = (nRepeatStartY != SCROW_REPEAT_NONE);
+ bool bRowFound = false;
+ tools::Long nSizeY = 0;
+ ScFlatBoolRowSegments::ForwardIterator aIterHidden(*mpHiddenRows);
+ ScFlatUInt16RowSegments::ForwardIterator aIterHeights(*mpRowHeights);
+ SCROW nNextManualBreak = GetNextManualBreak(nStartRow); // -1 => no more manual breaks
+ for (SCROW nY = nStartRow; nY <= nEndRow; ++nY)
+ {
+ bool bStartOfPage = false;
+ bool bThisRowHidden = false;
+ const bool bHasValue = aIterHidden.getValue(nY, bThisRowHidden);
+ assert(bHasValue);
+ (void)bHasValue;
+ tools::Long nThisY = 0;
+ if (!bThisRowHidden)
+ {
+ sal_uInt16 nTmp;
+ const bool bHasHeight = aIterHeights.getValue(nY, nTmp);
+ assert(bHasHeight);
+ if (bHasHeight)
+ nThisY = static_cast<tools::Long>(nTmp);
+ }
+
+ bool bManualBreak = false;
+ if (nNextManualBreak >= 0)
+ {
+ bManualBreak = (nY == nNextManualBreak);
+ if (nY >= nNextManualBreak)
+ // Query the next manual break position.
+ nNextManualBreak = GetNextManualBreak(nY + 1);
+ }
+
+ if ((nSizeY + nThisY > nPageSizeY) || (bManualBreak && !bSkipRowBreaks))
+ {
+ SetRowBreak(nY, true, false);
+ nSizeY = 0;
+ bStartOfPage = true;
+ }
+ else if (nY != nStartRow)
+ ; // page break already removed
+ else
+ bStartOfPage = true;
+
+ if (bStartOfPage && bRepeatRow && nY > nRepeatStartY && !bRowFound)
+ {
+ // subtract size of repeat rows from page size
+ tools::Long nHeights = GetTotalRowHeight(nRepeatStartY, nRepeatEndY);
+#if OSL_DEBUG_LEVEL > 0
+ if (nHeights == ::std::numeric_limits<tools::Long>::max())
+ OSL_FAIL("ScTable::UpdatePageBreaks: row heights overflow");
+#endif
+ nPageSizeY -= nHeights;
+ if (nY <= nRepeatEndY)
+ RemoveRowPageBreaks(nY, nRepeatEndY);
+ bRowFound = true;
+ }
+
+ if (bThisRowHidden)
+ {
+ // Hidden row range. Skip them unless there is a manual break.
+ SCROW nLastCommon = aIterHidden.getLastPos();
+ if (nNextManualBreak >= 0)
+ nLastCommon = ::std::min(nLastCommon, nNextManualBreak - 1);
+ nY = nLastCommon;
+ }
+ else
+ {
+ // Visible row range.
+
+ SCROW nLastHidden = aIterHidden.getLastPos();
+ SCROW nLastHeight = aIterHeights.getLastPos();
+ SCROW nLastCommon = ::std::min(nLastHidden, nLastHeight);
+ if (nNextManualBreak >= 0)
+ nLastCommon = ::std::min(nLastCommon, nNextManualBreak - 1);
+
+ if (nLastCommon > nY)
+ {
+ tools::Long nMaxMultiple = static_cast<tools::Long>(nLastCommon - nY);
+ tools::Long nMultiple = (nPageSizeY - nSizeY) / nThisY;
+ if (nMultiple > nMaxMultiple)
+ nMultiple = nMaxMultiple;
+ if (nMultiple > 1)
+ {
+ nSizeY += nThisY * (nMultiple - 1);
+ nY += nMultiple - 1;
+ }
+ }
+ }
+
+ nSizeY += nThisY;
+ }
+
+ // End: Remove Break
+
+ if (nEndCol < rDocument.MaxCol())
+ {
+ SetColBreak(nEndCol + 1, true, false); // AREABREAK
+ for (SCCOL nCol : GetColumnsRange(nEndCol + 2, rDocument.MaxCol()))
+ RemoveColBreak(nCol, true, false);
+ }
+ if (nEndRow < rDocument.MaxRow())
+ {
+ SetRowBreak(nEndRow + 1, true, false); // AREABREAK
+ if (nEndRow + 2 <= rDocument.MaxRow())
+ RemoveRowPageBreaks(nEndRow + 2, rDocument.MaxRow());
+ }
+ mbPageBreaksValid
+ = !pUserArea; // #i116881# the valid flag can only apply to the "no user area" case
+}
+
+void ScTable::RemoveManualBreaks()
+{
+ maRowManualBreaks.clear();
+ maColManualBreaks.clear();
+ InvalidatePageBreaks();
+
+ SetStreamValid(false);
+}
+
+bool ScTable::HasManualBreaks() const
+{
+ return !maRowManualBreaks.empty() || !maColManualBreaks.empty();
+}
+
+void ScTable::SetRowManualBreaks(::std::set<SCROW>&& rBreaks)
+{
+ maRowManualBreaks = std::move(rBreaks);
+ InvalidatePageBreaks();
+ SetStreamValid(false);
+}
+
+void ScTable::SetColManualBreaks(::std::set<SCCOL>&& rBreaks)
+{
+ maColManualBreaks = std::move(rBreaks);
+ InvalidatePageBreaks();
+ SetStreamValid(false);
+}
+
+void ScTable::GetAllRowBreaks(set<SCROW>& rBreaks, bool bPage, bool bManual) const
+{
+ if (bPage)
+ rBreaks = maRowPageBreaks;
+
+ if (bManual)
+ {
+ copy(maRowManualBreaks.begin(), maRowManualBreaks.end(),
+ inserter(rBreaks, rBreaks.begin()));
+ }
+}
+
+void ScTable::GetAllColBreaks(set<SCCOL>& rBreaks, bool bPage, bool bManual) const
+{
+ if (bPage)
+ rBreaks = maColPageBreaks;
+
+ if (bManual)
+ {
+ copy(maColManualBreaks.begin(), maColManualBreaks.end(),
+ inserter(rBreaks, rBreaks.begin()));
+ }
+}
+
+bool ScTable::HasRowPageBreak(SCROW nRow) const
+{
+ if (!ValidRow(nRow))
+ return false;
+
+ return maRowPageBreaks.find(nRow) != maRowPageBreaks.end();
+}
+
+bool ScTable::HasColPageBreak(SCCOL nCol) const
+{
+ if (!ValidCol(nCol))
+ return false;
+
+ return maColPageBreaks.find(nCol) != maColPageBreaks.end();
+}
+
+bool ScTable::HasRowManualBreak(SCROW nRow) const
+{
+ if (!ValidRow(nRow))
+ return false;
+
+ return maRowManualBreaks.find(nRow) != maRowManualBreaks.end();
+}
+
+bool ScTable::HasColManualBreak(SCCOL nCol) const
+{
+ if (!ValidCol(nCol))
+ return false;
+
+ return maColManualBreaks.find(nCol) != maColManualBreaks.end();
+}
+
+SCROW ScTable::GetNextManualBreak(SCROW nRow) const
+{
+ set<SCROW>::const_iterator itr = maRowManualBreaks.lower_bound(nRow);
+ return itr == maRowManualBreaks.end() ? -1 : *itr;
+}
+
+void ScTable::RemoveRowPageBreaks(SCROW nStartRow, SCROW nEndRow)
+{
+ if (!ValidRow(nStartRow) || !ValidRow(nEndRow))
+ return;
+
+ set<SCROW>::iterator low = maRowPageBreaks.lower_bound(nStartRow);
+ set<SCROW>::iterator high = maRowPageBreaks.upper_bound(nEndRow);
+ maRowPageBreaks.erase(low, high);
+}
+
+void ScTable::RemoveRowBreak(SCROW nRow, bool bPage, bool bManual)
+{
+ if (!ValidRow(nRow))
+ return;
+
+ if (bPage)
+ maRowPageBreaks.erase(nRow);
+
+ if (bManual)
+ {
+ maRowManualBreaks.erase(nRow);
+ InvalidatePageBreaks();
+ }
+}
+
+void ScTable::RemoveColBreak(SCCOL nCol, bool bPage, bool bManual)
+{
+ if (!ValidCol(nCol))
+ return;
+
+ if (bPage)
+ maColPageBreaks.erase(nCol);
+
+ if (bManual)
+ {
+ maColManualBreaks.erase(nCol);
+ InvalidatePageBreaks();
+ }
+}
+
+void ScTable::SetRowBreak(SCROW nRow, bool bPage, bool bManual)
+{
+ if (!ValidRow(nRow))
+ return;
+
+ if (bPage)
+ maRowPageBreaks.insert(nRow);
+
+ if (bManual)
+ {
+ maRowManualBreaks.insert(nRow);
+ InvalidatePageBreaks();
+ }
+}
+
+void ScTable::SetColBreak(SCCOL nCol, bool bPage, bool bManual)
+{
+ if (!ValidCol(nCol))
+ return;
+
+ if (bPage)
+ maColPageBreaks.insert(nCol);
+
+ if (bManual)
+ {
+ maColManualBreaks.insert(nCol);
+ InvalidatePageBreaks();
+ }
+}
+
+Sequence<TablePageBreakData> ScTable::GetRowBreakData() const
+{
+ using ::std::inserter;
+
+ set<SCROW> aRowBreaks = maRowPageBreaks;
+ copy(maRowManualBreaks.begin(), maRowManualBreaks.end(),
+ inserter(aRowBreaks, aRowBreaks.begin()));
+
+ Sequence<TablePageBreakData> aSeq(aRowBreaks.size());
+ std::transform(aRowBreaks.begin(), aRowBreaks.end(), aSeq.getArray(), [this](const SCROW nRow) {
+ return TablePageBreakData(nRow, HasRowManualBreak(nRow));
+ });
+
+ return aSeq;
+}
+
+bool ScTable::RowHidden(SCROW nRow, SCROW* pFirstRow, SCROW* pLastRow) const
+{
+ if (!ValidRow(nRow))
+ {
+ if (pFirstRow)
+ *pFirstRow = nRow;
+ if (pLastRow)
+ *pLastRow = nRow;
+ return true;
+ }
+
+ ScFlatBoolRowSegments::RangeData aData;
+ if (!mpHiddenRows->getRangeData(nRow, aData))
+ {
+ // search failed.
+ if (pFirstRow)
+ *pFirstRow = nRow;
+ if (pLastRow)
+ *pLastRow = nRow;
+ return true;
+ }
+
+ if (pFirstRow)
+ *pFirstRow = aData.mnRow1;
+ if (pLastRow)
+ *pLastRow = aData.mnRow2;
+
+ return aData.mbValue;
+}
+
+bool ScTable::RowHiddenLeaf(SCROW nRow, SCROW* pFirstRow, SCROW* pLastRow) const
+{
+ if (!ValidRow(nRow))
+ {
+ if (pFirstRow)
+ *pFirstRow = nRow;
+ if (pLastRow)
+ *pLastRow = nRow;
+ return true;
+ }
+
+ ScFlatBoolRowSegments::RangeData aData;
+ if (!mpHiddenRows->getRangeDataLeaf(nRow, aData))
+ {
+ // search failed.
+ if (pFirstRow)
+ *pFirstRow = nRow;
+ if (pLastRow)
+ *pLastRow = nRow;
+ return true;
+ }
+
+ if (pFirstRow)
+ *pFirstRow = aData.mnRow1;
+ if (pLastRow)
+ *pLastRow = aData.mnRow2;
+
+ return aData.mbValue;
+}
+
+bool ScTable::HasHiddenRows(SCROW nStartRow, SCROW nEndRow) const
+{
+ SCROW nRow = nStartRow;
+ while (nRow <= nEndRow)
+ {
+ SCROW nLastRow = -1;
+ bool bHidden = RowHidden(nRow, nullptr, &nLastRow);
+ if (bHidden)
+ return true;
+
+ nRow = nLastRow + 1;
+ }
+ return false;
+}
+
+bool ScTable::ColHidden(SCCOL nCol, SCCOL* pFirstCol, SCCOL* pLastCol) const
+{
+ if (!ValidCol(nCol))
+ return true;
+
+ ScFlatBoolColSegments::RangeData aData;
+ if (!mpHiddenCols->getRangeData(nCol, aData))
+ return true;
+
+ if (pFirstCol)
+ *pFirstCol = aData.mnCol1;
+ if (pLastCol)
+ *pLastCol = aData.mnCol2;
+
+ return aData.mbValue;
+}
+
+bool ScTable::SetRowHidden(SCROW nStartRow, SCROW nEndRow, bool bHidden)
+{
+ bool bChanged = false;
+ if (bHidden)
+ bChanged = mpHiddenRows->setTrue(nStartRow, nEndRow);
+ else
+ bChanged = mpHiddenRows->setFalse(nStartRow, nEndRow);
+
+ // Cell anchored objects might change visibility
+ ScDrawLayer* pDrawLayer = rDocument.GetDrawLayer();
+ if (pDrawLayer)
+ {
+ std::vector<SdrObject*> aRowDrawObjects;
+ aRowDrawObjects = pDrawLayer->GetObjectsAnchoredToRows(GetTab(), nStartRow, nEndRow);
+ for (auto aObj : aRowDrawObjects)
+ {
+ ScDrawObjData* pData = ScDrawLayer::GetObjData(aObj);
+ if (pData)
+ {
+ if (bHidden)
+ aObj->SetVisible(false);
+ else if (!GetDoc().ColHidden(pData->maStart.Col(), pData->maStart.Tab()))
+ {
+ // Only change visibility if object is not hidden by a hidden col
+ aObj->SetVisible(true);
+ }
+ }
+ }
+ }
+
+ if (bChanged)
+ {
+ SetStreamValid(false);
+
+ { // Scoped bulk broadcast.
+ // Only subtotal formula cells will accept the notification of
+ // SfxHintId::ScHiddenRowsChanged, leaving the bulk will track
+ // those and broadcast SfxHintId::ScDataChanged to notify all
+ // dependents.
+ ScBulkBroadcast aBulkBroadcast(rDocument.GetBASM(), SfxHintId::ScDataChanged);
+ for (SCCOL i = 0; i < aCol.size(); i++)
+ {
+ aCol[i].BroadcastRows(nStartRow, nEndRow, SfxHintId::ScHiddenRowsChanged);
+ }
+ }
+ }
+
+ return bChanged;
+}
+
+void ScTable::SetColHidden(SCCOL nStartCol, SCCOL nEndCol, bool bHidden)
+{
+ bool bChanged = false;
+ if (bHidden)
+ bChanged = mpHiddenCols->setTrue(nStartCol, nEndCol);
+ else
+ bChanged = mpHiddenCols->setFalse(nStartCol, nEndCol);
+
+ // Cell anchored objects might change visibility
+ ScDrawLayer* pDrawLayer = rDocument.GetDrawLayer();
+ if (pDrawLayer)
+ {
+ std::vector<SdrObject*> aColDrawObjects;
+ aColDrawObjects = pDrawLayer->GetObjectsAnchoredToCols(GetTab(), nStartCol, nEndCol);
+ for (auto aObj : aColDrawObjects)
+ {
+ ScDrawObjData* pData = ScDrawLayer::GetObjData(aObj);
+ if (pData)
+ {
+ if (bHidden)
+ aObj->SetVisible(false);
+ else if (!GetDoc().RowHidden(pData->maStart.Row(), pData->maStart.Tab()))
+ {
+ // Only change visibility if object is not hidden by a hidden row
+ aObj->SetVisible(true);
+ }
+ }
+ }
+ }
+
+ if (bChanged)
+ SetStreamValid(false);
+}
+
+void ScTable::CopyColHidden(const ScTable& rTable, SCCOL nStartCol, SCCOL nEndCol)
+{
+ SCCOL nCol = nStartCol;
+ while (nCol <= nEndCol)
+ {
+ SCCOL nLastCol = -1;
+ bool bHidden = rTable.ColHidden(nCol, nullptr, &nLastCol);
+ if (nLastCol > nEndCol)
+ nLastCol = nEndCol;
+
+ SetColHidden(nCol, nLastCol, bHidden);
+ nCol = nLastCol + 1;
+ }
+}
+
+void ScTable::CopyRowHidden(const ScTable& rTable, SCROW nStartRow, SCROW nEndRow)
+{
+ SCROW nRow = nStartRow;
+ while (nRow <= nEndRow)
+ {
+ SCROW nLastRow = -1;
+ bool bHidden = rTable.RowHidden(nRow, nullptr, &nLastRow);
+ if (nLastRow > nEndRow)
+ nLastRow = nEndRow;
+ SetRowHidden(nRow, nLastRow, bHidden);
+ nRow = nLastRow + 1;
+ }
+}
+
+void ScTable::CopyRowHeight(const ScTable& rSrcTable, SCROW nStartRow, SCROW nEndRow,
+ SCROW nSrcOffset)
+{
+ SCROW nRow = nStartRow;
+ ScFlatUInt16RowSegments::RangeData aSrcData;
+ while (nRow <= nEndRow)
+ {
+ if (!rSrcTable.mpRowHeights->getRangeData(nRow + nSrcOffset, aSrcData))
+ // Something is wrong !
+ return;
+
+ SCROW nLastRow = aSrcData.mnRow2 - nSrcOffset;
+ if (nLastRow > nEndRow)
+ nLastRow = nEndRow;
+
+ mpRowHeights->setValue(nRow, nLastRow, aSrcData.mnValue);
+ nRow = nLastRow + 1;
+ }
+}
+
+SCROW ScTable::FirstVisibleRow(SCROW nStartRow, SCROW nEndRow) const
+{
+ SCROW nRow = nStartRow;
+ ScFlatBoolRowSegments::RangeData aData;
+ while (nRow <= nEndRow)
+ {
+ if (!ValidRow(nRow))
+ break;
+
+ if (!mpHiddenRows->getRangeData(nRow, aData))
+ // failed to get range data.
+ break;
+
+ if (!aData.mbValue)
+ // visible row found
+ return nRow;
+
+ nRow = aData.mnRow2 + 1;
+ }
+
+ return ::std::numeric_limits<SCROW>::max();
+}
+
+SCROW ScTable::LastVisibleRow(SCROW nStartRow, SCROW nEndRow) const
+{
+ SCROW nRow = nEndRow;
+ ScFlatBoolRowSegments::RangeData aData;
+ while (nRow >= nStartRow)
+ {
+ if (!ValidRow(nRow))
+ break;
+
+ if (!mpHiddenRows->getRangeData(nRow, aData))
+ // failed to get range data.
+ break;
+
+ if (!aData.mbValue)
+ // visible row found
+ return nRow;
+
+ nRow = aData.mnRow1 - 1;
+ }
+
+ return ::std::numeric_limits<SCROW>::max();
+}
+
+SCROW ScTable::CountVisibleRows(SCROW nStartRow, SCROW nEndRow) const
+{
+ SCROW nCount = 0;
+ SCROW nRow = nStartRow;
+ ScFlatBoolRowSegments::RangeData aData;
+ while (nRow <= nEndRow)
+ {
+ if (!mpHiddenRows->getRangeData(nRow, aData))
+ break;
+
+ if (aData.mnRow2 > nEndRow)
+ aData.mnRow2 = nEndRow;
+
+ if (!aData.mbValue)
+ nCount += aData.mnRow2 - nRow + 1;
+
+ nRow = aData.mnRow2 + 1;
+ }
+ return nCount;
+}
+
+tools::Long ScTable::GetTotalRowHeight(SCROW nStartRow, SCROW nEndRow, bool bHiddenAsZero) const
+{
+ tools::Long nHeight = 0;
+ SCROW nRow = nStartRow;
+ ScFlatBoolRowSegments::RangeData aData;
+ while (nRow <= nEndRow)
+ {
+ if (!mpHiddenRows->getRangeData(nRow, aData))
+ break;
+
+ if (aData.mnRow2 > nEndRow)
+ aData.mnRow2 = nEndRow;
+
+ if (!(bHiddenAsZero && aData.mbValue))
+ // visible row range.
+ nHeight += mpRowHeights->getSumValue(nRow, aData.mnRow2);
+
+ nRow = aData.mnRow2 + 1;
+ }
+
+ return nHeight;
+}
+
+SCCOL ScTable::CountVisibleCols(SCCOL nStartCol, SCCOL nEndCol) const
+{
+ assert(nStartCol <= nEndCol);
+ SCCOL nCount = 0;
+ SCCOL nCol = nStartCol;
+ ScFlatBoolColSegments::RangeData aData;
+ while (nCol <= nEndCol)
+ {
+ if (!mpHiddenCols->getRangeData(nCol, aData))
+ break;
+
+ if (aData.mnCol2 > nEndCol)
+ aData.mnCol2 = nEndCol;
+
+ if (!aData.mbValue)
+ nCount += aData.mnCol2 - nCol + 1;
+
+ nCol = aData.mnCol2 + 1;
+ }
+ return nCount;
+}
+
+SCCOLROW ScTable::LastHiddenColRow(SCCOLROW nPos, bool bCol) const
+{
+ if (bCol)
+ {
+ SCCOL nCol = static_cast<SCCOL>(nPos);
+ if (ColHidden(nCol))
+ {
+ for (SCCOL i = nCol + 1; i <= rDocument.MaxCol(); ++i)
+ {
+ if (!ColHidden(i))
+ return i - 1;
+ }
+ }
+ }
+ else
+ {
+ SCROW nRow = static_cast<SCROW>(nPos);
+ SCROW nLastRow;
+ if (RowHidden(nRow, nullptr, &nLastRow))
+ return static_cast<SCCOLROW>(nLastRow);
+ }
+ return ::std::numeric_limits<SCCOLROW>::max();
+}
+
+bool ScTable::RowFiltered(SCROW nRow, SCROW* pFirstRow, SCROW* pLastRow) const
+{
+ if (!ValidRow(nRow))
+ return false;
+
+ ScFlatBoolRowSegments::RangeData aData;
+ if (!mpFilteredRows->getRangeData(nRow, aData))
+ // search failed.
+ return false;
+
+ if (pFirstRow)
+ *pFirstRow = aData.mnRow1;
+ if (pLastRow)
+ *pLastRow = aData.mnRow2;
+
+ return aData.mbValue;
+}
+
+bool ScTable::ColFiltered(SCCOL nCol, SCCOL* pFirstCol, SCCOL* pLastCol) const
+{
+ if (!ValidCol(nCol))
+ return false;
+
+ ScFlatBoolColSegments::RangeData aData;
+ if (!mpFilteredCols->getRangeData(nCol, aData))
+ // search failed.
+ return false;
+
+ if (pFirstCol)
+ *pFirstCol = aData.mnCol1;
+ if (pLastCol)
+ *pLastCol = aData.mnCol2;
+
+ return aData.mbValue;
+}
+
+bool ScTable::HasFilteredRows(SCROW nStartRow, SCROW nEndRow) const
+{
+ SCROW nRow = nStartRow;
+ while (nRow <= nEndRow)
+ {
+ SCROW nLastRow = nRow;
+ bool bFiltered = RowFiltered(nRow, nullptr, &nLastRow);
+ if (bFiltered)
+ return true;
+
+ nRow = nLastRow + 1;
+ }
+ return false;
+}
+
+void ScTable::CopyColFiltered(const ScTable& rTable, SCCOL nStartCol, SCCOL nEndCol)
+{
+ SCCOL nCol = nStartCol;
+ while (nCol <= nEndCol)
+ {
+ SCCOL nLastCol = -1;
+ bool bFiltered = rTable.ColFiltered(nCol, nullptr, &nLastCol);
+ if (nLastCol > nEndCol)
+ nLastCol = nEndCol;
+
+ SetColFiltered(nCol, nLastCol, bFiltered);
+ nCol = nLastCol + 1;
+ }
+}
+
+void ScTable::CopyRowFiltered(const ScTable& rTable, SCROW nStartRow, SCROW nEndRow)
+{
+ SCROW nRow = nStartRow;
+ while (nRow <= nEndRow)
+ {
+ SCROW nLastRow = -1;
+ bool bFiltered = rTable.RowFiltered(nRow, nullptr, &nLastRow);
+ if (nLastRow > nEndRow)
+ nLastRow = nEndRow;
+ SetRowFiltered(nRow, nLastRow, bFiltered);
+ nRow = nLastRow + 1;
+ }
+}
+
+void ScTable::SetRowFiltered(SCROW nStartRow, SCROW nEndRow, bool bFiltered)
+{
+ if (bFiltered)
+ mpFilteredRows->setTrue(nStartRow, nEndRow);
+ else
+ mpFilteredRows->setFalse(nStartRow, nEndRow);
+}
+
+void ScTable::SetColFiltered(SCCOL nStartCol, SCCOL nEndCol, bool bFiltered)
+{
+ if (bFiltered)
+ mpFilteredCols->setTrue(nStartCol, nEndCol);
+ else
+ mpFilteredCols->setFalse(nStartCol, nEndCol);
+}
+
+SCROW ScTable::FirstNonFilteredRow(SCROW nStartRow, SCROW nEndRow) const
+{
+ SCROW nRow = nStartRow;
+ ScFlatBoolRowSegments::RangeData aData;
+ while (nRow <= nEndRow)
+ {
+ if (!ValidRow(nRow))
+ break;
+
+ if (!mpFilteredRows->getRangeData(nRow, aData))
+ // failed to get range data.
+ break;
+
+ if (!aData.mbValue)
+ // non-filtered row found
+ return nRow;
+
+ nRow = aData.mnRow2 + 1;
+ }
+
+ return ::std::numeric_limits<SCROW>::max();
+}
+
+SCROW ScTable::LastNonFilteredRow(SCROW nStartRow, SCROW nEndRow) const
+{
+ SCROW nRow = nEndRow;
+ ScFlatBoolRowSegments::RangeData aData;
+ while (nRow >= nStartRow)
+ {
+ if (!ValidRow(nRow))
+ break;
+
+ if (!mpFilteredRows->getRangeData(nRow, aData))
+ // failed to get range data.
+ break;
+
+ if (!aData.mbValue)
+ // non-filtered row found
+ return nRow;
+
+ nRow = aData.mnRow1 - 1;
+ }
+
+ return ::std::numeric_limits<SCROW>::max();
+}
+
+SCROW ScTable::CountNonFilteredRows(SCROW nStartRow, SCROW nEndRow) const
+{
+ SCROW nCount = 0;
+ SCROW nRow = nStartRow;
+ ScFlatBoolRowSegments::RangeData aData;
+ while (nRow <= nEndRow)
+ {
+ if (!mpFilteredRows->getRangeData(nRow, aData))
+ break;
+
+ if (aData.mnRow2 > nEndRow)
+ aData.mnRow2 = nEndRow;
+
+ if (!aData.mbValue)
+ nCount += aData.mnRow2 - nRow + 1;
+
+ nRow = aData.mnRow2 + 1;
+ }
+ return nCount;
+}
+
+bool ScTable::IsManualRowHeight(SCROW nRow) const
+{
+ return bool(pRowFlags->GetValue(nRow) & CRFlags::ManualSize);
+}
+
+namespace
+{
+void lcl_syncFlags(const ScDocument* pDocument, ScFlatBoolColSegments& rColSegments,
+ const ScFlatBoolRowSegments& rRowSegments,
+ ScBitMaskCompressedArray<SCCOL, CRFlags>* pColFlags,
+ ScBitMaskCompressedArray<SCROW, CRFlags>* pRowFlags, const CRFlags nFlagMask)
+{
+ using ::sal::static_int_cast;
+
+ CRFlags nFlagMaskComplement = ~nFlagMask;
+
+ pRowFlags->AndValue(0, pDocument->MaxRow(), nFlagMaskComplement);
+ pColFlags->AndValue(0, pDocument->MaxCol() + 1, nFlagMaskComplement);
+
+ {
+ // row hidden flags.
+
+ SCROW nRow = 0;
+ ScFlatBoolRowSegments::RangeData aData;
+ while (nRow <= pDocument->MaxRow())
+ {
+ if (!rRowSegments.getRangeData(nRow, aData))
+ break;
+
+ if (aData.mbValue)
+ pRowFlags->OrValue(nRow, aData.mnRow2, nFlagMask);
+
+ nRow = aData.mnRow2 + 1;
+ }
+ }
+
+ {
+ // column hidden flags.
+
+ SCCOL nCol = 0;
+ ScFlatBoolColSegments::RangeData aData;
+ while (nCol <= pDocument->MaxCol())
+ {
+ if (!rColSegments.getRangeData(nCol, aData))
+ break;
+
+ if (aData.mbValue)
+ pColFlags->OrValue(nCol, aData.mnCol2, nFlagMask);
+
+ nCol = aData.mnCol2 + 1;
+ }
+ }
+}
+}
+
+void ScTable::SyncColRowFlags()
+{
+ CRFlags nManualBreakComplement = ~CRFlags::ManualBreak;
+
+ // Manual breaks.
+ pRowFlags->AndValue(0, rDocument.MaxRow(), nManualBreakComplement);
+ mpColFlags->AndValue(0, rDocument.MaxCol() + 1, nManualBreakComplement);
+
+ for (const auto& rBreakPos : maRowManualBreaks)
+ pRowFlags->OrValue(rBreakPos, CRFlags::ManualBreak);
+
+ for (const auto& rBreakPos : maColManualBreaks)
+ mpColFlags->OrValue(rBreakPos, CRFlags::ManualBreak);
+
+ // Hidden flags.
+ lcl_syncFlags(&rDocument, *mpHiddenCols, *mpHiddenRows, mpColFlags.get(), pRowFlags.get(),
+ CRFlags::Hidden);
+ lcl_syncFlags(&rDocument, *mpFilteredCols, *mpFilteredRows, mpColFlags.get(), pRowFlags.get(),
+ CRFlags::Filtered);
+}
+
+void ScTable::SetPageSize(const Size& rSize)
+{
+ if (!rSize.IsEmpty())
+ {
+ if (aPageSizeTwips != rSize)
+ InvalidatePageBreaks();
+
+ bPageSizeValid = true;
+ aPageSizeTwips = rSize;
+ }
+ else
+ bPageSizeValid = false;
+}
+
+bool ScTable::IsProtected() const { return pTabProtection && pTabProtection->isProtected(); }
+
+void ScTable::SetProtection(const ScTableProtection* pProtect)
+{
+ if (pProtect)
+ pTabProtection.reset(new ScTableProtection(*pProtect));
+ else
+ pTabProtection.reset();
+
+ SetStreamValid(false);
+}
+
+const ScTableProtection* ScTable::GetProtection() const { return pTabProtection.get(); }
+
+Size ScTable::GetPageSize() const
+{
+ if (bPageSizeValid)
+ return aPageSizeTwips;
+ else
+ return Size(); // blank
+}
+
+void ScTable::SetRepeatArea(SCCOL nStartCol, SCCOL nEndCol, SCROW nStartRow, SCROW nEndRow)
+{
+ // #i117952# page break calculation uses these values (set from ScPrintFunc), not pRepeatColRange/pRepeatRowRange
+ if (nStartCol != nRepeatStartX || nEndCol != nRepeatEndX || nStartRow != nRepeatStartY
+ || nEndRow != nRepeatEndY)
+ InvalidatePageBreaks();
+
+ nRepeatStartX = nStartCol;
+ nRepeatEndX = nEndCol;
+ nRepeatStartY = nStartRow;
+ nRepeatEndY = nEndRow;
+}
+
+void ScTable::StartListening(const ScAddress& rAddress, SvtListener* pListener)
+{
+ if (!ValidCol(rAddress.Col()))
+ return;
+
+ CreateColumnIfNotExists(rAddress.Col()).StartListening(*pListener, rAddress.Row());
+}
+
+void ScTable::EndListening(const ScAddress& rAddress, SvtListener* pListener)
+{
+ if (!ValidCol(rAddress.Col()))
+ return;
+
+ if (rAddress.Col() < aCol.size())
+ aCol[rAddress.Col()].EndListening(*pListener, rAddress.Row());
+}
+
+void ScTable::StartListening(sc::StartListeningContext& rCxt, const ScAddress& rAddress,
+ SvtListener& rListener)
+{
+ if (!ValidCol(rAddress.Col()))
+ return;
+
+ CreateColumnIfNotExists(rAddress.Col()).StartListening(rCxt, rAddress, rListener);
+}
+
+void ScTable::EndListening(sc::EndListeningContext& rCxt, const ScAddress& rAddress,
+ SvtListener& rListener)
+{
+ if (!ValidCol(rAddress.Col()))
+ return;
+
+ if (rAddress.Col() < aCol.size())
+ aCol[rAddress.Col()].EndListening(rCxt, rAddress, rListener);
+}
+
+void ScTable::SetPageStyle(const OUString& rName)
+{
+ if (aPageStyle == rName)
+ return;
+
+ OUString aStrNew = rName;
+ SfxStyleSheetBasePool* pStylePool = rDocument.GetStyleSheetPool();
+ SfxStyleSheetBase* pNewStyle = pStylePool->Find(aStrNew, SfxStyleFamily::Page);
+
+ if (!pNewStyle)
+ {
+ aStrNew = ScResId(STR_STYLENAME_STANDARD);
+ pNewStyle = pStylePool->Find(aStrNew, SfxStyleFamily::Page);
+ }
+
+ if (aPageStyle == aStrNew)
+ return;
+
+ SfxStyleSheetBase* pOldStyle = pStylePool->Find(aPageStyle, SfxStyleFamily::Page);
+ if (pOldStyle && pNewStyle)
+ {
+ SfxItemSet& rOldSet = pOldStyle->GetItemSet();
+ SfxItemSet& rNewSet = pNewStyle->GetItemSet();
+ auto getScaleValue = [](const SfxItemSet& rSet, sal_uInt16 nId) {
+ return static_cast<const SfxUInt16Item&>(rSet.Get(nId)).GetValue();
+ };
+
+ const sal_uInt16 nOldScale = getScaleValue(rOldSet, ATTR_PAGE_SCALE);
+ const sal_uInt16 nOldScaleToPages = getScaleValue(rOldSet, ATTR_PAGE_SCALETOPAGES);
+ const sal_uInt16 nNewScale = getScaleValue(rNewSet, ATTR_PAGE_SCALE);
+ const sal_uInt16 nNewScaleToPages = getScaleValue(rNewSet, ATTR_PAGE_SCALETOPAGES);
+
+ if ((nOldScale != nNewScale) || (nOldScaleToPages != nNewScaleToPages))
+ InvalidateTextWidth(nullptr, nullptr, false, false);
+ }
+
+ if (pNewStyle) // also without the old one (for UpdateStdNames)
+ aPageStyle = aStrNew;
+
+ SetStreamValid(false);
+}
+
+void ScTable::PageStyleModified(const OUString& rNewName)
+{
+ aPageStyle = rNewName;
+ InvalidateTextWidth(nullptr, nullptr, false, false); // don't know what was in the style before
+}
+
+void ScTable::InvalidateTextWidth(const ScAddress* pAdrFrom, const ScAddress* pAdrTo,
+ bool bNumFormatChanged, bool bBroadcast)
+{
+ if (pAdrFrom && !pAdrTo)
+ {
+ // Special case: only process the "from" cell.
+ SCCOL nCol = pAdrFrom->Col();
+ SCROW nRow = pAdrFrom->Row();
+ if (nCol >= aCol.size())
+ return;
+ ScColumn& rCol = aCol[nCol];
+ ScRefCellValue aCell = rCol.GetCellValue(nRow);
+ if (aCell.isEmpty())
+ return;
+
+ rCol.SetTextWidth(nRow, TEXTWIDTH_DIRTY);
+
+ if (bNumFormatChanged)
+ rCol.SetScriptType(nRow, SvtScriptType::UNKNOWN);
+
+ if (bBroadcast)
+ { // Only with CalcAsShown
+ switch (aCell.meType)
+ {
+ case CELLTYPE_VALUE:
+ rCol.Broadcast(nRow);
+ break;
+ case CELLTYPE_FORMULA:
+ aCell.mpFormula->SetDirty();
+ break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+ }
+
+ return;
+ }
+
+ const SCCOL nCol1 = pAdrFrom ? pAdrFrom->Col() : 0;
+ const SCROW nRow1 = pAdrFrom ? pAdrFrom->Row() : 0;
+ const SCCOL nCol2 = pAdrTo ? pAdrTo->Col() : aCol.size() - 1;
+ const SCROW nRow2 = pAdrTo ? pAdrTo->Row() : rDocument.MaxRow();
+
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ {
+ ScColumnTextWidthIterator aIter(GetDoc(), aCol[nCol], nRow1, nRow2);
+ sc::ColumnBlockPosition blockPos; // cache mdds position
+ InitColumnBlockPosition(blockPos, nCol);
+
+ for (; aIter.hasCell(); aIter.next())
+ {
+ SCROW nRow = aIter.getPos();
+ aIter.setValue(TEXTWIDTH_DIRTY);
+ ScRefCellValue aCell = aCol[nCol].GetCellValue(blockPos, nRow);
+ if (aCell.isEmpty())
+ continue;
+
+ if (bNumFormatChanged)
+ aCol[nCol].SetScriptType(nRow, SvtScriptType::UNKNOWN);
+
+ if (bBroadcast)
+ { // Only with CalcAsShown
+ switch (aCell.meType)
+ {
+ case CELLTYPE_VALUE:
+ aCol[nCol].Broadcast(nRow);
+ break;
+ case CELLTYPE_FORMULA:
+ aCell.mpFormula->SetDirty();
+ break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+ }
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/table6.cxx b/sc/source/core/data/table6.cxx
new file mode 100644
index 000000000..966b4491c
--- /dev/null
+++ b/sc/source/core/data/table6.cxx
@@ -0,0 +1,1155 @@
+/* -*- 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 <com/sun/star/util/SearchResult.hpp>
+#include <svl/srchitem.hxx>
+#include <editeng/editobj.hxx>
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+
+#include <table.hxx>
+#include <formulacell.hxx>
+#include <document.hxx>
+#include <stlpool.hxx>
+#include <stlsheet.hxx>
+#include <markdata.hxx>
+#include <editutil.hxx>
+#include <postit.hxx>
+
+namespace {
+
+bool lcl_GetTextWithBreaks( const EditTextObject& rData, ScDocument* pDoc, OUString& rVal )
+{
+ // true = more than 1 paragraph
+
+ EditEngine& rEngine = pDoc->GetEditEngine();
+ rEngine.SetText(rData);
+ rVal = rEngine.GetText();
+ return ( rEngine.GetParagraphCount() > 1 );
+}
+
+}
+
+bool ScTable::SearchCell(const SvxSearchItem& rSearchItem, SCCOL nCol, sc::ColumnBlockConstPosition& rBlockPos, SCROW nRow,
+ const ScMarkData& rMark, OUString& rUndoStr, ScDocument* pUndoDoc)
+{
+ if ( !IsColRowValid( nCol, nRow ) )
+ return false;
+
+ bool bFound = false;
+ bool bDoSearch = true;
+ bool bDoBack = rSearchItem.GetBackward();
+ bool bSearchFormatted = rSearchItem.IsSearchFormatted();
+
+ OUString aString;
+ ScRefCellValue aCell;
+ if (rSearchItem.GetSelection())
+ bDoSearch = rMark.IsCellMarked(nCol, nRow);
+
+ if (!bDoSearch)
+ return false;
+
+ ScPostIt* pNote;
+ if (rSearchItem.GetCellType() == SvxSearchCellType::NOTE)
+ {
+ pNote = aCol[nCol].GetCellNote(rBlockPos, nRow);
+ if (!pNote)
+ return false;
+ }
+ else
+ {
+ aCell = aCol[nCol].GetCellValue(rBlockPos, nRow);
+ if (aCell.isEmpty())
+ return false;
+ pNote = nullptr;
+ }
+
+ bool bMultiLine = false;
+ CellType eCellType = aCell.meType;
+ switch (rSearchItem.GetCellType())
+ {
+ case SvxSearchCellType::FORMULA:
+ {
+ if ( eCellType == CELLTYPE_FORMULA )
+ aString = aCell.mpFormula->GetFormula(rDocument.GetGrammar());
+ else if ( eCellType == CELLTYPE_EDIT )
+ bMultiLine = lcl_GetTextWithBreaks(*aCell.mpEditText, &rDocument, aString);
+ else
+ {
+ if( !bSearchFormatted )
+ aString = aCol[nCol].GetInputString( rBlockPos, nRow );
+ else
+ aString = aCol[nCol].GetString( rBlockPos, nRow );
+ }
+ break;
+ }
+ case SvxSearchCellType::VALUE:
+ if ( eCellType == CELLTYPE_EDIT )
+ bMultiLine = lcl_GetTextWithBreaks(*aCell.mpEditText, &rDocument, aString);
+ else
+ {
+ if( !bSearchFormatted )
+ aString = aCol[nCol].GetInputString( rBlockPos, nRow );
+ else
+ aString = aCol[nCol].GetString( rBlockPos, nRow );
+ }
+ break;
+ case SvxSearchCellType::NOTE:
+ {
+ if (pNote)
+ {
+ aString = pNote->GetText();
+ bMultiLine = pNote->HasMultiLineText();
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ sal_Int32 nStart = 0;
+ sal_Int32 nEnd = aString.getLength();
+ css::util::SearchResult aSearchResult;
+ if (pSearchText)
+ {
+ if ( bDoBack )
+ {
+ sal_Int32 nTemp=nStart; nStart=nEnd; nEnd=nTemp;
+ bFound = pSearchText->SearchBackward(aString, &nStart, &nEnd, &aSearchResult);
+ // change results to definition before 614:
+ --nEnd;
+ }
+ else
+ {
+ bFound = pSearchText->SearchForward(aString, &nStart, &nEnd, &aSearchResult);
+ // change results to definition before 614:
+ --nEnd;
+ }
+
+ if (bFound && rSearchItem.GetWordOnly())
+ bFound = (nStart == 0 && nEnd == aString.getLength() - 1);
+ }
+ else
+ {
+ OSL_FAIL("pSearchText == NULL");
+ return bFound;
+ }
+
+ if (!bFound)
+ return false;
+ if ( rSearchItem.GetCommand() != SvxSearchCmd::REPLACE
+ && rSearchItem.GetCommand() != SvxSearchCmd::REPLACE_ALL )
+ return bFound;
+
+ if (!IsBlockEditable(nCol, nRow, nCol, nRow))
+ return bFound;
+
+ ScMatrixMode cMatrixFlag = ScMatrixMode::NONE;
+
+ // Don't split the matrix, only replace Matrix formulas
+ if (eCellType == CELLTYPE_FORMULA)
+ {
+ cMatrixFlag = aCell.mpFormula->GetMatrixFlag();
+ if(cMatrixFlag == ScMatrixMode::Reference)
+ return bFound;
+ }
+ // No UndoDoc => Matrix not restorable => don't replace
+ if (cMatrixFlag != ScMatrixMode::NONE && !pUndoDoc)
+ return bFound;
+
+ if ( cMatrixFlag == ScMatrixMode::NONE && rSearchItem.GetCommand() == SvxSearchCmd::REPLACE )
+ rUndoStr = aString;
+ else if (pUndoDoc)
+ {
+ ScAddress aAdr( nCol, nRow, nTab );
+ aCell.commit(*pUndoDoc, aAdr);
+ }
+
+ bool bRepeat = !rSearchItem.GetWordOnly();
+ do
+ {
+ // don't continue search if the found text is empty,
+ // otherwise it would never stop (#35410#)
+ if ( nEnd < nStart )
+ bRepeat = false;
+
+ OUString sReplStr = rSearchItem.GetReplaceString();
+ if (rSearchItem.GetRegExp())
+ {
+ pSearchText->ReplaceBackReferences( sReplStr, aString, aSearchResult );
+ OUStringBuffer aStrBuffer(aString);
+ aStrBuffer.remove(nStart, nEnd-nStart+1);
+ aStrBuffer.insert(nStart, sReplStr);
+ aString = aStrBuffer.makeStringAndClear();
+ }
+ else
+ {
+ OUStringBuffer aStrBuffer(aString);
+ aStrBuffer.remove(nStart, nEnd-nStart+1);
+ aStrBuffer.insert(nStart, rSearchItem.GetReplaceString());
+ aString = aStrBuffer.makeStringAndClear();
+ }
+
+ // Adjust index
+ if (bDoBack)
+ {
+ nEnd = nStart;
+ nStart = 0;
+ }
+ else
+ {
+ nStart = nStart + sReplStr.getLength();
+ nEnd = aString.getLength();
+ }
+
+ // continue search ?
+ if (bRepeat)
+ {
+ if ( rSearchItem.GetCommand() != SvxSearchCmd::REPLACE_ALL || nStart >= nEnd )
+ bRepeat = false;
+ else if (bDoBack)
+ {
+ sal_Int32 nTemp=nStart; nStart=nEnd; nEnd=nTemp;
+ bRepeat = pSearchText->SearchBackward(aString, &nStart, &nEnd, &aSearchResult);
+ // change results to definition before 614:
+ --nEnd;
+ }
+ else
+ {
+ bRepeat = pSearchText->SearchForward(aString, &nStart, &nEnd, &aSearchResult);
+ // change results to definition before 614:
+ --nEnd;
+ }
+ }
+ }
+ while (bRepeat);
+ if (rSearchItem.GetCellType() == SvxSearchCellType::NOTE)
+ {
+ // NB: rich text format is lost.
+ // This is also true of Cells.
+ if (pNote)
+ pNote->SetText( ScAddress( nCol, nRow, nTab ), aString );
+ }
+ else if ( cMatrixFlag != ScMatrixMode::NONE )
+ { // don't split Matrix
+ if ( aString.getLength() > 2 )
+ { // remove {} here so that "{=" can be replaced by "{=..."
+ if ( aString[ aString.getLength()-1 ] == '}' )
+ aString = aString.copy( 0, aString.getLength()-1 );
+ if ( aString[0] == '{' )
+ aString = aString.copy( 1 );
+ }
+ ScAddress aAdr( nCol, nRow, nTab );
+ ScFormulaCell* pFCell = new ScFormulaCell( rDocument, aAdr,
+ aString, rDocument.GetGrammar(), cMatrixFlag );
+ SCCOL nMatCols;
+ SCROW nMatRows;
+ aCell.mpFormula->GetMatColsRows(nMatCols, nMatRows);
+ pFCell->SetMatColsRows( nMatCols, nMatRows );
+ aCol[nCol].SetFormulaCell(nRow, pFCell);
+ }
+ else if ( bMultiLine && aString.indexOf('\n') != -1 )
+ {
+ ScFieldEditEngine& rEngine = rDocument.GetEditEngine();
+ rEngine.SetTextCurrentDefaults(aString);
+ SetEditText(nCol, nRow, rEngine.CreateTextObject());
+ }
+ else
+ aCol[nCol].SetString(nRow, nTab, aString, rDocument.GetAddressConvention());
+ // pCell is invalid now (deleted)
+ aCol[nCol].InitBlockPosition( rBlockPos ); // invalidate also the cached position
+
+ return bFound;
+}
+
+void ScTable::SkipFilteredRows(SCROW& rRow, SCROW& rLastNonFilteredRow, bool bForward)
+{
+ if (bForward)
+ {
+ // forward search
+
+ if (rRow <= rLastNonFilteredRow)
+ return;
+
+ SCROW nLastRow = rRow;
+ if (RowFiltered(rRow, nullptr, &nLastRow))
+ // move to the first non-filtered row.
+ rRow = nLastRow + 1;
+ else
+ // record the last non-filtered row to avoid checking
+ // the filtered state for each and every row.
+ rLastNonFilteredRow = nLastRow;
+ }
+ else
+ {
+ // backward search
+
+ if (rRow >= rLastNonFilteredRow)
+ return;
+
+ SCROW nFirstRow = rRow;
+ if (RowFiltered(rRow, &nFirstRow))
+ // move to the first non-filtered row.
+ rRow = nFirstRow - 1;
+ else
+ // record the last non-filtered row to avoid checking
+ // the filtered state for each and every row.
+ rLastNonFilteredRow = nFirstRow;
+ }
+}
+
+bool ScTable::Search(const SvxSearchItem& rSearchItem, SCCOL& rCol, SCROW& rRow,
+ const ScMarkData& rMark, OUString& rUndoStr, ScDocument* pUndoDoc)
+{
+ SCCOL nLastCol;
+ SCROW nLastRow;
+ if (rSearchItem.GetCellType() == SvxSearchCellType::NOTE)
+ GetCellArea( nLastCol, nLastRow);
+ else
+ GetLastDataPos(nLastCol, nLastRow);
+ std::vector< sc::ColumnBlockConstPosition > blockPos;
+ return Search(rSearchItem, rCol, rRow, nLastCol, nLastRow, rMark, rUndoStr, pUndoDoc, blockPos);
+}
+
+bool ScTable::Search(const SvxSearchItem& rSearchItem, SCCOL& rCol, SCROW& rRow,
+ SCCOL nLastCol, SCROW nLastRow,
+ const ScMarkData& rMark, OUString& rUndoStr, ScDocument* pUndoDoc,
+ std::vector< sc::ColumnBlockConstPosition >& blockPos)
+{
+ bool bFound = false;
+ bool bAll = (rSearchItem.GetCommand() == SvxSearchCmd::FIND_ALL)
+ ||(rSearchItem.GetCommand() == SvxSearchCmd::REPLACE_ALL);
+ SCCOL nCol = rCol;
+ SCROW nRow = rRow;
+
+ bool bSkipFiltered = !rSearchItem.IsSearchFiltered();
+ bool bSearchNotes = (rSearchItem.GetCellType() == SvxSearchCellType::NOTE);
+ // We need to cache sc::ColumnBlockConstPosition per each column.
+ if (static_cast<SCCOL>(blockPos.size()) != nLastCol + 1)
+ {
+ blockPos.resize( nLastCol + 1 );
+ for( SCCOL i = 0; i <= nLastCol; ++i )
+ aCol[ i ].InitBlockPosition( blockPos[ i ] );
+ }
+ if (!bAll && rSearchItem.GetBackward())
+ {
+ SCROW nLastNonFilteredRow = rDocument.MaxRow() + 1;
+ if (rSearchItem.GetRowDirection())
+ {
+ nCol--;
+ nCol = std::min(nCol, nLastCol);
+ nRow = std::min(nRow, nLastRow);
+ while (!bFound && (nRow >= 0))
+ {
+ if (bSkipFiltered)
+ SkipFilteredRows(nRow, nLastNonFilteredRow, false);
+
+ while (!bFound && (nCol >= 0))
+ {
+ bFound = SearchCell(rSearchItem, nCol, blockPos[ nCol ], nRow,
+ rMark, rUndoStr, pUndoDoc);
+ if (!bFound)
+ {
+ bool bIsEmpty;
+ do
+ {
+ nCol--;
+ if (nCol >= 0)
+ {
+ if (bSearchNotes)
+ bIsEmpty = !aCol[nCol].HasCellNotes();
+ else
+ bIsEmpty = aCol[nCol].IsEmptyData();
+ }
+ else
+ bIsEmpty = true;
+ }
+ while ((nCol >= 0) && bIsEmpty);
+ }
+ }
+ if (!bFound)
+ {
+ nCol = nLastCol;
+ nRow--;
+ }
+ }
+ }
+ else
+ {
+ nRow--;
+ nCol = std::min(nCol, nLastCol);
+ nRow = std::min(nRow, nLastRow);
+ while (!bFound && (nCol >= 0))
+ {
+ while (!bFound && (nRow >= 0))
+ {
+ if (bSkipFiltered)
+ SkipFilteredRows(nRow, nLastNonFilteredRow, false);
+
+ bFound = SearchCell(rSearchItem, nCol, blockPos[ nCol ],
+ nRow, rMark, rUndoStr, pUndoDoc);
+ if (!bFound)
+ {
+ if (bSearchNotes)
+ {
+ /* TODO: can we look for the previous cell note instead? */
+ --nRow;
+ }
+ else
+ {
+ if (!aCol[nCol].GetPrevDataPos(nRow))
+ nRow = -1;
+ }
+ }
+ }
+ if (!bFound)
+ {
+ // Not found in this column. Move to the next column.
+ bool bIsEmpty;
+ nRow = nLastRow;
+ nLastNonFilteredRow = rDocument.MaxRow() + 1;
+ do
+ {
+ nCol--;
+ if (nCol >= 0)
+ {
+ if (bSearchNotes)
+ bIsEmpty = !aCol[nCol].HasCellNotes();
+ else
+ bIsEmpty = aCol[nCol].IsEmptyData();
+ }
+ else
+ bIsEmpty = true;
+ }
+ while ((nCol >= 0) && bIsEmpty);
+ }
+ }
+ }
+ }
+ else
+ {
+ SCROW nLastNonFilteredRow = -1;
+ if (rSearchItem.GetRowDirection())
+ {
+ nCol++;
+ while (!bFound && (nRow <= nLastRow))
+ {
+ if (bSkipFiltered)
+ SkipFilteredRows(nRow, nLastNonFilteredRow, true);
+
+ while (!bFound && (nCol <= nLastCol))
+ {
+ bFound = SearchCell(rSearchItem, nCol, blockPos[ nCol ],
+ nRow, rMark, rUndoStr, pUndoDoc);
+ if (!bFound)
+ {
+ nCol++;
+ while ((nCol <= nLastCol) &&
+ (bSearchNotes ? !aCol[nCol].HasCellNotes() : aCol[nCol].IsEmptyData()))
+ nCol++;
+ }
+ }
+ if (!bFound)
+ {
+ nCol = 0;
+ nRow++;
+ }
+ }
+ }
+ else
+ {
+ nRow++;
+ while (!bFound && (nCol <= nLastCol))
+ {
+ while (!bFound && (nRow <= nLastRow))
+ {
+ if (bSkipFiltered)
+ SkipFilteredRows(nRow, nLastNonFilteredRow, true);
+
+ bFound = SearchCell(rSearchItem, nCol, blockPos[ nCol ],
+ nRow, rMark, rUndoStr, pUndoDoc);
+ if (!bFound)
+ {
+ if (bSearchNotes)
+ {
+ /* TODO: can we look for the next cell note instead? */
+ ++nRow;
+ }
+ else
+ {
+ if (!aCol[nCol].GetNextDataPos(nRow))
+ nRow = rDocument.MaxRow() + 1;
+ }
+ }
+ }
+ if (!bFound)
+ {
+ // Not found in this column. Move to the next column.
+ nRow = 0;
+ nLastNonFilteredRow = -1;
+ nCol++;
+ while ((nCol <= nLastCol) &&
+ (bSearchNotes ? !aCol[nCol].HasCellNotes() : aCol[nCol].IsEmptyData()))
+ nCol++;
+ }
+ }
+ }
+ }
+ if (bFound)
+ {
+ rCol = nCol;
+ rRow = nRow;
+ }
+ return bFound;
+}
+
+bool ScTable::SearchAll(const SvxSearchItem& rSearchItem, const ScMarkData& rMark,
+ ScRangeList& rMatchedRanges, OUString& rUndoStr, ScDocument* pUndoDoc)
+{
+ bool bFound = true;
+ SCCOL nCol = 0;
+ SCROW nRow = -1;
+ bool bEverFound = false;
+
+ SCCOL nLastCol;
+ SCROW nLastRow;
+ if (rSearchItem.GetCellType() == SvxSearchCellType::NOTE)
+ GetCellArea( nLastCol, nLastRow);
+ else
+ GetLastDataPos(nLastCol, nLastRow);
+
+ std::vector< sc::ColumnBlockConstPosition > blockPos;
+ do
+ {
+ bFound = Search(rSearchItem, nCol, nRow, nLastCol, nLastRow, rMark, rUndoStr, pUndoDoc, blockPos);
+ if (bFound)
+ {
+ bEverFound = true;
+ rMatchedRanges.Join(ScRange(nCol, nRow, nTab));
+ }
+ }
+ while (bFound);
+
+ return bEverFound;
+}
+
+void ScTable::UpdateSearchItemAddressForReplace( const SvxSearchItem& rSearchItem, SCCOL& rCol, SCROW& rRow )
+{
+ if (rSearchItem.GetBackward())
+ {
+ if (rSearchItem.GetRowDirection())
+ rCol += 1;
+ else
+ rRow += 1;
+ }
+ else
+ {
+ if (rSearchItem.GetRowDirection())
+ rCol -= 1;
+ else
+ rRow -= 1;
+ }
+}
+
+bool ScTable::Replace(const SvxSearchItem& rSearchItem, SCCOL& rCol, SCROW& rRow,
+ const ScMarkData& rMark, OUString& rUndoStr, ScDocument* pUndoDoc)
+{
+ SCCOL nCol = rCol;
+ SCROW nRow = rRow;
+
+ UpdateSearchItemAddressForReplace( rSearchItem, nCol, nRow );
+ bool bFound = Search(rSearchItem, nCol, nRow, rMark, rUndoStr, pUndoDoc);
+ if (bFound)
+ {
+ rCol = nCol;
+ rRow = nRow;
+ }
+ return bFound;
+}
+
+bool ScTable::ReplaceAll(
+ const SvxSearchItem& rSearchItem, const ScMarkData& rMark, ScRangeList& rMatchedRanges,
+ OUString& rUndoStr, ScDocument* pUndoDoc)
+{
+ SCCOL nCol = 0;
+ SCROW nRow = -1;
+
+ SCCOL nLastCol;
+ SCROW nLastRow;
+ if (rSearchItem.GetCellType() == SvxSearchCellType::NOTE)
+ GetCellArea( nLastCol, nLastRow);
+ else
+ GetLastDataPos(nLastCol, nLastRow);
+
+ // tdf#92160 - columnar replace is faster, and more memory efficient.
+ SvxSearchItem aCopyItem(rSearchItem);
+ aCopyItem.SetRowDirection(false);
+
+ std::vector< sc::ColumnBlockConstPosition > blockPos;
+ bool bEverFound = false;
+ while (true)
+ {
+ bool bFound = Search(aCopyItem, nCol, nRow, nLastCol, nLastRow, rMark, rUndoStr, pUndoDoc, blockPos);
+
+ if (bFound)
+ {
+ bEverFound = true;
+ rMatchedRanges.Join(ScRange(nCol, nRow, nTab));
+ }
+ else
+ break;
+ }
+ return bEverFound;
+}
+
+bool ScTable::SearchStyle(const SvxSearchItem& rSearchItem, SCCOL& rCol, SCROW& rRow,
+ const ScMarkData& rMark)
+{
+ const ScStyleSheet* pSearchStyle = static_cast<const ScStyleSheet*>(
+ rDocument.GetStyleSheetPool()->Find(
+ rSearchItem.GetSearchString(), SfxStyleFamily::Para ));
+
+ SCCOL nCol = rCol;
+ SCROW nRow = rRow;
+ bool bFound = false;
+
+ bool bSelect = rSearchItem.GetSelection();
+ bool bRows = rSearchItem.GetRowDirection();
+ bool bBack = rSearchItem.GetBackward();
+ short nAdd = bBack ? -1 : 1;
+
+ if (bRows) // by row
+ {
+ if ( !IsColValid( nCol ) )
+ {
+ SAL_WARN( "sc.core", "SearchStyle: bad column " << nCol);
+ return false;
+ }
+ nRow += nAdd;
+ do
+ {
+ SCROW nNextRow = aCol[nCol].SearchStyle( nRow, pSearchStyle, bBack, bSelect, rMark );
+ if (!ValidRow(nNextRow))
+ {
+ nRow = bBack ? rDocument.MaxRow() : 0;
+ nCol = sal::static_int_cast<SCCOL>( nCol + nAdd );
+ }
+ else
+ {
+ nRow = nNextRow;
+ bFound = true;
+ }
+ }
+ while ( !bFound && IsColValid( nCol ) );
+ }
+ else // by column
+ {
+ SCCOL aColSize = aCol.size();
+ std::vector< SCROW > nNextRows ( aColSize );
+ SCCOL i;
+ for (i=0; i < aColSize; ++i)
+ {
+ SCROW nSRow = nRow;
+ if (bBack)
+ {
+ if (i>=nCol) --nSRow;
+ }
+ else
+ {
+ if (i<=nCol) ++nSRow;
+ }
+ nNextRows[i] = aCol[i].SearchStyle( nSRow, pSearchStyle, bBack, bSelect, rMark );
+ }
+ if (bBack) // backwards
+ {
+ nRow = -1;
+ for (i = aColSize - 1; i>=0; --i)
+ if (nNextRows[i]>nRow)
+ {
+ nCol = i;
+ nRow = nNextRows[i];
+ bFound = true;
+ }
+ }
+ else // forwards
+ {
+ nRow = rDocument.MaxRow()+1;
+ for (i=0; i < aColSize; ++i)
+ if (nNextRows[i]<nRow)
+ {
+ nCol = i;
+ nRow = nNextRows[i];
+ bFound = true;
+ }
+ }
+ }
+
+ if (bFound)
+ {
+ rCol = nCol;
+ rRow = nRow;
+ }
+ return bFound;
+}
+
+//TODO: return single Pattern for Undo
+
+bool ScTable::ReplaceStyle(const SvxSearchItem& rSearchItem, SCCOL& rCol, SCROW& rRow,
+ const ScMarkData& rMark, bool bIsUndo)
+{
+ bool bRet;
+ if (bIsUndo)
+ bRet = true;
+ else
+ bRet = SearchStyle(rSearchItem, rCol, rRow, rMark);
+ if (bRet)
+ {
+ const ScStyleSheet* pReplaceStyle = static_cast<const ScStyleSheet*>(
+ rDocument.GetStyleSheetPool()->Find(
+ rSearchItem.GetReplaceString(), SfxStyleFamily::Para ));
+
+ if (pReplaceStyle)
+ ApplyStyle( rCol, rRow, pReplaceStyle );
+ else
+ {
+ OSL_FAIL("pReplaceStyle==0");
+ }
+ }
+
+ return bRet;
+}
+
+bool ScTable::SearchAllStyle(
+ const SvxSearchItem& rSearchItem, const ScMarkData& rMark, ScRangeList& rMatchedRanges)
+{
+ const ScStyleSheet* pSearchStyle = static_cast<const ScStyleSheet*>(
+ rDocument.GetStyleSheetPool()->Find(
+ rSearchItem.GetSearchString(), SfxStyleFamily::Para ));
+ bool bSelect = rSearchItem.GetSelection();
+ bool bBack = rSearchItem.GetBackward();
+ bool bEverFound = false;
+
+ for (SCCOL i=0; i < aCol.size(); ++i)
+ {
+ bool bFound = true;
+ SCROW nRow = 0;
+ SCROW nEndRow;
+ while (bFound && nRow <= rDocument.MaxRow())
+ {
+ bFound = aCol[i].SearchStyleRange( nRow, nEndRow, pSearchStyle, bBack, bSelect, rMark );
+ if (bFound)
+ {
+ if (nEndRow<nRow)
+ {
+ SCROW nTemp = nRow;
+ nRow = nEndRow;
+ nEndRow = nTemp;
+ }
+ rMatchedRanges.Join(ScRange(i, nRow, nTab, i, nEndRow, nTab));
+ nRow = nEndRow + 1;
+ bEverFound = true;
+ }
+ }
+ }
+
+ return bEverFound;
+}
+
+bool ScTable::ReplaceAllStyle(
+ const SvxSearchItem& rSearchItem, const ScMarkData& rMark, ScRangeList& rMatchedRanges,
+ ScDocument* pUndoDoc)
+{
+ bool bRet = SearchAllStyle(rSearchItem, rMark, rMatchedRanges);
+ if (bRet)
+ {
+ const ScStyleSheet* pReplaceStyle = static_cast<const ScStyleSheet*>(
+ rDocument.GetStyleSheetPool()->Find(
+ rSearchItem.GetReplaceString(), SfxStyleFamily::Para ));
+
+ if (pReplaceStyle)
+ {
+ if (pUndoDoc)
+ rDocument.CopyToDocument(0, 0 ,nTab, rDocument.MaxCol(),rDocument.MaxRow(),nTab,
+ InsertDeleteFlags::ATTRIB, true, *pUndoDoc, &rMark);
+ ApplySelectionStyle( *pReplaceStyle, rMark );
+ }
+ else
+ {
+ OSL_FAIL("pReplaceStyle==0");
+ }
+ }
+
+ return bRet;
+}
+
+bool ScTable::SearchAndReplace(
+ const SvxSearchItem& rSearchItem, SCCOL& rCol, SCROW& rRow, const ScMarkData& rMark,
+ ScRangeList& rMatchedRanges, OUString& rUndoStr, ScDocument* pUndoDoc)
+{
+ SvxSearchCmd nCommand = rSearchItem.GetCommand();
+ bool bFound = false;
+ if ( ValidColRow(rCol, rRow) ||
+ ((nCommand == SvxSearchCmd::FIND || nCommand == SvxSearchCmd::REPLACE) &&
+ (((rCol == GetDoc().GetMaxColCount() || rCol == -1) && ValidRow(rRow)) ||
+ ((rRow == GetDoc().GetMaxRowCount() || rRow == -1) && ValidCol(rCol))
+ )
+ )
+ )
+ {
+ bool bStyles = rSearchItem.GetPattern();
+ if (bStyles)
+ {
+ if (nCommand == SvxSearchCmd::FIND)
+ bFound = SearchStyle(rSearchItem, rCol, rRow, rMark);
+ else if (nCommand == SvxSearchCmd::REPLACE)
+ bFound = ReplaceStyle(rSearchItem, rCol, rRow, rMark, false);
+ else if (nCommand == SvxSearchCmd::FIND_ALL)
+ bFound = SearchAllStyle(rSearchItem, rMark, rMatchedRanges);
+ else if (nCommand == SvxSearchCmd::REPLACE_ALL)
+ bFound = ReplaceAllStyle(rSearchItem, rMark, rMatchedRanges, pUndoDoc);
+ }
+ else if (ScDocument::IsEmptyCellSearch( rSearchItem))
+ {
+ // Search for empty cells.
+ bFound = SearchAndReplaceEmptyCells(rSearchItem, rCol, rRow, rMark, rMatchedRanges, rUndoStr, pUndoDoc);
+ }
+ else
+ {
+ // SearchParam no longer needed - SearchOptions contains all settings
+ i18nutil::SearchOptions2 aSearchOptions = rSearchItem.GetSearchOptions();
+ aSearchOptions.Locale = ScGlobal::GetLocale();
+
+ // reflect UseAsianOptions flag in SearchOptions
+ // (use only ignore case and width if asian options are disabled).
+ // This is also done in SvxSearchDialog CommandHdl, but not in API object.
+ if ( !rSearchItem.IsUseAsianOptions() )
+ aSearchOptions.transliterateFlags &=
+ TransliterationFlags::IGNORE_CASE |
+ TransliterationFlags::IGNORE_WIDTH;
+
+ pSearchText.reset( new utl::TextSearch( aSearchOptions ) );
+
+ if (nCommand == SvxSearchCmd::FIND)
+ bFound = Search(rSearchItem, rCol, rRow, rMark, rUndoStr, pUndoDoc);
+ else if (nCommand == SvxSearchCmd::FIND_ALL)
+ bFound = SearchAll(rSearchItem, rMark, rMatchedRanges, rUndoStr, pUndoDoc);
+ else if (nCommand == SvxSearchCmd::REPLACE)
+ bFound = Replace(rSearchItem, rCol, rRow, rMark, rUndoStr, pUndoDoc);
+ else if (nCommand == SvxSearchCmd::REPLACE_ALL)
+ bFound = ReplaceAll(rSearchItem, rMark, rMatchedRanges, rUndoStr, pUndoDoc);
+
+ pSearchText.reset();
+ }
+ }
+ return bFound;
+}
+
+bool ScTable::SearchAndReplaceEmptyCells(
+ const SvxSearchItem& rSearchItem, SCCOL& rCol, SCROW& rRow, const ScMarkData& rMark,
+ ScRangeList& rMatchedRanges, OUString& rUndoStr, ScDocument* pUndoDoc)
+{
+ SCCOL nColStart, nColEnd;
+ SCROW nRowStart, nRowEnd;
+ GetFirstDataPos(nColStart, nRowStart);
+ GetLastDataPos(nColEnd, nRowEnd);
+
+ ScRangeList aRanges(ScRange(nColStart, nRowStart, nTab, nColEnd, nRowEnd, nTab));
+
+ if (rSearchItem.GetSelection())
+ {
+ // current selection only.
+ if (!rMark.IsMarked() && !rMark.IsMultiMarked())
+ // There is no selection. Bail out.
+ return false;
+
+ ScRangeList aMarkedRanges, aNewRanges;
+ rMark.FillRangeListWithMarks(&aMarkedRanges, true);
+ for ( size_t i = 0, n = aMarkedRanges.size(); i < n; ++i )
+ {
+ ScRange & rRange = aMarkedRanges[ i ];
+ if (rRange.aStart.Col() > nColEnd || rRange.aStart.Row() > nRowEnd || rRange.aEnd.Col() < nColStart || rRange.aEnd.Row() < nRowStart)
+ // This range is outside the data area. Skip it.
+ continue;
+
+ // Shrink the range into data area only.
+ if (rRange.aStart.Col() < nColStart)
+ rRange.aStart.SetCol(nColStart);
+ if (rRange.aStart.Row() < nRowStart)
+ rRange.aStart.SetRow(nRowStart);
+
+ if (rRange.aEnd.Col() > nColEnd)
+ rRange.aEnd.SetCol(nColEnd);
+ if (rRange.aEnd.Row() > nRowEnd)
+ rRange.aEnd.SetRow(nRowEnd);
+
+ aNewRanges.push_back(rRange);
+ }
+ aRanges = aNewRanges;
+ }
+
+ SvxSearchCmd nCommand = rSearchItem.GetCommand();
+ if (nCommand == SvxSearchCmd::FIND || nCommand == SvxSearchCmd::REPLACE)
+ {
+ if (rSearchItem.GetBackward())
+ {
+ for ( size_t i = aRanges.size(); i > 0; --i )
+ {
+ const ScRange & rRange = aRanges[ i - 1 ];
+ if (SearchRangeForEmptyCell(rRange, rSearchItem, rCol, rRow, rUndoStr))
+ return true;
+ }
+ }
+ else
+ {
+ for ( size_t i = 0, nListSize = aRanges.size(); i < nListSize; ++i )
+ {
+ const ScRange & rRange = aRanges[ i ];
+ if (SearchRangeForEmptyCell(rRange, rSearchItem, rCol, rRow, rUndoStr))
+ return true;
+ }
+ }
+ }
+ else if (nCommand == SvxSearchCmd::FIND_ALL || nCommand == SvxSearchCmd::REPLACE_ALL)
+ {
+ bool bFound = false;
+ for ( size_t i = 0, nListSize = aRanges.size(); i < nListSize; ++i )
+ {
+ ScRange const & rRange = aRanges[ i ];
+ bFound |= SearchRangeForAllEmptyCells(rRange, rSearchItem, rMatchedRanges, rUndoStr, pUndoDoc);
+ }
+ return bFound;
+ }
+ return false;
+}
+
+namespace {
+
+bool lcl_maybeReplaceCellString(
+ ScColumn& rColObj, SCCOL& rCol, SCROW& rRow, OUString& rUndoStr, SCCOL nCol, SCROW nRow, const SvxSearchItem& rSearchItem)
+{
+ ScRefCellValue aCell = rColObj.GetCellValue(nRow);
+ if (aCell.isEmpty())
+ {
+ // empty cell found.
+ rCol = nCol;
+ rRow = nRow;
+ if (rSearchItem.GetCommand() == SvxSearchCmd::REPLACE &&
+ !rSearchItem.GetReplaceString().isEmpty())
+ {
+ rColObj.SetRawString(nRow, rSearchItem.GetReplaceString());
+ rUndoStr.clear();
+ }
+ return true;
+ }
+ return false;
+}
+
+}
+
+bool ScTable::SearchRangeForEmptyCell(
+ const ScRange& rRange, const SvxSearchItem& rSearchItem,
+ SCCOL& rCol, SCROW& rRow, OUString& rUndoStr)
+{
+ SvxSearchCmd nCmd = rSearchItem.GetCommand();
+ bool bSkipFiltered = rSearchItem.IsSearchFiltered();
+ if (rSearchItem.GetBackward())
+ {
+ // backward search
+ if (rSearchItem.GetRowDirection())
+ {
+ // row direction.
+ SCROW nLastNonFilteredRow = rDocument.MaxRow() + 1;
+ SCROW nBeginRow = std::min(rRange.aEnd.Row(), rRow);
+ for (SCROW nRow = nBeginRow; nRow >= rRange.aStart.Row(); --nRow)
+ {
+ if (bSkipFiltered)
+ SkipFilteredRows(nRow, nLastNonFilteredRow, false);
+ if (nRow < rRange.aStart.Row())
+ break;
+
+ SCCOL nBeginCol = rRange.aEnd.Col();
+ if (nRow == rRow && nBeginCol >= rCol)
+ // always start from one cell before the cursor.
+ nBeginCol = rCol - (nCmd == SvxSearchCmd::FIND ? 1 : 0);
+
+ for (SCCOL nCol = nBeginCol; nCol >= rRange.aStart.Col(); --nCol)
+ {
+ if (lcl_maybeReplaceCellString(aCol[nCol], rCol, rRow, rUndoStr, nCol, nRow, rSearchItem))
+ return true;
+ }
+ }
+ }
+ else
+ {
+ // column direction.
+ SCCOL nBeginCol = std::min(rRange.aEnd.Col(), rCol);
+ for (SCCOL nCol = nBeginCol; nCol >= rRange.aStart.Col(); --nCol)
+ {
+ SCROW nLastNonFilteredRow = rDocument.MaxRow() + 1;
+ SCROW nBeginRow = rRange.aEnd.Row();
+ if (nCol == rCol && nBeginRow >= rRow)
+ // always start from one cell before the cursor.
+ nBeginRow = rRow - (nCmd == SvxSearchCmd::FIND ? 1 : 0);
+ for (SCROW nRow = nBeginRow; nRow >= rRange.aStart.Row(); --nRow)
+ {
+ if (bSkipFiltered)
+ SkipFilteredRows(nRow, nLastNonFilteredRow, false);
+ if (nRow < rRange.aStart.Row())
+ break;
+
+ if (lcl_maybeReplaceCellString(aCol[nCol], rCol, rRow, rUndoStr, nCol, nRow, rSearchItem))
+ return true;
+ }
+ }
+ }
+ }
+ else
+ {
+ // forward search
+ if (rSearchItem.GetRowDirection())
+ {
+ // row direction.
+ SCROW nLastNonFilteredRow = -1;
+ SCROW nBeginRow = rRange.aStart.Row() < rRow ? rRow : rRange.aStart.Row();
+ for (SCROW nRow = nBeginRow; nRow <= rRange.aEnd.Row(); ++nRow)
+ {
+ if (bSkipFiltered)
+ SkipFilteredRows(nRow, nLastNonFilteredRow, true);
+ if (nRow > rRange.aEnd.Row())
+ break;
+
+ SCCOL nBeginCol = rRange.aStart.Col();
+ if (nRow == rRow && nBeginCol <= rCol)
+ // always start from one cell past the cursor.
+ nBeginCol = rCol + (nCmd == SvxSearchCmd::FIND ? 1 : 0);
+ for (SCCOL nCol = nBeginCol; nCol <= rRange.aEnd.Col(); ++nCol)
+ {
+ if (lcl_maybeReplaceCellString(aCol[nCol], rCol, rRow, rUndoStr, nCol, nRow, rSearchItem))
+ return true;
+ }
+ }
+ }
+ else
+ {
+ // column direction.
+ SCCOL nBeginCol = rRange.aStart.Col() < rCol ? rCol : rRange.aStart.Col();
+ for (SCCOL nCol = nBeginCol; nCol <= rRange.aEnd.Col(); ++nCol)
+ {
+ SCROW nLastNonFilteredRow = -1;
+ SCROW nBeginRow = rRange.aStart.Row();
+ if (nCol == rCol && nBeginRow <= rRow)
+ // always start from one cell past the cursor.
+ nBeginRow = rRow + (nCmd == SvxSearchCmd::FIND ? 1 : 0);
+ for (SCROW nRow = nBeginRow; nRow <= rRange.aEnd.Row(); ++nRow)
+ {
+ if (bSkipFiltered)
+ SkipFilteredRows(nRow, nLastNonFilteredRow, true);
+ if (nRow > rRange.aEnd.Row())
+ break;
+
+ if (lcl_maybeReplaceCellString(aCol[nCol], rCol, rRow, rUndoStr, nCol, nRow, rSearchItem))
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+bool ScTable::SearchRangeForAllEmptyCells(
+ const ScRange& rRange, const SvxSearchItem& rSearchItem,
+ ScRangeList& rMatchedRanges, OUString& rUndoStr, ScDocument* pUndoDoc)
+{
+ bool bFound = false;
+ bool bReplace = (rSearchItem.GetCommand() == SvxSearchCmd::REPLACE_ALL) &&
+ !rSearchItem.GetReplaceString().isEmpty();
+ bool bSkipFiltered = rSearchItem.IsSearchFiltered();
+
+ for (SCCOL nCol = rRange.aStart.Col(); nCol <= rRange.aEnd.Col(); ++nCol)
+ {
+ SCROW nLastNonFilteredRow = -1;
+ if (aCol[nCol].IsEmptyData())
+ {
+ // The entire column is empty.
+ const SCROW nEndRow = rRange.aEnd.Row();
+ for (SCROW nRow = rRange.aStart.Row(); nRow <= nEndRow; ++nRow)
+ {
+ SCROW nLastRow;
+ const bool bFiltered = RowFiltered(nRow, nullptr, &nLastRow);
+ if (nLastRow > nEndRow)
+ nLastRow = nEndRow;
+ if (!bFiltered)
+ {
+ rMatchedRanges.Join(ScRange(nCol, nRow, nTab, nCol, nLastRow, nTab));
+ if (bReplace)
+ {
+ const OUString& rNewStr = rSearchItem.GetReplaceString();
+ for (SCROW i = nRow; i <= nLastRow; ++i)
+ {
+ aCol[nCol].SetRawString(i, rNewStr);
+ if (pUndoDoc)
+ {
+ // TODO: I'm using a string cell with empty content to
+ // trigger deletion of cell instance on undo. Maybe I
+ // should create a new cell type for this?
+ pUndoDoc->SetString(ScAddress(nCol, i, nTab), OUString());
+ }
+ }
+ rUndoStr.clear();
+ }
+ }
+
+ nRow = nLastRow; // move to the last filtered row.
+ }
+ bFound = true;
+ continue;
+ }
+
+ for (SCROW nRow = rRange.aStart.Row(); nRow <= rRange.aEnd.Row(); ++nRow)
+ {
+ if (bSkipFiltered)
+ SkipFilteredRows(nRow, nLastNonFilteredRow, true);
+ if (nRow > rRange.aEnd.Row())
+ break;
+
+ ScRefCellValue aCell = aCol[nCol].GetCellValue(nRow);
+ if (aCell.isEmpty())
+ {
+ // empty cell found
+ rMatchedRanges.Join(ScRange(nCol, nRow, nTab));
+ bFound = true;
+
+ if (bReplace)
+ {
+ aCol[nCol].SetRawString(nRow, rSearchItem.GetReplaceString());
+ if (pUndoDoc)
+ {
+ // TODO: I'm using a string cell with empty content to
+ // trigger deletion of cell instance on undo. Maybe I
+ // should create a new cell type for this?
+ pUndoDoc->SetString(ScAddress(nCol, nRow, nTab), OUString());
+ }
+ }
+ }
+ }
+ }
+ return bFound;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/table7.cxx b/sc/source/core/data/table7.cxx
new file mode 100644
index 000000000..9af01cba7
--- /dev/null
+++ b/sc/source/core/data/table7.cxx
@@ -0,0 +1,652 @@
+/* -*- 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 <table.hxx>
+#include <clipcontext.hxx>
+#include <document.hxx>
+#include <clipparam.hxx>
+#include <segmenttree.hxx>
+#include <sharedformula.hxx>
+#include <cellvalues.hxx>
+#include <olinetab.hxx>
+#include <tabprotection.hxx>
+#include <columniterator.hxx>
+#include <drwlayer.hxx>
+#include <compressedarray.hxx>
+
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+#include <tools/stream.hxx>
+
+bool ScTable::IsMerged( SCCOL nCol, SCROW nRow ) const
+{
+ if (!ValidCol(nCol) || nCol >= GetAllocatedColumnsCount() )
+ return false;
+
+ return aCol[nCol].IsMerged(nRow);
+}
+
+sc::MultiDataCellState ScTable::HasMultipleDataCells( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) const
+{
+ if (!ValidColRow(nCol1, nRow1) || !ValidColRow(nCol2, nRow2))
+ return sc::MultiDataCellState();
+
+ if (nCol1 > nCol2 || nRow1 > nRow2)
+ // invalid range.
+ return sc::MultiDataCellState();
+
+ if (aCol.empty())
+ return sc::MultiDataCellState(sc::MultiDataCellState::Empty);
+
+ auto setFirstCell = []( sc::MultiDataCellState& rRet, SCCOL nCurCol, SCROW nCurRow )
+ {
+ if (rRet.mnCol1 < 0)
+ {
+ // First cell not yet set. Set it.
+ rRet.mnCol1 = nCurCol;
+ rRet.mnRow1 = nCurRow;
+ }
+ };
+
+ SCCOL nMaxCol = aCol.size()-1;
+ bool bHasOne = false;
+ sc::MultiDataCellState aRet(sc::MultiDataCellState::Empty);
+
+ for (SCCOL nCol = nCol1; nCol <= nCol2 && nCol <= nMaxCol; ++nCol)
+ {
+ SCROW nFirstDataRow = -1;
+ switch (aCol[nCol].HasDataCellsInRange(nRow1, nRow2, &nFirstDataRow))
+ {
+ case sc::MultiDataCellState::HasOneCell:
+ {
+ setFirstCell(aRet, nCol, nFirstDataRow);
+
+ if (bHasOne)
+ {
+ // We've already found one data cell in another column.
+ aRet.meState = sc::MultiDataCellState::HasMultipleCells;
+ return aRet;
+ }
+ bHasOne = true;
+ break;
+ }
+ case sc::MultiDataCellState::HasMultipleCells:
+ {
+ setFirstCell(aRet, nCol, nFirstDataRow);
+
+ aRet.meState = sc::MultiDataCellState::HasMultipleCells;
+ return aRet;
+ }
+ case sc::MultiDataCellState::Empty:
+ default:
+ ;
+ }
+ }
+
+ if (bHasOne)
+ aRet.meState = sc::MultiDataCellState::HasOneCell;
+
+ return aRet;
+}
+
+void ScTable::DeleteBeforeCopyFromClip(
+ sc::CopyFromClipContext& rCxt, const ScTable& rClipTab, sc::ColumnSpanSet& rBroadcastSpans )
+{
+ sc::CopyFromClipContext::Range aRange = rCxt.getDestRange();
+ if (!ValidCol(aRange.mnCol1) || !ValidCol(aRange.mnCol2))
+ return;
+
+ // Pass some stuff to the columns via context.
+ rCxt.setTableProtected(IsProtected());
+ rCxt.setCondFormatList(mpCondFormatList.get());
+
+ ScRange aClipRange = rCxt.getClipDoc()->GetClipParam().getWholeRange();
+ SCCOL nClipCol = aClipRange.aStart.Col();
+ {
+ const SCCOL nMaxCol2 = std::min<SCCOL>( aRange.mnCol2, aCol.size() - 1 );
+ for (SCCOL nCol = aRange.mnCol1; nCol <= nMaxCol2; ++nCol, ++nClipCol)
+ {
+ if (nClipCol > aClipRange.aEnd.Col())
+ nClipCol = aClipRange.aStart.Col(); // loop through columns.
+
+ const ScColumn& rClipCol = const_cast<ScTable&>(rClipTab).CreateColumnIfNotExists(nClipCol);
+ aCol[nCol].DeleteBeforeCopyFromClip(rCxt, rClipCol, rBroadcastSpans);
+ }
+ }
+
+ SetStreamValid(false);
+}
+
+void ScTable::CopyOneCellFromClip(
+ sc::CopyFromClipContext& rCxt, const SCCOL nCol1, const SCROW nRow1, const SCCOL nCol2, const SCROW nRow2, const SCROW nSrcRow, const ScTable* pSrcTab )
+{
+ ScRange aSrcRange = rCxt.getClipDoc()->GetClipParam().getWholeRange();
+ SCCOL nSrcColSize = aSrcRange.aEnd.Col() - aSrcRange.aStart.Col() + 1;
+
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ {
+ SCCOL nColOffset = nCol - nCol1;
+ nColOffset = nColOffset % nSrcColSize;
+ assert(nColOffset >= 0);
+ CreateColumnIfNotExists(nCol).CopyOneCellFromClip(rCxt, nRow1, nRow2, nColOffset);
+
+ if (rCxt.getInsertFlag() & InsertDeleteFlags::ATTRIB)
+ {
+ for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow)
+ CopyConditionalFormat(nCol, nRow, nCol, nRow, nCol - aSrcRange.aStart.Col() - nColOffset,
+ nRow - nSrcRow, pSrcTab);
+ }
+ }
+
+ if (nCol1 == 0 && nCol2 == rDocument.MaxCol() && mpRowHeights)
+ {
+ mpRowHeights->setValue(nRow1, nRow2, pSrcTab->GetOriginalHeight(nSrcRow));
+
+ if (pRowFlags && pSrcTab->pRowFlags) {
+ if (pSrcTab->pRowFlags->GetValue(nSrcRow) & CRFlags::ManualSize)
+ pRowFlags->OrValue(nRow1, CRFlags::ManualSize);
+ else
+ pRowFlags->AndValue(nRow1, ~CRFlags::ManualSize);
+ }
+ }
+
+ // Copy graphics over too
+ bool bCopyGraphics
+ = (rCxt.getInsertFlag() & InsertDeleteFlags::OBJECTS) != InsertDeleteFlags::NONE;
+ if (!(bCopyGraphics && rCxt.getClipDoc()->mpDrawLayer))
+ return;
+
+ ScDrawLayer* pDrawLayer = GetDoc().GetDrawLayer();
+ OSL_ENSURE(pDrawLayer, "No drawing layer");
+ if (pDrawLayer)
+ {
+ const ScAddress aSrcStartPos
+ = rCxt.getClipDoc()->GetClipParam().getWholeRange().aStart;
+ const ScAddress aSrcEndPos = rCxt.getClipDoc()->GetClipParam().getWholeRange().aEnd;
+ tools::Rectangle aSourceRect = rCxt.getClipDoc()->GetMMRect(
+ aSrcStartPos.Col(), aSrcStartPos.Row(), aSrcEndPos.Col(), aSrcEndPos.Row(),
+ aSrcStartPos.Tab());
+ tools::Rectangle aDestRect = GetDoc().GetMMRect(nCol1, nRow1, nCol2, nRow2, nTab);
+ pDrawLayer->CopyFromClip(rCxt.getClipDoc()->mpDrawLayer.get(), aSrcStartPos.Tab(),
+ aSourceRect, ScAddress(nCol1, nRow1, nTab), aDestRect);
+ }
+}
+
+void ScTable::SetValues( const SCCOL nCol, const SCROW nRow, const std::vector<double>& rVals )
+{
+ if (!ValidCol(nCol))
+ return;
+
+ CreateColumnIfNotExists(nCol).SetValues(nRow, rVals);
+}
+
+void ScTable::TransferCellValuesTo( const SCCOL nCol, SCROW nRow, size_t nLen, sc::CellValues& rDest )
+{
+ if (!ValidCol(nCol))
+ return;
+
+ CreateColumnIfNotExists(nCol).TransferCellValuesTo(nRow, nLen, rDest);
+}
+
+void ScTable::CopyCellValuesFrom( const SCCOL nCol, SCROW nRow, const sc::CellValues& rSrc )
+{
+ if (!ValidCol(nCol))
+ return;
+
+ CreateColumnIfNotExists(nCol).CopyCellValuesFrom(nRow, rSrc);
+}
+
+void ScTable::ConvertFormulaToValue(
+ sc::EndListeningContext& rCxt, const SCCOL nCol1, const SCROW nRow1, const SCCOL nCol2, const SCROW nRow2,
+ sc::TableValues* pUndo )
+{
+ if (!ValidCol(nCol1) || !ValidCol(nCol2) || nCol1 > nCol2)
+ return;
+
+ for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
+ CreateColumnIfNotExists(nCol).ConvertFormulaToValue(rCxt, nRow1, nRow2, pUndo);
+}
+
+void ScTable::SwapNonEmpty(
+ sc::TableValues& rValues, sc::StartListeningContext& rStartCxt, sc::EndListeningContext& rEndCxt )
+{
+ const ScRange& rRange = rValues.getRange();
+ assert(rRange.IsValid());
+ for (SCCOL nCol = rRange.aStart.Col(); nCol <= rRange.aEnd.Col(); ++nCol)
+ CreateColumnIfNotExists(nCol).SwapNonEmpty(rValues, rStartCxt, rEndCxt);
+}
+
+void ScTable::PreprocessRangeNameUpdate(
+ sc::EndListeningContext& rEndListenCxt, sc::CompileFormulaContext& rCompileCxt )
+{
+ for (SCCOL i = 0; i < aCol.size(); ++i)
+ aCol[i].PreprocessRangeNameUpdate(rEndListenCxt, rCompileCxt);
+}
+
+void ScTable::PreprocessDBDataUpdate(
+ sc::EndListeningContext& rEndListenCxt, sc::CompileFormulaContext& rCompileCxt )
+{
+ for (SCCOL i = 0; i < aCol.size(); ++i)
+ aCol[i].PreprocessDBDataUpdate(rEndListenCxt, rCompileCxt);
+}
+
+void ScTable::CompileHybridFormula(
+ sc::StartListeningContext& rStartListenCxt, sc::CompileFormulaContext& rCompileCxt )
+{
+ for (SCCOL i = 0; i < aCol.size(); ++i)
+ aCol[i].CompileHybridFormula(rStartListenCxt, rCompileCxt);
+}
+
+void ScTable::UpdateScriptTypes( const SCCOL nCol1, SCROW nRow1, const SCCOL nCol2, SCROW nRow2 )
+{
+ if (!IsColValid(nCol1) || !ValidCol(nCol2) || nCol1 > nCol2)
+ return;
+
+ const SCCOL nMaxCol2 = std::min<SCCOL>( nCol2, aCol.size() - 1 );
+
+ for (SCCOL nCol = nCol1; nCol <= nMaxCol2; ++nCol)
+ aCol[nCol].UpdateScriptTypes(nRow1, nRow2);
+}
+
+bool ScTable::HasUniformRowHeight( SCROW nRow1, SCROW nRow2 ) const
+{
+ if (!ValidRow(nRow1) || !ValidRow(nRow2) || nRow1 > nRow2)
+ return false;
+
+ ScFlatUInt16RowSegments::RangeData aData;
+ if (!mpRowHeights->getRangeData(nRow1, aData))
+ // Search failed.
+ return false;
+
+ return nRow2 <= aData.mnRow2;
+}
+
+void ScTable::SplitFormulaGroups( SCCOL nCol, std::vector<SCROW>& rRows )
+{
+ if (!IsColValid(nCol))
+ return;
+
+ sc::SharedFormulaUtil::splitFormulaCellGroups(GetDoc(), aCol[nCol].maCells, rRows);
+}
+
+void ScTable::UnshareFormulaCells( SCCOL nCol, std::vector<SCROW>& rRows )
+{
+ if (!IsColValid(nCol))
+ return;
+
+ sc::SharedFormulaUtil::unshareFormulaCells(rDocument, aCol[nCol].maCells, rRows);
+}
+
+void ScTable::RegroupFormulaCells( SCCOL nCol )
+{
+ if (!IsColValid(nCol))
+ return;
+
+ aCol[nCol].RegroupFormulaCells();
+}
+
+bool ScTable::HasFormulaCell( const SCCOL nCol1, SCROW nRow1, const SCCOL nCol2, SCROW nRow2 ) const
+{
+ if (nCol2 < nCol1 || !IsColValid(nCol1) || !ValidCol(nCol2))
+ return false;
+
+ const SCCOL nMaxCol2 = std::min<SCCOL>( nCol2, aCol.size() - 1 );
+
+ for (SCCOL nCol = nCol1; nCol <= nMaxCol2; ++nCol)
+ if (aCol[nCol].HasFormulaCell(nRow1, nRow2))
+ return true;
+
+ return false;
+}
+
+void ScTable::EndListeningIntersectedGroup(
+ sc::EndListeningContext& rCxt, SCCOL nCol, SCROW nRow, std::vector<ScAddress>* pGroupPos )
+{
+ if (!IsColValid(nCol))
+ return;
+
+ aCol[nCol].EndListeningIntersectedGroup(rCxt, nRow, pGroupPos);
+}
+
+void ScTable::EndListeningIntersectedGroups(
+ sc::EndListeningContext& rCxt, const SCCOL nCol1, SCROW nRow1, const SCCOL nCol2, SCROW nRow2,
+ std::vector<ScAddress>* pGroupPos )
+{
+ if (nCol2 < nCol1 || !IsColValid(nCol1) || !ValidCol(nCol2))
+ return;
+
+ for (SCCOL nCol : GetAllocatedColumnsRange(nCol1, nCol2))
+ aCol[nCol].EndListeningIntersectedGroups(rCxt, nRow1, nRow2, pGroupPos);
+}
+
+void ScTable::EndListeningGroup( sc::EndListeningContext& rCxt, const SCCOL nCol, SCROW nRow )
+{
+ if (!IsColValid(nCol))
+ return;
+
+ aCol[nCol].EndListeningGroup(rCxt, nRow);
+}
+
+void ScTable::SetNeedsListeningGroup( SCCOL nCol, SCROW nRow )
+{
+ if (!ValidCol(nCol))
+ return;
+
+ CreateColumnIfNotExists(nCol).SetNeedsListeningGroup(nRow);
+}
+
+bool ScTable::IsEditActionAllowed(
+ sc::ColRowEditAction eAction, SCCOLROW nStart, SCCOLROW nEnd ) const
+{
+ if (!IsProtected())
+ {
+ SCCOL nCol1 = 0, nCol2 = aCol.size() - 1;
+ SCROW nRow1 = 0, nRow2 = rDocument.MaxRow();
+
+ switch (eAction)
+ {
+ case sc::ColRowEditAction::InsertColumnsBefore:
+ case sc::ColRowEditAction::InsertColumnsAfter:
+ case sc::ColRowEditAction::DeleteColumns:
+ {
+ nCol1 = nStart;
+ nCol2 = nEnd;
+ break;
+ }
+ case sc::ColRowEditAction::InsertRowsBefore:
+ case sc::ColRowEditAction::InsertRowsAfter:
+ case sc::ColRowEditAction::DeleteRows:
+ {
+ nRow1 = nStart;
+ nRow2 = nEnd;
+ break;
+ }
+ default:
+ ;
+ }
+
+ return IsBlockEditable(nCol1, nRow1, nCol2, nRow2, nullptr);
+ }
+
+ if (IsScenario())
+ // TODO: I don't even know what this scenario thingie is. Perhaps we
+ // should check it against the scenario ranges?
+ return false;
+
+ assert(pTabProtection);
+
+ switch (eAction)
+ {
+ case sc::ColRowEditAction::InsertColumnsBefore:
+ case sc::ColRowEditAction::InsertColumnsAfter:
+ {
+ // TODO: improve the matrix range handling for the insert-before action.
+ if (HasBlockMatrixFragment(nStart, 0, nEnd, rDocument.MaxRow()))
+ return false;
+
+ return pTabProtection->isOptionEnabled(ScTableProtection::INSERT_COLUMNS);
+ }
+ case sc::ColRowEditAction::InsertRowsBefore:
+ case sc::ColRowEditAction::InsertRowsAfter:
+ {
+ // TODO: improve the matrix range handling for the insert-before action.
+ if (HasBlockMatrixFragment(0, nStart, rDocument.MaxCol(), nEnd))
+ return false;
+
+ return pTabProtection->isOptionEnabled(ScTableProtection::INSERT_ROWS);
+ }
+ case sc::ColRowEditAction::DeleteColumns:
+ {
+ if (!pTabProtection->isOptionEnabled(ScTableProtection::DELETE_COLUMNS))
+ return false;
+
+ return !HasAttrib(nStart, 0, nEnd, rDocument.MaxRow(), HasAttrFlags::Protected);
+ }
+ case sc::ColRowEditAction::DeleteRows:
+ {
+ if (!pTabProtection->isOptionEnabled(ScTableProtection::DELETE_ROWS))
+ return false;
+
+ return !HasAttrib(0, nStart, rDocument.MaxCol(), nEnd, HasAttrFlags::Protected);
+ }
+ default:
+ ;
+ }
+
+ return false;
+}
+
+std::optional<sc::ColumnIterator> ScTable::GetColumnIterator( SCCOL nCol, SCROW nRow1, SCROW nRow2 ) const
+{
+ if (!ValidCol(nCol))
+ return {};
+
+ return const_cast<ScTable*>(this)->CreateColumnIfNotExists(nCol).GetColumnIterator(nRow1, nRow2);
+}
+
+bool ScTable::EnsureFormulaCellResults( const SCCOL nCol1, SCROW nRow1, const SCCOL nCol2, SCROW nRow2, bool bSkipRunning )
+{
+ if (nCol2 < nCol1 || !IsColValid(nCol1) || !ValidCol(nCol2))
+ return false;
+
+ const SCCOL nMaxCol2 = std::min<SCCOL>( nCol2, aCol.size() - 1 );
+
+ bool bAnyDirty = false;
+
+ for (SCCOL nCol = nCol1; nCol <= nMaxCol2; ++nCol)
+ {
+ bool bRet = aCol[nCol].EnsureFormulaCellResults(nRow1, nRow2, bSkipRunning);
+ bAnyDirty = bAnyDirty || bRet;
+ }
+
+ return bAnyDirty;
+}
+
+void ScTable::finalizeOutlineImport()
+{
+ if (pOutlineTable && pRowFlags)
+ {
+ pOutlineTable->GetRowArray().finalizeImport(*this);
+ }
+}
+
+void ScTable::StoreToCache(SvStream& rStrm) const
+{
+ SCCOL nStartCol = 0;
+ SCCOL nEndCol = rDocument.MaxCol();
+ SCROW nStartRow = 0;
+ SCROW nEndRow = rDocument.MaxRow();
+
+ GetDataArea(nStartCol, nStartRow, nEndCol, nEndRow, false, false);
+
+ rStrm.WriteUInt64(nEndCol + 1);
+ for (SCCOL nCol = 0; nCol <= nEndCol; ++nCol)
+ {
+ aCol[nCol].StoreToCache(rStrm);
+ }
+}
+
+void ScTable::RestoreFromCache(SvStream& rStrm)
+{
+ sal_uInt64 nCols = 0;
+ rStrm.ReadUInt64(nCols);
+ for (SCCOL nCol = 0; nCol < static_cast<SCCOL>(nCols); ++nCol)
+ {
+ aCol[nCol].RestoreFromCache(rStrm);
+ }
+}
+
+OString ScTable::dumpSheetGeomData(bool bColumns, SheetGeomType eGeomType)
+{
+ switch (eGeomType)
+ {
+ case SheetGeomType::SIZES:
+ // returns a non-empty space separated list of spans with trailing space.
+ // The format of the span is <size of any row/col in the span in print twips>:<last row/col of the span>
+ // Example (for columns with three spans if MAXCOL is 1023): "1280:3 1049:50 1280:1023"
+ return dumpColumnRowSizes(bColumns);
+ case SheetGeomType::HIDDEN:
+ // returns a non-empty space separated list of spans with trailing space.
+ // The format of the span is:
+ // 1) First span: <1 (span is hidden) / 0 (not hidden)>:<last row/col of the span>
+ // 2) Rest of the spans: <last row/col of the span>
+ // The hidden state of the spans after the first can be inferred from the first span's flag as no adjacent
+ // spans can have the same state by definition of span.
+ return dumpHiddenFiltered(bColumns, /*bHidden*/ true);
+ case SheetGeomType::FILTERED:
+ // has exactly the same format as 'hidden'.
+ return dumpHiddenFiltered(bColumns, /*bHidden*/ false);
+ case SheetGeomType::GROUPS:
+ // returns a space separated list of 'levels' with trailing space.
+ // A 'level' is a comma separated list of groups(outline entries) with trailing comma.
+ // format of a group is:
+ // <start row/col of group>:<number of rows/cols in the group>:<1/0(group is hidden?)>:<1/0(control is visible?)>
+ return dumpColumnRowGroups(bColumns);
+ default:
+ ;
+ }
+
+ return "";
+}
+
+OString ScTable::dumpColumnRowSizes(bool bColumns)
+{
+ // If the data-structures are not available, just report that all
+ // rows/cols have the default sizes.
+ static const OString aDefaultForCols
+ = OString::number(STD_COL_WIDTH) + ":" + OString::number(GetDoc().MaxCol()) + " ";
+ static const OString aDefaultForRows
+ = OString::number(ScGlobal::nStdRowHeight) + ":" + OString::number(GetDoc().MaxRow()) + " ";
+
+ // ScCompressedArray is a template class and we don't want to impose
+ // the restriction that its value type should be string serializable,
+ // instead just operate on the specialized object.
+ typedef ScCompressedArray<SCCOL, sal_uInt16> ColWidthsType;
+ auto dumpColWidths = [this](const ColWidthsType& rWidths) -> OString {
+ OString aOutput;
+ OString aSegment;
+ SCCOL nStartCol = 0;
+ const SCCOL nMaxCol = std::min(rWidths.GetLastPos(), GetDoc().MaxCol());
+ size_t nDummy = 0;
+ while (nStartCol <= nMaxCol)
+ {
+ SCCOL nEndCol;
+ sal_uInt16 nWidth = rWidths.GetValue(nStartCol, nDummy, nEndCol);
+ // The last span nEndCol is always MAXCOL+1 for some reason, and we don't want that.
+ if (nEndCol > nMaxCol)
+ nEndCol = nMaxCol;
+ aSegment = OString::number(nWidth) + ":" + OString::number(nEndCol) + " ";
+ aOutput += aSegment;
+ nStartCol = nEndCol + 1;
+ }
+
+ return aOutput;
+ };
+
+ if (bColumns)
+ return mpColWidth ? dumpColWidths(*mpColWidth) : aDefaultForCols;
+
+ return mpRowHeights ? mpRowHeights->dumpAsString() : aDefaultForRows;
+}
+
+OString ScTable::dumpHiddenFiltered(bool bColumns, bool bHidden)
+{
+ // defaults to no hidden/filtered row/cols.
+ static const OString aDefaultForCols = "0:" + OString::number(GetDoc().MaxCol()) + " ";
+ static const OString aDefaultForRows = "0:" + OString::number(GetDoc().MaxRow()) + " ";
+
+ if (bHidden)
+ {
+ if (bColumns)
+ return mpHiddenCols ? mpHiddenCols->dumpAsString() : aDefaultForCols;
+
+ return mpHiddenRows ? mpHiddenRows->dumpAsString() : aDefaultForRows;
+ }
+
+ if (bColumns)
+ return mpFilteredCols ? mpFilteredCols->dumpAsString() : aDefaultForCols;
+
+ return mpFilteredRows ? mpFilteredRows->dumpAsString() : aDefaultForRows;
+}
+
+OString ScTable::dumpColumnRowGroups(bool bColumns) const
+{
+ if (!pOutlineTable)
+ return "";
+
+ if (bColumns)
+ return pOutlineTable->GetColArray().dumpAsString();
+
+ return pOutlineTable->GetRowArray().dumpAsString();
+}
+
+SCCOL ScTable::GetLOKFreezeCol() const
+{
+ return maLOKFreezeCell.Col();
+}
+
+SCROW ScTable::GetLOKFreezeRow() const
+{
+ return maLOKFreezeCell.Row();
+}
+
+bool ScTable::SetLOKFreezeCol(SCCOL nFreezeCol)
+{
+ if (!ValidCol(nFreezeCol))
+ {
+ SAL_WARN("sc.core", "ScTable::SetLOKFreezeCol : invalid nFreezeCol = " << nFreezeCol);
+ return false;
+ }
+
+ if (maLOKFreezeCell.Col() != nFreezeCol)
+ {
+ maLOKFreezeCell.SetCol(nFreezeCol);
+ return true;
+ }
+
+ return false;
+}
+
+bool ScTable::SetLOKFreezeRow(SCROW nFreezeRow)
+{
+ if (!ValidRow(nFreezeRow))
+ {
+ SAL_WARN("sc.core", "ScTable::SetLOKFreezeRow : invalid nFreezeRow = " << nFreezeRow);
+ return false;
+ }
+
+ if (maLOKFreezeCell.Row() != nFreezeRow)
+ {
+ maLOKFreezeCell.SetRow(nFreezeRow);
+ return true;
+ }
+
+ return false;
+}
+
+std::set<SCCOL> ScTable::QueryColumnsWithFormulaCells() const
+{
+ std::set<SCCOL> aColIndices;
+
+ for (const auto& pCol : aCol)
+ {
+ if (pCol->HasFormulaCell())
+ aColIndices.insert(pCol->GetCol());
+ }
+
+ return aColIndices;
+}
+
+void ScTable::CheckIntegrity() const
+{
+ for (const auto& pCol : aCol)
+ pCol->CheckIntegrity();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/tabprotection.cxx b/sc/source/core/data/tabprotection.cxx
new file mode 100644
index 000000000..12bfff06b
--- /dev/null
+++ b/sc/source/core/data/tabprotection.cxx
@@ -0,0 +1,724 @@
+/* -*- 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 <tabprotection.hxx>
+#include <svl/PasswordHelper.hxx>
+#include <comphelper/docpasswordhelper.hxx>
+#include <comphelper/hash.hxx>
+#include <comphelper/sequence.hxx>
+#include <osl/diagnose.h>
+#include <document.hxx>
+
+#include <vector>
+
+#define DEBUG_TAB_PROTECTION 0
+
+constexpr OUStringLiteral URI_SHA1 = u"http://www.w3.org/2000/09/xmldsig#sha1";
+constexpr OUStringLiteral URI_SHA256_ODF12 = u"http://www.w3.org/2000/09/xmldsig#sha256";
+constexpr OUStringLiteral URI_SHA256_W3C = u"http://www.w3.org/2001/04/xmlenc#sha256";
+constexpr OUStringLiteral URI_XLS_LEGACY = u"http://docs.oasis-open.org/office/ns/table/legacy-hash-excel";
+
+using namespace ::com::sun::star;
+using ::com::sun::star::uno::Sequence;
+using ::std::vector;
+
+bool ScPassHashHelper::needsPassHashRegen(const ScDocument& rDoc, ScPasswordHash eHash1, ScPasswordHash eHash2)
+{
+ if (rDoc.IsDocProtected())
+ {
+ const ScDocProtection* p = rDoc.GetDocProtection();
+ if (!p->isPasswordEmpty() && !p->hasPasswordHash(eHash1, eHash2))
+ return true;
+ }
+
+ SCTAB nTabCount = rDoc.GetTableCount();
+ for (SCTAB i = 0; i < nTabCount; ++i)
+ {
+ const ScTableProtection* p = rDoc.GetTabProtection(i);
+ if (!p || !p->isProtected())
+ // Sheet not protected. Skip it.
+ continue;
+
+ if (!p->isPasswordEmpty() && !p->hasPasswordHash(eHash1, eHash2))
+ return true;
+ }
+
+ return false;
+}
+
+OUString ScPassHashHelper::getHashURI(ScPasswordHash eHash)
+{
+ switch (eHash)
+ {
+ case PASSHASH_SHA256:
+ return URI_SHA256_ODF12;
+ case PASSHASH_SHA1:
+ return URI_SHA1;
+ case PASSHASH_XL:
+ return URI_XLS_LEGACY;
+ case PASSHASH_UNSPECIFIED:
+ default:
+ ;
+ }
+ return OUString();
+}
+
+ScPasswordHash ScPassHashHelper::getHashTypeFromURI(std::u16string_view rURI)
+{
+ if (rURI == URI_SHA256_ODF12 || rURI == URI_SHA256_W3C)
+ return PASSHASH_SHA256;
+ if ( rURI == URI_SHA1 )
+ return PASSHASH_SHA1;
+ else if ( rURI == URI_XLS_LEGACY )
+ return PASSHASH_XL;
+ return PASSHASH_UNSPECIFIED;
+}
+
+bool ScOoxPasswordHash::verifyPassword( const OUString& aPassText ) const
+{
+ if (!hasPassword())
+ return false;
+
+ const OUString aHash( comphelper::DocPasswordHelper::GetOoxHashAsBase64(
+ aPassText, maSaltValue, mnSpinCount, comphelper::Hash::IterCount::APPEND, maAlgorithmName));
+ if (aHash.isEmpty())
+ // unsupported algorithm
+ return false;
+
+ return aHash == maHashValue;
+}
+
+ScPassHashProtectable::~ScPassHashProtectable()
+{
+}
+
+class ScTableProtectionImpl
+{
+public:
+ static Sequence<sal_Int8> hashPassword(std::u16string_view aPassText, ScPasswordHash eHash);
+ static Sequence<sal_Int8> hashPassword(const Sequence<sal_Int8>& rPassHash, ScPasswordHash eHash);
+
+ explicit ScTableProtectionImpl(SCSIZE nOptSize);
+ explicit ScTableProtectionImpl(const ScTableProtectionImpl& r);
+
+ bool isProtected() const { return mbProtected;}
+ bool isProtectedWithPass() const;
+ void setProtected(bool bProtected);
+
+ bool isPasswordEmpty() const { return mbEmptyPass;}
+ bool hasPasswordHash(ScPasswordHash eHash, ScPasswordHash eHash2) const;
+ void setPassword(const OUString& aPassText);
+ css::uno::Sequence<sal_Int8> getPasswordHash(
+ ScPasswordHash eHash, ScPasswordHash eHash2) const;
+ const ScOoxPasswordHash& getPasswordHash() const;
+ void setPasswordHash(
+ const css::uno::Sequence<sal_Int8>& aPassword,
+ ScPasswordHash eHash, ScPasswordHash eHash2);
+ void setPasswordHash( const OUString& rAlgorithmName, const OUString& rHashValue,
+ const OUString& rSaltValue, sal_uInt32 nSpinCount );
+ bool verifyPassword(const OUString& aPassText) const;
+
+ bool isOptionEnabled(SCSIZE nOptId) const;
+ void setOption(SCSIZE nOptId, bool bEnabled);
+
+ void setEnhancedProtection( ::std::vector< ScEnhancedProtection > && rProt );
+ const ::std::vector< ScEnhancedProtection > & getEnhancedProtection() const { return maEnhancedProtection;}
+ bool updateReference( UpdateRefMode, const ScDocument&, const ScRange& rWhere, SCCOL nDx, SCROW nDy, SCTAB nDz );
+ bool isBlockEditable( const ScRange& rRange ) const;
+ bool isSelectionEditable( const ScRangeList& rRangeList ) const;
+
+private:
+ OUString maPassText;
+ css::uno::Sequence<sal_Int8> maPassHash;
+ ::std::vector<bool> maOptions;
+ bool mbEmptyPass;
+ bool mbProtected;
+ ScPasswordHash meHash1;
+ ScPasswordHash meHash2;
+ ScOoxPasswordHash maPasswordHash;
+ ::std::vector< ScEnhancedProtection > maEnhancedProtection;
+};
+
+Sequence<sal_Int8> ScTableProtectionImpl::hashPassword(std::u16string_view aPassText, ScPasswordHash eHash)
+{
+ Sequence<sal_Int8> aHash;
+ switch (eHash)
+ {
+ case PASSHASH_XL:
+ aHash = ::comphelper::DocPasswordHelper::GetXLHashAsSequence( aPassText );
+ break;
+ case PASSHASH_SHA1:
+ SvPasswordHelper::GetHashPassword(aHash, aPassText);
+ break;
+ case PASSHASH_SHA1_UTF8:
+ SvPasswordHelper::GetHashPasswordSHA1UTF8(aHash, aPassText);
+ break;
+ case PASSHASH_SHA256:
+ SvPasswordHelper::GetHashPasswordSHA256(aHash, aPassText);
+ break;
+ default:
+ ;
+ }
+ return aHash;
+}
+
+Sequence<sal_Int8> ScTableProtectionImpl::hashPassword(
+ const Sequence<sal_Int8>& rPassHash, ScPasswordHash eHash)
+{
+ if (!rPassHash.hasElements() || eHash == PASSHASH_UNSPECIFIED)
+ return rPassHash;
+
+ // TODO: Right now, we only support double-hash by SHA1.
+ if (eHash == PASSHASH_SHA1)
+ {
+ auto aChars = comphelper::sequenceToContainer<vector<char>>(rPassHash);
+
+ Sequence<sal_Int8> aNewHash;
+ SvPasswordHelper::GetHashPassword(aNewHash, aChars.data(), aChars.size());
+ return aNewHash;
+ }
+
+ return rPassHash;
+}
+
+ScTableProtectionImpl::ScTableProtectionImpl(SCSIZE nOptSize) :
+ maOptions(nOptSize),
+ mbEmptyPass(true),
+ mbProtected(false),
+ meHash1(PASSHASH_SHA1),
+ meHash2(PASSHASH_UNSPECIFIED)
+{
+}
+
+ScTableProtectionImpl::ScTableProtectionImpl(const ScTableProtectionImpl& r) :
+ maPassText(r.maPassText),
+ maPassHash(r.maPassHash),
+ maOptions(r.maOptions),
+ mbEmptyPass(r.mbEmptyPass),
+ mbProtected(r.mbProtected),
+ meHash1(r.meHash1),
+ meHash2(r.meHash2),
+ maPasswordHash(r.maPasswordHash),
+ maEnhancedProtection(r.maEnhancedProtection)
+{
+}
+
+bool ScTableProtectionImpl::isProtectedWithPass() const
+{
+ if (!mbProtected)
+ return false;
+
+ return !maPassText.isEmpty() || maPassHash.hasElements() || maPasswordHash.hasPassword();
+}
+
+void ScTableProtectionImpl::setProtected(bool bProtected)
+{
+ mbProtected = bProtected;
+ // We need to keep the old password even when the protection is off. So,
+ // don't erase the password data here.
+}
+
+void ScTableProtectionImpl::setPassword(const OUString& aPassText)
+{
+ // We can't hash it here because we don't know whether this document will
+ // get saved to Excel or ODF, depending on which we will need to use a
+ // different hashing algorithm. One alternative is to hash it using all
+ // hash algorithms that we support, and store them all.
+
+ maPassText = aPassText;
+ mbEmptyPass = aPassText.isEmpty();
+ if (mbEmptyPass)
+ {
+ maPassHash = Sequence<sal_Int8>();
+ }
+ maPasswordHash.clear();
+}
+
+bool ScTableProtectionImpl::hasPasswordHash(ScPasswordHash eHash, ScPasswordHash eHash2) const
+{
+ if (mbEmptyPass)
+ return true;
+
+ if (!maPassText.isEmpty())
+ return true;
+
+ if (meHash1 == eHash)
+ {
+ if (meHash2 == PASSHASH_UNSPECIFIED)
+ // single hash.
+ return true;
+
+ return meHash2 == eHash2;
+ }
+
+ return false;
+}
+
+Sequence<sal_Int8> ScTableProtectionImpl::getPasswordHash(
+ ScPasswordHash eHash, ScPasswordHash eHash2) const
+{
+ Sequence<sal_Int8> aPassHash;
+
+ if (mbEmptyPass)
+ // Flagged as empty.
+ return aPassHash;
+
+ if (!maPassText.isEmpty())
+ {
+ // Cleartext password exists. Hash it.
+ aPassHash = hashPassword(maPassText, eHash);
+ if (eHash2 != PASSHASH_UNSPECIFIED)
+ // Double-hash it.
+ aPassHash = hashPassword(aPassHash, eHash2);
+
+ return aPassHash;
+ }
+ else
+ {
+ // No clear text password. Check if we have a hash value of the right hash type.
+ if (meHash1 == eHash)
+ {
+ aPassHash = maPassHash;
+
+ if (meHash2 == eHash2)
+ // Matching double-hash requested.
+ return aPassHash;
+ else if (meHash2 == PASSHASH_UNSPECIFIED)
+ // primary hashing type match. Double hash it by the requested
+ // double-hash type.
+ return hashPassword(aPassHash, eHash2);
+ }
+ }
+
+ // failed.
+ return Sequence<sal_Int8>();
+}
+
+const ScOoxPasswordHash& ScTableProtectionImpl::getPasswordHash() const
+{
+ return maPasswordHash;
+}
+
+void ScTableProtectionImpl::setPasswordHash(
+ const uno::Sequence<sal_Int8>& aPassword, ScPasswordHash eHash, ScPasswordHash eHash2)
+{
+ sal_Int32 nLen = aPassword.getLength();
+ mbEmptyPass = nLen <= 0;
+ meHash1 = eHash;
+ meHash2 = eHash2;
+ maPassHash = aPassword;
+
+#if DEBUG_TAB_PROTECTION
+ for (sal_Int8 n : aPassword)
+ printf("%2.2X ", static_cast<sal_uInt8>(n));
+ printf("\n");
+#endif
+}
+
+void ScTableProtectionImpl::setPasswordHash( const OUString& rAlgorithmName, const OUString& rHashValue,
+ const OUString& rSaltValue, sal_uInt32 nSpinCount )
+{
+ if (!rHashValue.isEmpty())
+ {
+ // Invalidate the other hashes.
+ setPasswordHash( uno::Sequence<sal_Int8>(), PASSHASH_UNSPECIFIED, PASSHASH_UNSPECIFIED);
+
+ // We don't know whether this is an empty password (or would
+ // unnecessarily have to try to verify an empty password), assume it is
+ // not. A later verifyPassword() with an empty password will determine.
+ // If this was not set to false then a verifyPassword() with an empty
+ // password would unlock even if this hash here wasn't for an empty
+ // password. Ugly stuff.
+ mbEmptyPass = false;
+ }
+
+ maPasswordHash.maAlgorithmName = rAlgorithmName;
+ maPasswordHash.maHashValue = rHashValue;
+ maPasswordHash.maSaltValue = rSaltValue;
+ maPasswordHash.mnSpinCount = nSpinCount;
+}
+
+bool ScTableProtectionImpl::verifyPassword(const OUString& aPassText) const
+{
+#if DEBUG_TAB_PROTECTION
+ fprintf(stdout, "ScTableProtectionImpl::verifyPassword: input = '%s'\n",
+ OUStringToOString(aPassText, RTL_TEXTENCODING_UTF8).getStr());
+#endif
+
+ if (mbEmptyPass)
+ return aPassText.isEmpty();
+
+ if (!maPassText.isEmpty())
+ // Clear text password exists, and this one takes precedence.
+ return aPassText == maPassText;
+
+ // For PASSHASH_UNSPECIFIED also maPassHash is empty and any aPassText
+ // would yield an empty hash as well and thus compare true. Don't.
+ if (meHash1 != PASSHASH_UNSPECIFIED)
+ {
+ Sequence<sal_Int8> aHash = hashPassword(aPassText, meHash1);
+ aHash = hashPassword(aHash, meHash2);
+
+#if DEBUG_TAB_PROTECTION
+ fprintf(stdout, "ScTableProtectionImpl::verifyPassword: hash = ");
+ for (sal_Int32 i = 0; i < aHash.getLength(); ++i)
+ printf("%2.2X ", static_cast<sal_uInt8>(aHash[i]));
+ printf("\n");
+#endif
+
+ if (aHash == maPassHash)
+ {
+ return true;
+ }
+ }
+
+ // tdf#115483 compat hack for ODF 1.2; for now UTF8-SHA1 passwords are only
+ // verified, not generated
+ if (meHash1 == PASSHASH_SHA1 && meHash2 == PASSHASH_UNSPECIFIED)
+ {
+ Sequence<sal_Int8> const aHash2 = hashPassword(aPassText, PASSHASH_SHA1_UTF8);
+ return aHash2 == maPassHash;
+ }
+
+ // Not yet generated or tracked with meHash1 or meHash2, but can be read
+ // from OOXML.
+ return maPasswordHash.verifyPassword( aPassText);
+}
+
+bool ScTableProtectionImpl::isOptionEnabled(SCSIZE nOptId) const
+{
+ if ( maOptions.size() <= static_cast<size_t>(nOptId) )
+ {
+ OSL_FAIL("ScTableProtectionImpl::isOptionEnabled: wrong size");
+ return false;
+ }
+
+ return maOptions[nOptId];
+}
+
+void ScTableProtectionImpl::setOption(SCSIZE nOptId, bool bEnabled)
+{
+ if ( maOptions.size() <= static_cast<size_t>(nOptId) )
+ {
+ OSL_FAIL("ScTableProtectionImpl::setOption: wrong size");
+ return;
+ }
+
+ maOptions[nOptId] = bEnabled;
+}
+
+void ScTableProtectionImpl::setEnhancedProtection( ::std::vector< ScEnhancedProtection > && rProt )
+{
+ maEnhancedProtection = std::move(rProt);
+}
+
+bool ScTableProtectionImpl::updateReference( UpdateRefMode eMode, const ScDocument& rDoc,
+ const ScRange& rWhere, SCCOL nDx, SCROW nDy, SCTAB nDz )
+{
+ bool bChanged = false;
+ for (auto& rEnhancedProtection : maEnhancedProtection)
+ {
+ if (rEnhancedProtection.maRangeList.is())
+ bChanged |= rEnhancedProtection.maRangeList->UpdateReference( eMode, &rDoc, rWhere, nDx, nDy, nDz);
+ }
+ return bChanged;
+}
+
+bool ScTableProtectionImpl::isBlockEditable( const ScRange& rRange ) const
+{
+ /* TODO: ask for password (and remember) if a password was set for
+ * a matching range and no matching range without password was encountered.
+ * Would need another return type than boolean to reflect
+ * "password required for a specific protection". */
+
+ // No protection exception or overriding permission to edit if empty.
+ if (maEnhancedProtection.empty())
+ return false;
+
+ // No security descriptor in an enhanced protection means the ranges of
+ // that protection are editable. If there is any security descriptor
+ // present we assume the permission to edit is not granted. Until we
+ // actually can evaluate the descriptors...
+
+ auto lIsEditable = [rRange](const ScEnhancedProtection& rEnhancedProtection) {
+ return !rEnhancedProtection.hasSecurityDescriptor()
+ && rEnhancedProtection.maRangeList.is() && rEnhancedProtection.maRangeList->Contains( rRange)
+ && !rEnhancedProtection.hasPassword(); // Range is editable if no password is assigned.
+ };
+ if (std::any_of(maEnhancedProtection.begin(), maEnhancedProtection.end(), lIsEditable))
+ return true;
+
+ // For a single address, a simple check with single ranges was sufficient.
+ if (rRange.aStart == rRange.aEnd)
+ return false;
+
+ // Test also for cases where rRange is encompassed by a union of two or
+ // more ranges of the list. The original ranges are not necessarily joined.
+ for (const auto& rEnhancedProtection : maEnhancedProtection)
+ {
+ if (!rEnhancedProtection.hasSecurityDescriptor() && rEnhancedProtection.maRangeList.is())
+ {
+ ScRangeList aList( rEnhancedProtection.maRangeList->GetIntersectedRange( rRange));
+ if (aList.size() == 1 && aList[0] == rRange)
+ {
+ // Range is editable if no password is assigned.
+ if (!rEnhancedProtection.hasPassword())
+ return true;
+ }
+ }
+ }
+
+ // Ranges may even be distributed over different protection records, for
+ // example if they are assigned different names, and can have different
+ // passwords. Combine the ones that can be edited.
+ /* TODO: once we handle passwords, remember a successful unlock at
+ * ScEnhancedProtection so we can use that here. */
+ ScRangeList aRangeList;
+ for (const auto& rEnhancedProtection : maEnhancedProtection)
+ {
+ if (!rEnhancedProtection.hasSecurityDescriptor() && rEnhancedProtection.maRangeList.is())
+ {
+ // Ranges are editable if no password is assigned.
+ if (!rEnhancedProtection.hasPassword())
+ {
+ const ScRangeList& rRanges = *rEnhancedProtection.maRangeList;
+ size_t nRanges = rRanges.size();
+ for (size_t i=0; i < nRanges; ++i)
+ {
+ aRangeList.push_back( rRanges[i]);
+ }
+ }
+ }
+ }
+ ScRangeList aResultList( aRangeList.GetIntersectedRange( rRange));
+ return aResultList.size() == 1 && aResultList[0] == rRange;
+}
+
+bool ScTableProtectionImpl::isSelectionEditable( const ScRangeList& rRangeList ) const
+{
+ if (rRangeList.empty())
+ return false;
+
+ for (size_t i=0, nRanges = rRangeList.size(); i < nRanges; ++i)
+ {
+ if (!isBlockEditable( rRangeList[i]))
+ return false;
+ }
+ return true;
+}
+
+ScDocProtection::ScDocProtection() :
+ mpImpl(new ScTableProtectionImpl(static_cast<SCSIZE>(ScDocProtection::NONE)))
+{
+}
+
+ScDocProtection::ScDocProtection(const ScDocProtection& r) :
+ ScPassHashProtectable(),
+ mpImpl(new ScTableProtectionImpl(*r.mpImpl))
+{
+}
+
+ScDocProtection::~ScDocProtection()
+{
+}
+
+bool ScDocProtection::isProtected() const
+{
+ return mpImpl->isProtected();
+}
+
+bool ScDocProtection::isProtectedWithPass() const
+{
+ return mpImpl->isProtectedWithPass();
+}
+
+void ScDocProtection::setProtected(bool bProtected)
+{
+ mpImpl->setProtected(bProtected);
+
+ // Currently Calc doesn't support document protection options. So, let's
+ // assume that when the document is protected, its structure is protected.
+ // We need to do this for Excel export.
+ mpImpl->setOption(ScDocProtection::STRUCTURE, bProtected);
+}
+
+bool ScDocProtection::isPasswordEmpty() const
+{
+ return mpImpl->isPasswordEmpty();
+}
+
+bool ScDocProtection::hasPasswordHash(ScPasswordHash eHash, ScPasswordHash eHash2) const
+{
+ return mpImpl->hasPasswordHash(eHash, eHash2);
+}
+
+void ScDocProtection::setPassword(const OUString& aPassText)
+{
+ mpImpl->setPassword(aPassText);
+}
+
+uno::Sequence<sal_Int8> ScDocProtection::getPasswordHash(ScPasswordHash eHash, ScPasswordHash eHash2) const
+{
+ return mpImpl->getPasswordHash(eHash, eHash2);
+}
+
+const ScOoxPasswordHash& ScDocProtection::getPasswordHash() const
+{
+ return mpImpl->getPasswordHash();
+}
+
+void ScDocProtection::setPasswordHash(
+ const uno::Sequence<sal_Int8>& aPassword, ScPasswordHash eHash, ScPasswordHash eHash2)
+{
+ mpImpl->setPasswordHash(aPassword, eHash, eHash2);
+}
+
+void ScDocProtection::setPasswordHash( const OUString& rAlgorithmName, const OUString& rHashValue,
+ const OUString& rSaltValue, sal_uInt32 nSpinCount )
+{
+ mpImpl->setPasswordHash( rAlgorithmName, rHashValue, rSaltValue, nSpinCount);
+}
+
+bool ScDocProtection::verifyPassword(const OUString& aPassText) const
+{
+ return mpImpl->verifyPassword(aPassText);
+}
+
+bool ScDocProtection::isOptionEnabled(Option eOption) const
+{
+ return mpImpl->isOptionEnabled(eOption);
+}
+
+void ScDocProtection::setOption(Option eOption, bool bEnabled)
+{
+ mpImpl->setOption(eOption, bEnabled);
+}
+
+ScTableProtection::ScTableProtection() :
+ mpImpl(new ScTableProtectionImpl(static_cast<SCSIZE>(ScTableProtection::NONE)))
+{
+ // Set default values for the options.
+ mpImpl->setOption(SELECT_LOCKED_CELLS, true);
+ mpImpl->setOption(SELECT_UNLOCKED_CELLS, true);
+}
+
+ScTableProtection::ScTableProtection(const ScTableProtection& r) :
+ ScPassHashProtectable(),
+ mpImpl(new ScTableProtectionImpl(*r.mpImpl))
+{
+}
+
+ScTableProtection::~ScTableProtection()
+{
+}
+
+bool ScTableProtection::isProtected() const
+{
+ return mpImpl->isProtected();
+}
+
+bool ScTableProtection::isProtectedWithPass() const
+{
+ return mpImpl->isProtectedWithPass();
+}
+
+void ScTableProtection::setProtected(bool bProtected)
+{
+ mpImpl->setProtected(bProtected);
+}
+
+bool ScTableProtection::isPasswordEmpty() const
+{
+ return mpImpl->isPasswordEmpty();
+}
+
+bool ScTableProtection::hasPasswordHash(ScPasswordHash eHash, ScPasswordHash eHash2) const
+{
+ return mpImpl->hasPasswordHash(eHash, eHash2);
+}
+
+void ScTableProtection::setPassword(const OUString& aPassText)
+{
+ mpImpl->setPassword(aPassText);
+}
+
+Sequence<sal_Int8> ScTableProtection::getPasswordHash(ScPasswordHash eHash, ScPasswordHash eHash2) const
+{
+ return mpImpl->getPasswordHash(eHash, eHash2);
+}
+
+const ScOoxPasswordHash& ScTableProtection::getPasswordHash() const
+{
+ return mpImpl->getPasswordHash();
+}
+
+void ScTableProtection::setPasswordHash(
+ const uno::Sequence<sal_Int8>& aPassword, ScPasswordHash eHash, ScPasswordHash eHash2)
+{
+ mpImpl->setPasswordHash(aPassword, eHash, eHash2);
+}
+
+void ScTableProtection::setPasswordHash( const OUString& rAlgorithmName, const OUString& rHashValue,
+ const OUString& rSaltValue, sal_uInt32 nSpinCount )
+{
+ mpImpl->setPasswordHash( rAlgorithmName, rHashValue, rSaltValue, nSpinCount);
+}
+
+bool ScTableProtection::verifyPassword(const OUString& aPassText) const
+{
+ return mpImpl->verifyPassword(aPassText);
+}
+
+bool ScTableProtection::isOptionEnabled(Option eOption) const
+{
+ return mpImpl->isOptionEnabled(eOption);
+}
+
+void ScTableProtection::setOption(Option eOption, bool bEnabled)
+{
+ mpImpl->setOption(eOption, bEnabled);
+}
+
+void ScTableProtection::setEnhancedProtection( ::std::vector< ScEnhancedProtection > && rProt )
+{
+ mpImpl->setEnhancedProtection(std::move(rProt));
+}
+
+const ::std::vector< ScEnhancedProtection > & ScTableProtection::getEnhancedProtection() const
+{
+ return mpImpl->getEnhancedProtection();
+}
+
+bool ScTableProtection::updateReference( UpdateRefMode eMode, const ScDocument& rDoc, const ScRange& rWhere,
+ SCCOL nDx, SCROW nDy, SCTAB nDz )
+{
+ return mpImpl->updateReference( eMode, rDoc, rWhere, nDx, nDy, nDz);
+}
+
+bool ScTableProtection::isBlockEditable( const ScRange& rRange ) const
+{
+ return mpImpl->isBlockEditable( rRange);
+}
+
+bool ScTableProtection::isSelectionEditable( const ScRangeList& rRangeList ) const
+{
+ return mpImpl->isSelectionEditable( rRangeList);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/types.cxx b/sc/source/core/data/types.cxx
new file mode 100644
index 000000000..e5f97c92d
--- /dev/null
+++ b/sc/source/core/data/types.cxx
@@ -0,0 +1,32 @@
+/* -*- 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 <types.hxx>
+#include <scmatrix.hxx>
+
+namespace sc {
+
+RangeMatrix::RangeMatrix() :
+ mpMat(nullptr), mnCol1(-1), mnRow1(-1), mnTab1(-1), mnCol2(-1), mnRow2(-1), mnTab2(-1) {}
+
+bool RangeMatrix::isRangeValid() const
+{
+ return mnCol1 >= 0 && mnRow1 >= 0 && mnTab1 >=0 &&
+ mnCol2 >= 0 && mnRow2 >= 0 && mnTab2 >= 0 &&
+ mnCol1 <= mnCol2 && mnRow1 <= mnRow2 && mnTab1 <= mnTab2;
+}
+
+MultiDataCellState::MultiDataCellState() :
+ mnRow1(-1), mnCol1(-1), meState(StateType::Invalid) {}
+MultiDataCellState::MultiDataCellState( StateType eState ) :
+ mnRow1(-1), mnCol1(-1), meState(eState) {}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/userdat.cxx b/sc/source/core/data/userdat.cxx
new file mode 100644
index 000000000..9a65caaa1
--- /dev/null
+++ b/sc/source/core/data/userdat.cxx
@@ -0,0 +1,51 @@
+/* -*- 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 <userdat.hxx>
+
+
+ScDrawObjData::ScDrawObjData() :
+ SdrObjUserData( SdrInventor::ScOrSwDraw, SC_UD_OBJDATA ),
+ maStart( ScAddress::INITIALIZE_INVALID ),
+ maEnd( ScAddress::INITIALIZE_INVALID ),
+ meType( DrawingObject ),
+ mbResizeWithCell( false )
+{
+}
+
+std::unique_ptr<SdrObjUserData> ScDrawObjData::Clone( SdrObject* ) const
+{
+ return std::unique_ptr<SdrObjUserData>(new ScDrawObjData( *this ));
+}
+
+ScMacroInfo::ScMacroInfo() :
+ SdrObjUserData( SdrInventor::ScOrSwDraw, SC_UD_MACRODATA )
+{
+}
+
+ScMacroInfo::~ScMacroInfo()
+{
+}
+
+std::unique_ptr<SdrObjUserData> ScMacroInfo::Clone( SdrObject* /*pObj*/ ) const
+{
+ return std::unique_ptr<SdrObjUserData>(new ScMacroInfo( *this ));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/data/validat.cxx b/sc/source/core/data/validat.cxx
new file mode 100644
index 000000000..688007885
--- /dev/null
+++ b/sc/source/core/data/validat.cxx
@@ -0,0 +1,1087 @@
+/* -*- 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 <validat.hxx>
+#include <com/sun/star/sheet/TableValidationVisibility.hpp>
+
+#include <sfx2/app.hxx>
+#include <sfx2/objsh.hxx>
+#include <sfx2/viewsh.hxx>
+#include <basic/sbmeth.hxx>
+#include <basic/sbmod.hxx>
+#include <basic/sbstar.hxx>
+#include <basic/sberrors.hxx>
+
+#include <basic/sbx.hxx>
+#include <svl/numformat.hxx>
+#include <svl/sharedstringpool.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/weld.hxx>
+#include <rtl/math.hxx>
+#include <osl/diagnose.h>
+
+#include <document.hxx>
+#include <formulacell.hxx>
+#include <patattr.hxx>
+#include <globstr.hrc>
+#include <scresid.hxx>
+#include <rangenam.hxx>
+#include <dbdata.hxx>
+#include <typedstrdata.hxx>
+#include <editutil.hxx>
+#include <tokenarray.hxx>
+#include <scmatrix.hxx>
+#include <cellvalue.hxx>
+#include <comphelper/lok.hxx>
+
+#include <math.h>
+#include <memory>
+
+using namespace formula;
+
+// Entries for validation (with only one condition)
+
+ScValidationData::ScValidationData( ScValidationMode eMode, ScConditionMode eOper,
+ const OUString& rExpr1, const OUString& rExpr2,
+ ScDocument& rDocument, const ScAddress& rPos,
+ const OUString& rExprNmsp1, const OUString& rExprNmsp2,
+ FormulaGrammar::Grammar eGrammar1,
+ FormulaGrammar::Grammar eGrammar2 )
+ : ScConditionEntry( eOper, rExpr1, rExpr2, rDocument, rPos, rExprNmsp1,
+ rExprNmsp2, eGrammar1, eGrammar2 )
+ , nKey( 0 )
+ , eDataMode( eMode )
+ , bShowInput(false)
+ , bShowError(false)
+ , eErrorStyle( SC_VALERR_STOP )
+ , mnListType( css::sheet::TableValidationVisibility::UNSORTED )
+{
+}
+
+ScValidationData::ScValidationData( ScValidationMode eMode, ScConditionMode eOper,
+ const ScTokenArray* pArr1, const ScTokenArray* pArr2,
+ ScDocument& rDocument, const ScAddress& rPos )
+ : ScConditionEntry( eOper, pArr1, pArr2, rDocument, rPos )
+ , nKey( 0 )
+ , eDataMode( eMode )
+ , bShowInput(false)
+ , bShowError(false)
+ , eErrorStyle( SC_VALERR_STOP )
+ , mnListType( css::sheet::TableValidationVisibility::UNSORTED )
+{
+}
+
+ScValidationData::ScValidationData( const ScValidationData& r )
+ : ScConditionEntry( r )
+ , nKey( r.nKey )
+ , eDataMode( r.eDataMode )
+ , bShowInput( r.bShowInput )
+ , bShowError( r.bShowError )
+ , eErrorStyle( r.eErrorStyle )
+ , mnListType( r.mnListType )
+ , aInputTitle( r.aInputTitle )
+ , aInputMessage( r.aInputMessage )
+ , aErrorTitle( r.aErrorTitle )
+ , aErrorMessage( r.aErrorMessage )
+{
+ // Formulae copied by RefCount
+}
+
+ScValidationData::ScValidationData( ScDocument& rDocument, const ScValidationData& r )
+ : ScConditionEntry( rDocument, r )
+ , nKey( r.nKey )
+ , eDataMode( r.eDataMode )
+ , bShowInput( r.bShowInput )
+ , bShowError( r.bShowError )
+ , eErrorStyle( r.eErrorStyle )
+ , mnListType( r.mnListType )
+ , aInputTitle( r.aInputTitle )
+ , aInputMessage( r.aInputMessage )
+ , aErrorTitle( r.aErrorTitle )
+ , aErrorMessage( r.aErrorMessage )
+{
+ // Formulae really copied
+}
+
+ScValidationData::~ScValidationData()
+{
+}
+
+bool ScValidationData::IsEmpty() const
+{
+ ScValidationData aDefault( SC_VALID_ANY, ScConditionMode::Equal, "", "", *GetDocument(), ScAddress() );
+ return EqualEntries( aDefault );
+}
+
+bool ScValidationData::EqualEntries( const ScValidationData& r ) const
+{
+ // test same parameters (excluding Key)
+
+ return ScConditionEntry::operator==(r) &&
+ eDataMode == r.eDataMode &&
+ bShowInput == r.bShowInput &&
+ bShowError == r.bShowError &&
+ eErrorStyle == r.eErrorStyle &&
+ mnListType == r.mnListType &&
+ aInputTitle == r.aInputTitle &&
+ aInputMessage == r.aInputMessage &&
+ aErrorTitle == r.aErrorTitle &&
+ aErrorMessage == r.aErrorMessage;
+}
+
+void ScValidationData::ResetInput()
+{
+ bShowInput = false;
+}
+
+void ScValidationData::ResetError()
+{
+ bShowError = false;
+}
+
+void ScValidationData::SetInput( const OUString& rTitle, const OUString& rMsg )
+{
+ bShowInput = true;
+ aInputTitle = rTitle;
+ aInputMessage = rMsg;
+}
+
+void ScValidationData::SetError( const OUString& rTitle, const OUString& rMsg,
+ ScValidErrorStyle eStyle )
+{
+ bShowError = true;
+ eErrorStyle = eStyle;
+ aErrorTitle = rTitle;
+ aErrorMessage = rMsg;
+}
+
+bool ScValidationData::GetErrMsg( OUString& rTitle, OUString& rMsg,
+ ScValidErrorStyle& rStyle ) const
+{
+ rTitle = aErrorTitle;
+ rMsg = aErrorMessage;
+ rStyle = eErrorStyle;
+ return bShowError;
+}
+
+bool ScValidationData::DoScript( const ScAddress& rPos, const OUString& rInput,
+ ScFormulaCell* pCell, weld::Window* pParent ) const
+{
+ ScDocument* pDocument = GetDocument();
+ SfxObjectShell* pDocSh = pDocument->GetDocumentShell();
+ if ( !pDocSh )
+ return false;
+
+ bool bScriptReturnedFalse = false; // default: do not abort
+
+ // 1) entered or calculated value
+ css::uno::Any aParam0(rInput);
+ if ( pCell ) // if cell exists, call interpret
+ {
+ if ( pCell->IsValue() )
+ aParam0 <<= pCell->GetValue();
+ else
+ aParam0 <<= pCell->GetString().getString();
+ }
+
+ // 2) Position of the cell
+ OUString aPosStr(rPos.Format(ScRefFlags::VALID | ScRefFlags::TAB_3D, pDocument, pDocument->GetAddressConvention()));
+
+ // Set up parameters
+ css::uno::Sequence< css::uno::Any > aParams{ aParam0, css::uno::Any(aPosStr) };
+
+ // use link-update flag to prevent closing the document
+ // while the macro is running
+ bool bWasInLinkUpdate = pDocument->IsInLinkUpdate();
+ if ( !bWasInLinkUpdate )
+ pDocument->SetInLinkUpdate( true );
+
+ if ( pCell )
+ pDocument->LockTable( rPos.Tab() );
+
+ css::uno::Any aRet;
+ css::uno::Sequence< sal_Int16 > aOutArgsIndex;
+ css::uno::Sequence< css::uno::Any > aOutArgs;
+
+ ErrCode eRet = pDocSh->CallXScript(
+ aErrorTitle, aParams, aRet, aOutArgsIndex, aOutArgs );
+
+ if ( pCell )
+ pDocument->UnlockTable( rPos.Tab() );
+
+ if ( !bWasInLinkUpdate )
+ pDocument->SetInLinkUpdate( false );
+
+ // Check the return value from the script
+ // The contents of the cell get reset if the script returns false
+ bool bTmp = false;
+ if ( eRet == ERRCODE_NONE &&
+ aRet.getValueType() == cppu::UnoType<bool>::get() &&
+ ( aRet >>= bTmp ) &&
+ !bTmp )
+ {
+ bScriptReturnedFalse = true;
+ }
+
+ if ( eRet == ERRCODE_BASIC_METHOD_NOT_FOUND && !pCell )
+ // Macro not found (only with input)
+ {
+ //TODO: different error message, if found, but not bAllowed ??
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pParent,
+ VclMessageType::Warning, VclButtonsType::Ok,
+ ScResId(STR_VALID_MACRONOTFOUND)));
+ xBox->run();
+ }
+
+ return bScriptReturnedFalse;
+}
+
+ // true -> abort
+
+bool ScValidationData::DoMacro( const ScAddress& rPos, const OUString& rInput,
+ ScFormulaCell* pCell, weld::Window* pParent ) const
+{
+ if ( SfxApplication::IsXScriptURL( aErrorTitle ) )
+ {
+ return DoScript( rPos, rInput, pCell, pParent );
+ }
+
+ ScDocument* pDocument = GetDocument();
+ SfxObjectShell* pDocSh = pDocument->GetDocumentShell();
+ if ( !pDocSh )
+ return false;
+
+ bool bDone = false;
+ bool bRet = false; // default: do not abort
+
+ // If the Doc was loaded during a Basic-Calls,
+ // the Sbx-object may not be created (?)
+// pDocSh->GetSbxObject();
+
+#if HAVE_FEATURE_SCRIPTING
+ // no security check ahead (only CheckMacroWarn), that happens in CallBasic
+
+ // Function search by their simple name,
+ // then assemble aBasicStr, aMacroStr for SfxObjectShell::CallBasic
+
+ StarBASIC* pRoot = pDocSh->GetBasic();
+ SbxVariable* pVar = pRoot->Find( aErrorTitle, SbxClassType::Method );
+ if (SbMethod* pMethod = dynamic_cast<SbMethod*>(pVar))
+ {
+ SbModule* pModule = pMethod->GetModule();
+ SbxObject* pObject = pModule->GetParent();
+ OUString aMacroStr(
+ pObject->GetName() + "." + pModule->GetName() + "." + pMethod->GetName());
+ OUString aBasicStr;
+
+ // the distinction between document- and app-basic has to be done
+ // by checking the parent (as in ScInterpreter::ScMacro), not by looping
+ // over all open documents, because this may be called from within loading,
+ // when SfxObjectShell::GetFirst/GetNext won't find the document.
+
+ if ( pObject->GetParent() )
+ aBasicStr = pObject->GetParent()->GetName(); // Basic of document
+ else
+ aBasicStr = SfxGetpApp()->GetName(); // Basic of application
+
+ // Parameter for Macro
+ SbxArrayRef refPar = new SbxArray;
+
+ // 1) entered or calculated value
+ OUString aValStr = rInput;
+ double nValue = 0.0;
+ bool bIsValue = false;
+ if ( pCell ) // if cell set, called from interpret
+ {
+ bIsValue = pCell->IsValue();
+ if ( bIsValue )
+ nValue = pCell->GetValue();
+ else
+ aValStr = pCell->GetString().getString();
+ }
+ if ( bIsValue )
+ refPar->Get(1)->PutDouble(nValue);
+ else
+ refPar->Get(1)->PutString(aValStr);
+
+ // 2) Position of the cell
+ OUString aPosStr(rPos.Format(ScRefFlags::VALID | ScRefFlags::TAB_3D, pDocument, pDocument->GetAddressConvention()));
+ refPar->Get(2)->PutString(aPosStr);
+
+ // use link-update flag to prevent closing the document
+ // while the macro is running
+ bool bWasInLinkUpdate = pDocument->IsInLinkUpdate();
+ if ( !bWasInLinkUpdate )
+ pDocument->SetInLinkUpdate( true );
+
+ if ( pCell )
+ pDocument->LockTable( rPos.Tab() );
+ SbxVariableRef refRes = new SbxVariable;
+ ErrCode eRet = pDocSh->CallBasic( aMacroStr, aBasicStr, refPar.get(), refRes.get() );
+ if ( pCell )
+ pDocument->UnlockTable( rPos.Tab() );
+
+ if ( !bWasInLinkUpdate )
+ pDocument->SetInLinkUpdate( false );
+
+ // Interrupt input if Basic macro returns false
+ if ( eRet == ERRCODE_NONE && refRes->GetType() == SbxBOOL && !refRes->GetBool() )
+ bRet = true;
+ bDone = true;
+ }
+#endif
+ if ( !bDone && !pCell ) // Macro not found (only with input)
+ {
+ //TODO: different error message, if found, but not bAllowed ??
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pParent,
+ VclMessageType::Warning, VclButtonsType::Ok,
+ ScResId(STR_VALID_MACRONOTFOUND)));
+ xBox->run();
+ }
+
+ return bRet;
+}
+
+void ScValidationData::DoCalcError( ScFormulaCell* pCell ) const
+{
+ if ( eErrorStyle == SC_VALERR_MACRO )
+ DoMacro( pCell->aPos, OUString(), pCell, nullptr );
+}
+
+ // true -> abort
+
+bool ScValidationData::DoError(weld::Window* pParent, const OUString& rInput,
+ const ScAddress& rPos) const
+{
+ if ( eErrorStyle == SC_VALERR_MACRO )
+ return DoMacro(rPos, rInput, nullptr, pParent);
+
+ // Output error message
+
+ OUString aTitle = aErrorTitle;
+ if (aTitle.isEmpty())
+ aTitle = ScResId( STR_MSSG_DOSUBTOTALS_0 ); // application title
+ OUString aMessage = aErrorMessage;
+ if (aMessage.isEmpty())
+ aMessage = ScResId( STR_VALID_DEFERROR );
+
+ VclButtonsType eStyle = VclButtonsType::Ok;
+ VclMessageType eType = VclMessageType::Error;
+ switch (eErrorStyle)
+ {
+ case SC_VALERR_INFO:
+ eType = VclMessageType::Info;
+ eStyle = VclButtonsType::OkCancel;
+ break;
+ case SC_VALERR_WARNING:
+ eType = VclMessageType::Warning;
+ eStyle = VclButtonsType::OkCancel;
+ break;
+ default:
+ break;
+ }
+
+ bool bIsMobile = comphelper::LibreOfficeKit::isActive() && SfxViewShell::Current()
+ && SfxViewShell::Current()->isLOKMobilePhone();
+
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pParent, eType,
+ eStyle, aMessage, bIsMobile));
+ xBox->set_title(aTitle);
+
+ switch (eErrorStyle)
+ {
+ case SC_VALERR_INFO:
+ xBox->set_default_response(RET_OK);
+ break;
+ case SC_VALERR_WARNING:
+ xBox->set_default_response(RET_CANCEL);
+ break;
+ default:
+ break;
+ }
+
+ short nRet = xBox->run();
+
+ return ( eErrorStyle == SC_VALERR_STOP || nRet == RET_CANCEL );
+}
+
+bool ScValidationData::IsDataValidCustom(
+ const OUString& rTest,
+ const ScPatternAttr& rPattern,
+ const ScAddress& rPos,
+ const CustomValidationPrivateAccess& ) const
+{
+ OSL_ENSURE(GetDataMode() == SC_VALID_CUSTOM,
+ "ScValidationData::IsDataValidCustom invoked for a non-custom validation");
+
+ if (rTest.isEmpty()) // check whether empty cells are allowed
+ return IsIgnoreBlank();
+
+ if (rTest[0] == '=') // formulas do not pass the validity test
+ return false;
+
+ SvNumberFormatter* pFormatter = GetDocument()->GetFormatTable();
+
+ // get the value if any
+ sal_uInt32 nFormat = rPattern.GetNumberFormat( pFormatter );
+ double nVal;
+ bool bIsVal = pFormatter->IsNumberFormat( rTest, nFormat, nVal );
+
+ ScRefCellValue aTmpCell;
+ svl::SharedString aSS;
+ if (bIsVal)
+ {
+ aTmpCell.meType = CELLTYPE_VALUE;
+ aTmpCell.mfValue = nVal;
+ }
+ else
+ {
+ aTmpCell.meType = CELLTYPE_STRING;
+ aSS = mpDoc->GetSharedStringPool().intern(rTest);
+ aTmpCell.mpString = &aSS;
+ }
+
+ ScCellValue aOriginalCellValue(ScRefCellValue(*GetDocument(), rPos));
+
+ aTmpCell.commit(*GetDocument(), rPos);
+ bool bRet = IsCellValid(aTmpCell, rPos);
+ aOriginalCellValue.commit(*GetDocument(), rPos);
+
+ return bRet;
+}
+
+/** To test numeric data text length in IsDataValidTextLen().
+
+ If mpFormatter is not set, it is obtained from the document and the format
+ key is determined from the cell position's attribute pattern.
+ */
+struct ScValidationDataIsNumeric
+{
+ SvNumberFormatter* mpFormatter;
+ double mfVal;
+ sal_uInt32 mnFormat;
+
+ ScValidationDataIsNumeric( double fVal, SvNumberFormatter* pFormatter = nullptr, sal_uInt32 nFormat = 0 )
+ : mpFormatter(pFormatter), mfVal(fVal), mnFormat(nFormat)
+ {
+ }
+
+ void init( const ScDocument& rDoc, const ScAddress& rPos )
+ {
+ const ScPatternAttr* pPattern = rDoc.GetPattern( rPos.Col(), rPos.Row(), rPos.Tab());
+ mpFormatter = rDoc.GetFormatTable();
+ mnFormat = pPattern->GetNumberFormat( mpFormatter);
+ }
+};
+
+bool ScValidationData::IsDataValidTextLen( const OUString& rTest, const ScAddress& rPos,
+ ScValidationDataIsNumeric* pDataNumeric ) const
+{
+ sal_Int32 nLen;
+ if (!pDataNumeric)
+ nLen = rTest.getLength();
+ else
+ {
+ if (!pDataNumeric->mpFormatter)
+ pDataNumeric->init( *GetDocument(), rPos);
+
+ // For numeric values use the resulting input line string to
+ // determine length, otherwise an once accepted value maybe could
+ // not be edited again, for example abbreviated dates or leading
+ // zeros or trailing zeros after decimal separator change length.
+ OUString aStr;
+ pDataNumeric->mpFormatter->GetInputLineString( pDataNumeric->mfVal, pDataNumeric->mnFormat, aStr);
+ nLen = aStr.getLength();
+ }
+ ScRefCellValue aTmpCell( static_cast<double>(nLen));
+ return IsCellValid( aTmpCell, rPos);
+}
+
+bool ScValidationData::IsDataValid(
+ const OUString& rTest, const ScPatternAttr& rPattern, const ScAddress& rPos ) const
+{
+ if ( eDataMode == SC_VALID_ANY ) // check if any cell content is allowed
+ return true;
+
+ if (rTest.isEmpty()) // check whether empty cells are allowed
+ return IsIgnoreBlank();
+
+ if (rTest[0] == '=') // formulas do not pass the validity test
+ return false;
+
+ SvNumberFormatter* pFormatter = GetDocument()->GetFormatTable();
+
+ // get the value if any
+ sal_uInt32 nFormat = rPattern.GetNumberFormat( pFormatter );
+ double nVal;
+ bool bIsVal = pFormatter->IsNumberFormat( rTest, nFormat, nVal );
+
+ bool bRet;
+ if (SC_VALID_TEXTLEN == eDataMode)
+ {
+ if (!bIsVal)
+ bRet = IsDataValidTextLen( rTest, rPos, nullptr);
+ else
+ {
+ ScValidationDataIsNumeric aDataNumeric( nVal, pFormatter, nFormat);
+ bRet = IsDataValidTextLen( rTest, rPos, &aDataNumeric);
+ }
+ }
+ else
+ {
+ if (bIsVal)
+ {
+ ScRefCellValue aTmpCell(nVal);
+ bRet = IsDataValid(aTmpCell, rPos);
+ }
+ else
+ {
+ svl::SharedString aSS = mpDoc->GetSharedStringPool().intern(rTest);
+ ScRefCellValue aTmpCell(&aSS);
+ bRet = IsDataValid(aTmpCell, rPos);
+ }
+ }
+
+ return bRet;
+}
+
+bool ScValidationData::IsDataValid( ScRefCellValue& rCell, const ScAddress& rPos ) const
+{
+ if( eDataMode == SC_VALID_LIST )
+ return IsListValid(rCell, rPos);
+
+ if ( eDataMode == SC_VALID_CUSTOM )
+ return IsCellValid(rCell, rPos);
+
+ double nVal = 0.0;
+ OUString aString;
+ bool bIsVal = true;
+
+ switch (rCell.meType)
+ {
+ case CELLTYPE_VALUE:
+ nVal = rCell.mfValue;
+ break;
+ case CELLTYPE_STRING:
+ aString = rCell.mpString->getString();
+ bIsVal = false;
+ break;
+ case CELLTYPE_EDIT:
+ if (rCell.mpEditText)
+ aString = ScEditUtil::GetString(*rCell.mpEditText, GetDocument());
+ bIsVal = false;
+ break;
+ case CELLTYPE_FORMULA:
+ {
+ ScFormulaCell* pFCell = rCell.mpFormula;
+ bIsVal = pFCell->IsValue();
+ if ( bIsVal )
+ nVal = pFCell->GetValue();
+ else
+ aString = pFCell->GetString().getString();
+ }
+ break;
+ default: // Notes, Broadcaster
+ return IsIgnoreBlank(); // as set
+ }
+
+ bool bOk = true;
+ switch (eDataMode)
+ {
+ // SC_VALID_ANY already above
+
+ case SC_VALID_WHOLE:
+ case SC_VALID_DECIMAL:
+ case SC_VALID_DATE: // Date/Time is only formatting
+ case SC_VALID_TIME:
+ bOk = bIsVal;
+ if ( bOk && eDataMode == SC_VALID_WHOLE )
+ bOk = ::rtl::math::approxEqual( nVal, floor(nVal+0.5) ); // integers
+ if ( bOk )
+ bOk = IsCellValid(rCell, rPos);
+ break;
+
+ case SC_VALID_TEXTLEN:
+ if (!bIsVal)
+ bOk = IsDataValidTextLen( aString, rPos, nullptr);
+ else
+ {
+ ScValidationDataIsNumeric aDataNumeric( nVal);
+ bOk = IsDataValidTextLen( aString, rPos, &aDataNumeric);
+ }
+ break;
+
+ default:
+ OSL_FAIL("not yet done");
+ break;
+ }
+
+ return bOk;
+}
+
+namespace {
+
+/** Token array helper. Iterates over all string tokens.
+ @descr The token array must contain separated string tokens only.
+ @param bSkipEmpty true = Ignores string tokens with empty strings. */
+class ScStringTokenIterator
+{
+public:
+ explicit ScStringTokenIterator( const ScTokenArray& rTokArr ) :
+ maIter( rTokArr ), mbOk( true ) {}
+
+ /** Returns the string of the first string token or NULL on error or empty token array. */
+ rtl_uString* First();
+ /** Returns the string of the next string token or NULL on error or end of token array. */
+ rtl_uString* Next();
+
+ /** Returns false, if a wrong token has been found. Does NOT return false on end of token array. */
+ bool Ok() const { return mbOk; }
+
+private:
+ svl::SharedString maCurString; /// Current string.
+ FormulaTokenArrayPlainIterator maIter;
+ bool mbOk; /// true = correct token or end of token array.
+};
+
+rtl_uString* ScStringTokenIterator::First()
+{
+ maIter.Reset();
+ mbOk = true;
+ return Next();
+}
+
+rtl_uString* ScStringTokenIterator::Next()
+{
+ if( !mbOk )
+ return nullptr;
+
+ // seek to next non-separator token
+ const FormulaToken* pToken = maIter.NextNoSpaces();
+ while( pToken && (pToken->GetOpCode() == ocSep) )
+ pToken = maIter.NextNoSpaces();
+
+ mbOk = !pToken || (pToken->GetType() == formula::svString);
+
+ maCurString = svl::SharedString(); // start with invalid string.
+ if (mbOk && pToken)
+ maCurString = pToken->GetString();
+
+ // string found but empty -> get next token; otherwise return it
+ return (maCurString.isValid() && maCurString.isEmpty()) ? Next() : maCurString.getData();
+}
+
+/** Returns the number format of the passed cell, or the standard format. */
+sal_uLong lclGetCellFormat( const ScDocument& rDoc, const ScAddress& rPos )
+{
+ const ScPatternAttr* pPattern = rDoc.GetPattern( rPos.Col(), rPos.Row(), rPos.Tab() );
+ if( !pPattern )
+ pPattern = rDoc.GetDefPattern();
+ return pPattern->GetNumberFormat( rDoc.GetFormatTable() );
+}
+
+} // namespace
+
+bool ScValidationData::HasSelectionList() const
+{
+ return (eDataMode == SC_VALID_LIST) && (mnListType != css::sheet::TableValidationVisibility::INVISIBLE);
+}
+
+bool ScValidationData::GetSelectionFromFormula(
+ std::vector<ScTypedStrData>* pStrings, ScRefCellValue& rCell, const ScAddress& rPos,
+ const ScTokenArray& rTokArr, int& rMatch) const
+{
+ bool bOk = true;
+
+ // pDoc is private in condition, use an accessor and a long winded name.
+ ScDocument* pDocument = GetDocument();
+ if( nullptr == pDocument )
+ return false;
+
+ ScFormulaCell aValidationSrc(
+ *pDocument, rPos, rTokArr, formula::FormulaGrammar::GRAM_DEFAULT, ScMatrixMode::Formula);
+
+ // Make sure the formula gets interpreted and a result is delivered,
+ // regardless of the AutoCalc setting.
+ aValidationSrc.Interpret();
+
+ ScMatrixRef xMatRef;
+ const ScMatrix *pValues = aValidationSrc.GetMatrix();
+ if (!pValues)
+ {
+ // The somewhat nasty case of either an error occurred, or the
+ // dereferenced value of a single cell reference or an immediate result
+ // is stored as a single value.
+
+ // Use an interim matrix to create the TypedStrData below.
+ xMatRef = new ScMatrix(1, 1, 0.0);
+
+ FormulaError nErrCode = aValidationSrc.GetErrCode();
+ if (nErrCode != FormulaError::NONE)
+ {
+ /* TODO : to use later in an alert box?
+ * OUString rStrResult = "...";
+ * rStrResult += ScGlobal::GetLongErrorString(nErrCode);
+ */
+
+ xMatRef->PutError( nErrCode, 0, 0);
+ bOk = false;
+ }
+ else if (aValidationSrc.IsValue())
+ xMatRef->PutDouble( aValidationSrc.GetValue(), 0);
+ else
+ {
+ svl::SharedString aStr = aValidationSrc.GetString();
+ xMatRef->PutString(aStr, 0);
+ }
+
+ pValues = xMatRef.get();
+ }
+
+ // which index matched. We will want it eventually to pre-select that item.
+ rMatch = -1;
+
+ SvNumberFormatter* pFormatter = GetDocument()->GetFormatTable();
+
+ SCSIZE nCol, nRow, nCols, nRows, n = 0;
+ pValues->GetDimensions( nCols, nRows );
+
+ bool bRef = false;
+ ScRange aRange;
+
+ ScTokenArray* pArr = const_cast<ScTokenArray*>(&rTokArr);
+ if (pArr->GetLen() == 1)
+ {
+ formula::FormulaTokenArrayPlainIterator aIter(*pArr);
+ formula::FormulaToken* t = aIter.GetNextReferenceOrName();
+ if (t)
+ {
+ OpCode eOpCode = t->GetOpCode();
+ if (eOpCode == ocDBArea || eOpCode == ocTableRef)
+ {
+ if (const ScDBData* pDBData = pDocument->GetDBCollection()->getNamedDBs().findByIndex(t->GetIndex()))
+ {
+ pDBData->GetArea(aRange);
+ bRef = true;
+ }
+ }
+ else if (eOpCode == ocName)
+ {
+ const ScRangeData* pName = pDocument->FindRangeNameBySheetAndIndex( t->GetSheet(), t->GetIndex());
+ if (pName && pName->IsReference(aRange))
+ {
+ bRef = true;
+ }
+ }
+ else if (t->GetType() != svIndex)
+ {
+ if (pArr->IsValidReference(aRange, rPos))
+ {
+ bRef = true;
+ }
+ }
+ }
+ }
+
+ bool bHaveEmpty = false;
+ svl::SharedStringPool& rSPool = pDocument->GetSharedStringPool();
+
+ /* XL artificially limits things to a single col or row in the UI but does
+ * not list the constraint in MOOXml. If a defined name or INDIRECT
+ * resulting in 1D is entered in the UI and the definition later modified
+ * to 2D, it is evaluated fine and also stored and loaded. Lets get ahead
+ * of the curve and support 2d. In XL, values are listed row-wise, do the
+ * same. */
+ for( nRow = 0; nRow < nRows ; nRow++ )
+ {
+ for( nCol = 0; nCol < nCols ; nCol++ )
+ {
+ ScTokenArray aCondTokArr(*pDocument);
+ std::unique_ptr<ScTypedStrData> pEntry;
+ OUString aValStr;
+ ScMatrixValue nMatVal = pValues->Get( nCol, nRow);
+
+ // strings and empties
+ if( ScMatrix::IsNonValueType( nMatVal.nType ) )
+ {
+ aValStr = nMatVal.GetString().getString();
+
+ // Do not add multiple empty strings to the validation list,
+ // especially not if they'd bloat the tail with a million empty
+ // entries for a column range, fdo#61520
+ if (aValStr.isEmpty())
+ {
+ if (bHaveEmpty)
+ continue;
+ bHaveEmpty = true;
+ }
+
+ if( nullptr != pStrings )
+ pEntry.reset(new ScTypedStrData(aValStr, 0.0, 0.0, ScTypedStrData::Standard));
+
+ if (!rCell.isEmpty() && rMatch < 0)
+ aCondTokArr.AddString(rSPool.intern(aValStr));
+ }
+ else
+ {
+ FormulaError nErr = nMatVal.GetError();
+
+ if( FormulaError::NONE != nErr )
+ {
+ aValStr = ScGlobal::GetErrorString( nErr );
+ }
+ else
+ {
+ // FIXME FIXME FIXME
+ // Feature regression. Date formats are lost passing through the matrix
+ //pFormatter->GetInputLineString( pMatVal->fVal, 0, aValStr );
+ //For external reference and a formula that results in an area or array, date formats are still lost.
+ if ( bRef )
+ {
+ aValStr = pDocument->GetInputString(static_cast<SCCOL>(nCol+aRange.aStart.Col()),
+ static_cast<SCROW>(nRow+aRange.aStart.Row()), aRange.aStart.Tab());
+ }
+ else
+ {
+ pFormatter->GetInputLineString( nMatVal.fVal, 0, aValStr );
+ }
+ }
+
+ if (!rCell.isEmpty() && rMatch < 0)
+ {
+ // I am not sure errors will work here, but a user can no
+ // manually enter an error yet so the point is somewhat moot.
+ aCondTokArr.AddDouble( nMatVal.fVal );
+ }
+ if( nullptr != pStrings )
+ pEntry.reset(new ScTypedStrData(aValStr, nMatVal.fVal, nMatVal.fVal, ScTypedStrData::Value));
+ }
+
+ if (rMatch < 0 && !rCell.isEmpty() && IsEqualToTokenArray(rCell, rPos, aCondTokArr))
+ {
+ rMatch = n;
+ // short circuit on the first match if not filling the list
+ if( nullptr == pStrings )
+ return true;
+ }
+
+ if( pEntry )
+ {
+ assert(pStrings);
+ pStrings->push_back(*pEntry);
+ n++;
+ }
+ }
+ }
+
+ // In case of no match needed and an error occurred, return that error
+ // entry as valid instead of silently failing.
+ return bOk || rCell.isEmpty();
+}
+
+bool ScValidationData::FillSelectionList(std::vector<ScTypedStrData>& rStrColl, const ScAddress& rPos) const
+{
+ bool bOk = false;
+
+ if( HasSelectionList() )
+ {
+ std::unique_ptr<ScTokenArray> pTokArr( CreateFlatCopiedTokenArray(0) );
+
+ // *** try if formula is a string list ***
+
+ sal_uInt32 nFormat = lclGetCellFormat( *GetDocument(), rPos );
+ ScStringTokenIterator aIt( *pTokArr );
+ for (rtl_uString* pString = aIt.First(); pString && aIt.Ok(); pString = aIt.Next())
+ {
+ double fValue;
+ OUString aStr(pString);
+ bool bIsValue = GetDocument()->GetFormatTable()->IsNumberFormat(aStr, nFormat, fValue);
+ rStrColl.emplace_back(
+ aStr, fValue, fValue, bIsValue ? ScTypedStrData::Value : ScTypedStrData::Standard);
+ }
+ bOk = aIt.Ok();
+
+ // *** if not a string list, try if formula results in a cell range or
+ // anything else we recognize as valid ***
+
+ if (!bOk)
+ {
+ int nMatch;
+ ScRefCellValue aEmptyCell;
+ bOk = GetSelectionFromFormula(&rStrColl, aEmptyCell, rPos, *pTokArr, nMatch);
+ }
+ }
+
+ return bOk;
+}
+
+bool ScValidationData::IsEqualToTokenArray( ScRefCellValue& rCell, const ScAddress& rPos, const ScTokenArray& rTokArr ) const
+{
+ // create a condition entry that tests on equality and set the passed token array
+ ScConditionEntry aCondEntry( ScConditionMode::Equal, &rTokArr, nullptr, *GetDocument(), rPos );
+ return aCondEntry.IsCellValid(rCell, rPos);
+}
+
+bool ScValidationData::IsListValid( ScRefCellValue& rCell, const ScAddress& rPos ) const
+{
+ bool bIsValid = false;
+
+ /* Compare input cell with all supported tokens from the formula.
+ Currently a formula may contain:
+ 1) A list of strings (at least one string).
+ 2) A single cell or range reference.
+ 3) A single defined name (must contain a cell/range reference, another
+ name, or DB range, or a formula resulting in a cell/range reference
+ or matrix/array).
+ 4) A single database range.
+ 5) A formula resulting in a cell/range reference or matrix/array.
+ */
+
+ std::unique_ptr< ScTokenArray > pTokArr( CreateFlatCopiedTokenArray( 0 ) );
+
+ // *** try if formula is a string list ***
+
+ svl::SharedStringPool& rSPool = GetDocument()->GetSharedStringPool();
+ sal_uInt32 nFormat = lclGetCellFormat( *GetDocument(), rPos );
+ ScStringTokenIterator aIt( *pTokArr );
+ for (rtl_uString* pString = aIt.First(); pString && aIt.Ok(); pString = aIt.Next())
+ {
+ /* Do not break the loop, if a valid string has been found.
+ This is to find invalid tokens following in the formula. */
+ if( !bIsValid )
+ {
+ // create a formula containing a single string or number
+ ScTokenArray aCondTokArr(*GetDocument());
+ double fValue;
+ OUString aStr(pString);
+ if (GetDocument()->GetFormatTable()->IsNumberFormat(aStr, nFormat, fValue))
+ aCondTokArr.AddDouble( fValue );
+ else
+ aCondTokArr.AddString(rSPool.intern(aStr));
+
+ bIsValid = IsEqualToTokenArray(rCell, rPos, aCondTokArr);
+ }
+ }
+
+ if( !aIt.Ok() )
+ bIsValid = false;
+
+ // *** if not a string list, try if formula results in a cell range or
+ // anything else we recognize as valid ***
+
+ if (!bIsValid)
+ {
+ int nMatch;
+ bIsValid = GetSelectionFromFormula(nullptr, rCell, rPos, *pTokArr, nMatch);
+ bIsValid = bIsValid && nMatch >= 0;
+ }
+
+ return bIsValid;
+}
+
+ScValidationDataList::ScValidationDataList(const ScValidationDataList& rList)
+{
+ // for Ref-Undo - real copy with new tokens!
+
+ for (const auto& rxItem : rList)
+ {
+ InsertNew( std::unique_ptr<ScValidationData>(rxItem->Clone()) );
+ }
+
+ //TODO: faster insert for sorted entries from rList ???
+}
+
+ScValidationDataList::ScValidationDataList(ScDocument& rNewDoc,
+ const ScValidationDataList& rList)
+{
+ // for new documents - real copy with new tokens!
+
+ for (const auto& rxItem : rList)
+ {
+ InsertNew( std::unique_ptr<ScValidationData>(rxItem->Clone(&rNewDoc)) );
+ }
+
+ //TODO: faster insert for sorted entries from rList ???
+}
+
+ScValidationData* ScValidationDataList::GetData( sal_uInt32 nKey )
+{
+ //TODO: binary search
+
+ for( iterator it = begin(); it != end(); ++it )
+ if( (*it)->GetKey() == nKey )
+ return it->get();
+
+ OSL_FAIL("ScValidationDataList: Entry not found");
+ return nullptr;
+}
+
+void ScValidationDataList::CompileXML()
+{
+ for( iterator it = begin(); it != end(); ++it )
+ (*it)->CompileXML();
+}
+
+void ScValidationDataList::UpdateReference( sc::RefUpdateContext& rCxt )
+{
+ for( iterator it = begin(); it != end(); ++it )
+ (*it)->UpdateReference(rCxt);
+}
+
+void ScValidationDataList::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt )
+{
+ for (iterator it = begin(); it != end(); ++it)
+ (*it)->UpdateInsertTab(rCxt);
+}
+
+void ScValidationDataList::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt )
+{
+ for (iterator it = begin(); it != end(); ++it)
+ (*it)->UpdateDeleteTab(rCxt);
+}
+
+void ScValidationDataList::UpdateMoveTab( sc::RefUpdateMoveTabContext& rCxt )
+{
+ for (iterator it = begin(); it != end(); ++it)
+ (*it)->UpdateMoveTab(rCxt);
+}
+
+ScValidationDataList::iterator ScValidationDataList::begin()
+{
+ return maData.begin();
+}
+
+ScValidationDataList::const_iterator ScValidationDataList::begin() const
+{
+ return maData.begin();
+}
+
+ScValidationDataList::iterator ScValidationDataList::end()
+{
+ return maData.end();
+}
+
+ScValidationDataList::const_iterator ScValidationDataList::end() const
+{
+ return maData.end();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/inc/addinhelpid.hxx b/sc/source/core/inc/addinhelpid.hxx
new file mode 100644
index 000000000..82967ecf9
--- /dev/null
+++ b/sc/source/core/inc/addinhelpid.hxx
@@ -0,0 +1,45 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <rtl/ustring.hxx>
+#include <rtl/string.hxx>
+
+struct ScUnoAddInHelpId;
+
+/** Generates help IDs for standard Calc AddIns. */
+class ScUnoAddInHelpIdGenerator
+{
+private:
+ const ScUnoAddInHelpId* pCurrHelpIds; /// Array of function names and help IDs.
+ sal_uInt32 nArrayCount; /// Count of array entries.
+
+public:
+ ScUnoAddInHelpIdGenerator() = delete;
+ ScUnoAddInHelpIdGenerator( std::u16string_view rServiceName );
+
+ /** Sets service name of the AddIn. Has to be done before requesting help IDs. */
+ void SetServiceName( std::u16string_view rServiceName );
+
+ /** @return The help ID of the function with given built-in name or 0 if not found. */
+ OString GetHelpId( const OUString& rFuncName ) const;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/inc/addinlis.hxx b/sc/source/core/inc/addinlis.hxx
new file mode 100644
index 000000000..ac3d2d64d
--- /dev/null
+++ b/sc/source/core/inc/addinlis.hxx
@@ -0,0 +1,84 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <memory>
+#include "adiasync.hxx"
+#include <com/sun/star/sheet/XResultListener.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <cppuhelper/implbase.hxx>
+
+namespace com::sun::star::sheet { class XVolatileResult; }
+namespace rtl { template <class reference_type> class Reference; }
+
+class ScDocument;
+
+class ScAddInListener final : public cppu::WeakImplHelper<
+ css::sheet::XResultListener,
+ css::lang::XServiceInfo >,
+ public SvtBroadcaster
+{
+private:
+ css::uno::Reference<css::sheet::XVolatileResult> xVolRes;
+ css::uno::Any aResult;
+ std::unique_ptr<ScAddInDocs> pDocs; // documents where this is used
+
+ static ::std::vector<rtl::Reference<ScAddInListener>> aAllListeners;
+
+ // always allocated via CreateListener
+ ScAddInListener( css::uno::Reference<css::sheet::XVolatileResult> const & xVR,
+ ScDocument* pD );
+
+public:
+ virtual ~ScAddInListener() override;
+
+ // create Listener and put it into global list
+ static ScAddInListener* CreateListener(
+ const css::uno::Reference<css::sheet::XVolatileResult>& xVR,
+ ScDocument* pDoc );
+
+ static ScAddInListener* Get( const css::uno::Reference<css::sheet::XVolatileResult>& xVR );
+
+ static void RemoveDocument( ScDocument* pDocument );
+
+ bool HasDocument( ScDocument* pDoc ) const
+ { return pDocs->find( pDoc ) != pDocs->end(); }
+
+ void AddDocument( ScDocument* pDoc )
+ { pDocs->insert( pDoc ); }
+
+ const css::uno::Any& GetResult() const
+ { return aResult; }
+
+ // XResultListener
+ virtual void SAL_CALL modified( const css::sheet::ResultEvent& aEvent ) override;
+
+ // XEventListener
+ virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/inc/adiasync.hxx b/sc/source/core/inc/adiasync.hxx
new file mode 100644
index 000000000..b693f12a2
--- /dev/null
+++ b/sc/source/core/inc/adiasync.hxx
@@ -0,0 +1,77 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <svl/broadcast.hxx>
+#include <set>
+#include <tools/solar.h>
+
+#include <callform.hxx>
+
+extern "C" {
+void CALLTYPE ScAddInAsyncCallBack( double& nHandle, void* pData );
+}
+
+class ScDocument;
+using ScAddInDocs = std::set<ScDocument*>;
+
+class ScAddInAsync final : public SvtBroadcaster
+{
+private:
+ union
+ {
+ double nVal; // current value
+ OUString* pStr;
+ };
+ std::unique_ptr<ScAddInDocs> pDocs; // List of using documents
+ LegacyFuncData* mpFuncData; // Pointer to data in collection
+ sal_uLong nHandle; // is casted from double to sal_uLong
+ ParamType meType; // result of type PTR_DOUBLE or PTR_STRING
+ bool bValid; // is value valid?
+
+public:
+ // cTor only if ScAddInAsync::Get fails.
+ // nIndex: Index from FunctionCollection
+ ScAddInAsync(sal_uLong nHandle, LegacyFuncData* pFuncData, ScDocument* pDoc);
+ virtual ~ScAddInAsync() override;
+ static ScAddInAsync* Get( sal_uLong nHandle );
+ static void CallBack( sal_uLong nHandle, void* pData );
+ static void RemoveDocument( ScDocument* pDocument );
+ bool IsValid() const { return bValid; }
+ ParamType GetType() const { return meType; }
+ double GetValue() const { return nVal; }
+ const OUString& GetString() const { return *pStr; }
+ bool HasDocument( ScDocument* pDoc ) const
+ { return pDocs->find( pDoc ) != pDocs->end(); }
+ void AddDocument( ScDocument* pDoc ) { pDocs->insert( pDoc ); }
+
+ // Comparators for PtrArrSort
+ bool operator< ( const ScAddInAsync& r ) const { return nHandle < r.nHandle; }
+};
+
+struct CompareScAddInAsync
+{
+ bool operator()( std::unique_ptr<ScAddInAsync> const& lhs, std::unique_ptr<ScAddInAsync> const& rhs ) const { return (*lhs)<(*rhs); }
+};
+using ScAddInAsyncs = std::set<std::unique_ptr<ScAddInAsync>, CompareScAddInAsync>;
+
+extern ScAddInAsyncs theAddInAsyncTbl; // in adiasync.cxx
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/inc/bcaslot.hxx b/sc/source/core/inc/bcaslot.hxx
new file mode 100644
index 000000000..c77a1173c
--- /dev/null
+++ b/sc/source/core/inc/bcaslot.hxx
@@ -0,0 +1,396 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <memory>
+#include <map>
+#include <unordered_set>
+
+#include <svl/broadcast.hxx>
+#include <svl/hint.hxx>
+#include <tools/solar.h>
+
+#include <document.hxx>
+#include <global.hxx>
+
+namespace sc { class ColumnSpanSet; }
+class ScHint;
+
+namespace sc {
+
+struct AreaListener
+{
+ ScRange maArea;
+ bool mbGroupListening;
+ SvtListener* mpListener;
+};
+
+}
+
+/**
+ Used in a Unique Associative Container.
+ */
+
+class ScBroadcastArea
+{
+private:
+ ScBroadcastArea* pUpdateChainNext;
+ SvtBroadcaster aBroadcaster;
+ ScRange aRange;
+ sal_uLong nRefCount;
+
+ bool mbInUpdateChain:1;
+ bool mbGroupListening:1;
+
+public:
+ ScBroadcastArea(const ScBroadcastArea&) = delete;
+ const ScBroadcastArea& operator=(const ScBroadcastArea&) = delete;
+
+ ScBroadcastArea( const ScRange& rRange );
+
+ SvtBroadcaster& GetBroadcaster() { return aBroadcaster; }
+ const SvtBroadcaster& GetBroadcaster() const { return aBroadcaster; }
+ void UpdateRange( const ScRange& rNewRange )
+ { aRange = rNewRange; }
+ const ScRange& GetRange() const { return aRange; }
+ void IncRef() { ++nRefCount; }
+ sal_uLong DecRef() { return nRefCount ? --nRefCount : 0; }
+ sal_uLong GetRef() const { return nRefCount; }
+ ScBroadcastArea* GetUpdateChainNext() const { return pUpdateChainNext; }
+ void SetUpdateChainNext( ScBroadcastArea* p ) { pUpdateChainNext = p; }
+ bool IsInUpdateChain() const { return mbInUpdateChain; }
+ void SetInUpdateChain( bool b ) { mbInUpdateChain = b; }
+
+ bool IsGroupListening() const { return mbGroupListening; }
+ void SetGroupListening( bool b ) { mbGroupListening = b; }
+
+ /** Equalness of this or range. */
+ inline bool operator==( const ScBroadcastArea & rArea ) const;
+};
+
+inline bool ScBroadcastArea::operator==( const ScBroadcastArea & rArea ) const
+{
+ return aRange == rArea.aRange && mbGroupListening == rArea.mbGroupListening;
+}
+
+struct ScBroadcastAreaEntry
+{
+ ScBroadcastArea* mpArea;
+ mutable bool mbErasure; ///< TRUE if marked for erasure in this set
+
+ ScBroadcastAreaEntry( ScBroadcastArea* p ) : mpArea( p), mbErasure( false) {}
+};
+
+struct ScBroadcastAreaHash
+{
+ size_t operator()( const ScBroadcastAreaEntry& rEntry ) const
+ {
+ return rEntry.mpArea->GetRange().hashArea() + static_cast<size_t>(rEntry.mpArea->IsGroupListening());
+ }
+};
+
+struct ScBroadcastAreaEqual
+{
+ bool operator()( const ScBroadcastAreaEntry& rEntry1, const ScBroadcastAreaEntry& rEntry2) const
+ {
+ return *rEntry1.mpArea == *rEntry2.mpArea;
+ }
+};
+
+typedef std::unordered_set< ScBroadcastAreaEntry, ScBroadcastAreaHash, ScBroadcastAreaEqual > ScBroadcastAreas;
+
+struct ScBroadcastAreaBulkHash
+{
+ size_t operator()( const ScBroadcastArea* p ) const
+ {
+ return reinterpret_cast<size_t>(p);
+ }
+};
+
+struct ScBroadcastAreaBulkEqual
+{
+ bool operator()( const ScBroadcastArea* p1, const ScBroadcastArea* p2) const
+ {
+ return p1 == p2;
+ }
+};
+
+typedef std::unordered_set< const ScBroadcastArea*, ScBroadcastAreaBulkHash,
+ ScBroadcastAreaBulkEqual > ScBroadcastAreasBulk;
+
+class ScBroadcastAreaSlotMachine;
+
+/// Collection of BroadcastAreas
+class ScBroadcastAreaSlot
+{
+private:
+ ScBroadcastAreas aBroadcastAreaTbl;
+ mutable ScBroadcastArea aTmpSeekBroadcastArea; // for FindBroadcastArea()
+ ScDocument* pDoc;
+ ScBroadcastAreaSlotMachine* pBASM;
+ bool mbInBroadcastIteration;
+
+ /**
+ * If true, the slot has at least one area broadcaster marked for removal.
+ * This flag is used only during broadcast iteration, to speed up
+ * iteration. Using this flag is cheaper than dereferencing each iterator
+ * and checking its own flag inside especially when no areas are marked
+ * for removal.
+ */
+ bool mbHasErasedArea;
+
+ ScBroadcastAreas::iterator FindBroadcastArea( const ScRange& rRange, bool bGroupListening );
+
+ /**
+ More hypothetical (memory would probably be doomed anyway) check
+ whether there would be an overflow when adding an area, setting the
+ proper state if so.
+
+ @return HardRecalcState::ETERNAL if a HardRecalcState is effective and
+ area is not to be added.
+ */
+ ScDocument::HardRecalcState CheckHardRecalcStateCondition() const;
+
+ /** Finally erase all areas pushed as to-be-erased. */
+ void FinallyEraseAreas();
+
+ static bool isMarkedErased( const ScBroadcastAreas::const_iterator& rIter )
+ {
+ return rIter->mbErasure;
+ }
+
+public:
+ ScBroadcastAreaSlot( ScDocument* pDoc,
+ ScBroadcastAreaSlotMachine* pBASM );
+ ~ScBroadcastAreaSlot();
+
+ /**
+ Only here new ScBroadcastArea objects are created, prevention of dupes.
+
+ @param rpArea
+ If NULL, a new ScBroadcastArea is created and assigned ton the
+ reference if a matching area wasn't found. If a matching area was
+ found, that is assigned. In any case, the SvtListener is added to
+ the broadcaster.
+
+ If not NULL then no listeners are started, only the area is
+ inserted and the reference count incremented. Effectively the same
+ as InsertListeningArea(), so use that instead.
+
+ @return
+ true if rpArea passed was NULL and ScBroadcastArea is newly
+ created.
+ */
+ bool StartListeningArea(
+ const ScRange& rRange, bool bGroupListening, SvtListener* pListener, ScBroadcastArea*& rpArea );
+
+ /**
+ Insert a ScBroadcastArea obtained via StartListeningArea() to
+ subsequent slots.
+ */
+ void InsertListeningArea( ScBroadcastArea* pArea );
+
+ void EndListeningArea(
+ const ScRange& rRange, bool bGroupListening, SvtListener* pListener, ScBroadcastArea*& rpArea );
+
+ bool AreaBroadcast( const ScRange& rRange, SfxHintId nHint );
+ bool AreaBroadcast( const ScHint& rHint );
+ void DelBroadcastAreasInRange( const ScRange& rRange );
+ void UpdateRemove( UpdateRefMode eUpdateRefMode,
+ const ScRange& rRange,
+ SCCOL nDx, SCROW nDy, SCTAB nDz );
+ void UpdateRemoveArea( ScBroadcastArea* pArea );
+ void UpdateInsert( ScBroadcastArea* pArea );
+
+ bool IsInBroadcastIteration() const { return mbInBroadcastIteration; }
+
+ /** Erase an area from set and delete it if last reference, or if
+ mbInBroadcastIteration is set push it to the vector of to-be-erased
+ areas instead.
+
+ Meant to be used internally and from ScBroadcastAreaSlotMachine only.
+ */
+ void EraseArea( ScBroadcastAreas::iterator& rIter );
+
+ void GetAllListeners(
+ const ScRange& rRange, std::vector<sc::AreaListener>& rListeners,
+ sc::AreaOverlapType eType, sc::ListenerGroupType eGroup );
+
+#if DEBUG_AREA_BROADCASTER
+ void Dump() const;
+#endif
+};
+
+/**
+ BroadcastAreaSlots and their management, once per document.
+ */
+
+class ScBroadcastAreaSlotMachine
+{
+private:
+ typedef std::map<ScBroadcastArea*, sc::ColumnSpanSet> BulkGroupAreasType;
+
+ /**
+ Slot offset arrangement of columns and rows, once per sheet.
+
+ +---+---+
+ | 0 | 3 |
+ +---+---+
+ | 1 | 4 |
+ +---+---+
+ | 2 | 5 |
+ +---+---+
+ */
+
+ class TableSlots
+ {
+ public:
+ TableSlots(SCSIZE nBcaSlots);
+ ~TableSlots();
+ ScBroadcastAreaSlot** getSlots() { return ppSlots.get(); }
+
+ private:
+ SCSIZE mnBcaSlots;
+ std::unique_ptr<ScBroadcastAreaSlot*[]> ppSlots;
+
+ TableSlots( const TableSlots& ) = delete;
+ TableSlots& operator=( const TableSlots& ) = delete;
+ };
+
+ typedef ::std::map< SCTAB, std::unique_ptr<TableSlots> > TableSlotsMap;
+
+ typedef ::std::vector< ::std::pair< ScBroadcastAreaSlot*, ScBroadcastAreas::iterator > > AreasToBeErased;
+
+private:
+ struct ScSlotData
+ {
+ SCROW nStartRow; // first row of this segment
+ SCROW nStopRow; // first row of next segment
+ SCSIZE nSliceRow; // row slice size in this segment
+ SCSIZE nCumulatedRow; // cumulated slots of previous segments (previous rows)
+ SCROW nStartCol; // first column of this segment
+ SCROW nStopCol; // first column of next segment
+ SCSIZE nSliceCol; // column slice size in this segment
+ SCSIZE nCumulatedCol; // cumulated slots of previous segments (previous columns)
+
+ ScSlotData( SCROW r1, SCROW r2, SCSIZE sr, SCSIZE cr, SCCOL c1, SCCOL c2, SCSIZE sc, SCSIZE cc )
+ : nStartRow(r1)
+ , nStopRow(r2)
+ , nSliceRow(sr)
+ , nCumulatedRow(cr)
+ , nStartCol(c1)
+ , nStopCol(c2)
+ , nSliceCol(sc)
+ , nCumulatedCol(cc) {}
+ };
+ typedef ::std::vector< ScSlotData > ScSlotDistribution;
+ ScSlotDistribution maSlotDistribution;
+ SCSIZE mnBcaSlotsCol;
+ SCSIZE mnBcaSlots;
+ ScBroadcastAreasBulk aBulkBroadcastAreas;
+ BulkGroupAreasType m_BulkGroupAreas;
+ TableSlotsMap aTableSlotsMap;
+ AreasToBeErased maAreasToBeErased;
+ std::unique_ptr<SvtBroadcaster> pBCAlways; // for the RC_ALWAYS special range
+ ScDocument *pDoc;
+ ScBroadcastArea *pUpdateChain;
+ ScBroadcastArea *pEOUpdateChain;
+ sal_uInt32 nInBulkBroadcast;
+
+ inline SCSIZE ComputeSlotOffset( const ScAddress& rAddress ) const;
+ void ComputeAreaPoints( const ScRange& rRange,
+ SCSIZE& nStart, SCSIZE& nEnd,
+ SCSIZE& nRowBreak ) const;
+#ifdef DBG_UTIL
+ void DoChecks();
+#endif
+
+public:
+ ScBroadcastAreaSlotMachine( ScDocument* pDoc );
+ ~ScBroadcastAreaSlotMachine();
+ void StartListeningArea(
+ const ScRange& rRange, bool bGroupListening, SvtListener* pListener );
+
+ void EndListeningArea(
+ const ScRange& rRange, bool bGroupListening, SvtListener* pListener );
+
+ bool AreaBroadcast( const ScRange& rRange, SfxHintId nHint );
+ bool AreaBroadcast( const ScHint& rHint ) const;
+ // return: at least one broadcast occurred
+ void DelBroadcastAreasInRange( const ScRange& rRange );
+ void UpdateBroadcastAreas( UpdateRefMode eUpdateRefMode,
+ const ScRange& rRange,
+ SCCOL nDx, SCROW nDy, SCTAB nDz );
+ void EnterBulkBroadcast();
+ void LeaveBulkBroadcast( SfxHintId nHintId );
+ bool InsertBulkArea( const ScBroadcastArea* p );
+
+ void InsertBulkGroupArea( ScBroadcastArea* pArea, const ScRange& rRange );
+ void RemoveBulkGroupArea( ScBroadcastArea* pArea );
+ bool BulkBroadcastGroupAreas( SfxHintId nHintId );
+
+ /// @return: how many removed
+ size_t RemoveBulkArea( const ScBroadcastArea* p );
+ void SetUpdateChain( ScBroadcastArea* p ) { pUpdateChain = p; }
+ ScBroadcastArea* GetEOUpdateChain() const { return pEOUpdateChain; }
+ void SetEOUpdateChain( ScBroadcastArea* p ) { pEOUpdateChain = p; }
+ bool IsInBulkBroadcast() const { return nInBulkBroadcast > 0; }
+
+ // only for ScBroadcastAreaSlot
+ void PushAreaToBeErased( ScBroadcastAreaSlot* pSlot,
+ ScBroadcastAreas::iterator& rIter );
+ // only for ScBroadcastAreaSlot
+ void FinallyEraseAreas( ScBroadcastAreaSlot* pSlot );
+
+ std::vector<sc::AreaListener> GetAllListeners(
+ const ScRange& rRange, sc::AreaOverlapType eType,
+ sc::ListenerGroupType eGroup = sc::ListenerGroupType::Both );
+
+#if DEBUG_AREA_BROADCASTER
+ void Dump() const;
+#endif
+};
+
+class ScBulkBroadcast
+{
+ ScBroadcastAreaSlotMachine* pBASM;
+ SfxHintId mnHintId;
+
+ ScBulkBroadcast(ScBulkBroadcast const &) = delete;
+ ScBulkBroadcast(ScBulkBroadcast &&) = delete;
+ ScBulkBroadcast & operator =(ScBulkBroadcast const &) = delete;
+ ScBulkBroadcast & operator =(ScBulkBroadcast &&) = delete;
+
+public:
+ explicit ScBulkBroadcast( ScBroadcastAreaSlotMachine* p, SfxHintId nHintId ) :
+ pBASM(p),
+ mnHintId(nHintId)
+ {
+ if (pBASM)
+ pBASM->EnterBulkBroadcast();
+ }
+ ~ScBulkBroadcast() COVERITY_NOEXCEPT_FALSE
+ {
+ if (pBASM)
+ pBASM->LeaveBulkBroadcast( mnHintId );
+ }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/inc/cellkeytranslator.hxx b/sc/source/core/inc/cellkeytranslator.hxx
new file mode 100644
index 000000000..1e42d1cf1
--- /dev/null
+++ b/sc/source/core/inc/cellkeytranslator.hxx
@@ -0,0 +1,83 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <formula/opcode.hxx>
+#include <unotools/transliterationwrapper.hxx>
+#include <vector>
+#include <memory>
+#include <unordered_map>
+
+namespace com::sun::star::lang
+{
+struct Locale;
+}
+
+struct TransItem;
+
+struct ScCellKeyword
+{
+ const char* mpName;
+ OpCode meOpCode;
+ const css::lang::Locale& mrLocale;
+
+ ScCellKeyword(const char* pName, OpCode eOpCode, const css::lang::Locale& rLocale);
+};
+
+typedef std::unordered_map<OUString, ::std::vector<ScCellKeyword>> ScCellKeywordHashMap;
+
+/** Translate cell function keywords.
+
+ This class provides a convenient way to translate a string keyword used as
+ a cell function argument. Since Calc's built-in cell functions don't
+ localize string keywords, this class is used mainly to deal with an Excel
+ document where string names may be localized.
+
+ To use, simply call the
+
+ ScCellKeywordTranslator::transKeyword(...)
+
+ function.
+
+ Note that when the locale and/or the opcode is specified, the function
+ tries to find a string with matching locale and/or opcode. But when it
+ fails to find one that satisfies the specified locale and/or opcode, it
+ returns a translated string with non-matching locale and/or opcode if
+ available. */
+class ScCellKeywordTranslator
+{
+public:
+ static void transKeyword(OUString& rName, const css::lang::Locale* pLocale, OpCode eOpCode);
+ ~ScCellKeywordTranslator();
+
+private:
+ ScCellKeywordTranslator();
+
+ void init();
+ void addToMap(const OUString& rKey, const char* pName, const css::lang::Locale& rLocale,
+ OpCode eOpCode);
+ void addToMap(const TransItem* pItems, const css::lang::Locale& rLocale);
+
+ static ::std::unique_ptr<ScCellKeywordTranslator> spInstance;
+ ScCellKeywordHashMap maStringNameMap;
+ ::utl::TransliterationWrapper maTransWrapper;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/inc/ddelink.hxx b/sc/source/core/inc/ddelink.hxx
new file mode 100644
index 000000000..6baa7cee4
--- /dev/null
+++ b/sc/source/core/inc/ddelink.hxx
@@ -0,0 +1,84 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <sfx2/lnkbase.hxx>
+#include <svl/broadcast.hxx>
+#include <types.hxx>
+
+namespace com::sun::star::uno { class Any; }
+
+class ScDocument;
+class ScMultipleReadHeader;
+class ScMultipleWriteHeader;
+class SvStream;
+
+class ScDdeLink final : public ::sfx2::SvBaseLink, public SvtBroadcaster
+{
+private:
+static bool bIsInUpdate;
+
+ ScDocument& rDoc;
+
+ OUString aAppl; // connection/ link data
+ OUString aTopic;
+ OUString aItem;
+ sal_uInt8 nMode; // number format mode
+
+ bool bNeedUpdate; // is set, if update was not possible
+
+ ScMatrixRef pResult;
+
+public:
+
+ ScDdeLink( ScDocument& rD,
+ const OUString& rA, const OUString& rT, const OUString& rI,
+ sal_uInt8 nM );
+ ScDdeLink( ScDocument& rD, SvStream& rStream, ScMultipleReadHeader& rHdr );
+ ScDdeLink( ScDocument& rD, const ScDdeLink& rOther );
+ virtual ~ScDdeLink() override;
+
+ void Store( SvStream& rStream, ScMultipleWriteHeader& rHdr ) const;
+
+ // SvBaseLink override:
+ virtual ::sfx2::SvBaseLink::UpdateResult DataChanged(
+ const OUString& rMimeType, const css::uno::Any & rValue ) override;
+
+ // SvtBroadcaster override:
+ virtual void ListenersGone() override;
+
+ // for interpreter:
+
+ const ScMatrix* GetResult() const;
+ void SetResult( const ScMatrixRef& pRes );
+
+ const OUString& GetAppl() const { return aAppl; }
+ const OUString& GetTopic() const { return aTopic; }
+ const OUString& GetItem() const { return aItem; }
+ sal_uInt8 GetMode() const { return nMode; }
+
+ void TryUpdate();
+
+ bool NeedsUpdate() const { return bNeedUpdate; }
+
+ static bool IsInUpdate() { return bIsInUpdate; }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/inc/doubleref.hxx b/sc/source/core/inc/doubleref.hxx
new file mode 100644
index 000000000..b8cb084e7
--- /dev/null
+++ b/sc/source/core/inc/doubleref.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <memory>
+
+#include <address.hxx>
+#include <types.hxx>
+
+class ScDocument;
+struct ScDBQueryParamBase;
+struct ScQueryParamBase;
+enum class FormulaError : sal_uInt16;
+
+/**
+ * Base class for abstracting range data backends for database functions.
+ */
+class ScDBRangeBase
+{
+public:
+ ScDBRangeBase() = delete;
+
+ virtual ~ScDBRangeBase() = 0;
+
+ bool fillQueryEntries(ScQueryParamBase* pParam, const ScDBRangeBase* pDBRef) const;
+
+ virtual SCCOL getColSize() const = 0;
+ virtual SCROW getRowSize() const = 0;
+ virtual SCSIZE getVisibleDataCellCount() const = 0;
+
+ /**
+ * Get a string value of a specified cell position. Note that the
+ * position of the upper left cell of the range is always (0, 0) even if
+ * the reference type is of internal range.
+ *
+ * @param nCol column position (0 to column size-1)
+ * @param nRow row position (0 to row size-1)
+ */
+ virtual OUString getString(SCCOL nCol, SCROW nRow) const = 0;
+
+ virtual SCCOL getFirstFieldColumn() const = 0;
+
+ /**
+ * Get a <i>0-based</i> column index that corresponds with the passed field
+ * index. Note that the field index passed as the 1st parameter is
+ * <i>1-based.</i>
+ *
+ * @param nIndex 1-based field index.
+ *
+ * @return 0-based column index
+ */
+ virtual SCCOL findFieldColumn(SCCOL nIndex) const = 0;
+ virtual SCCOL findFieldColumn(const OUString& rStr, FormulaError* pErr = nullptr) const = 0;
+ virtual std::unique_ptr<ScDBQueryParamBase>
+ createQueryParam(const ScDBRangeBase* pQueryRef) const = 0;
+ virtual bool isRangeEqual(const ScRange& rRange) const = 0;
+
+protected:
+ ScDBRangeBase(ScDocument* pDoc);
+ ScDocument* getDoc() const { return mpDoc; }
+
+ /**
+ * Populate query options that are always the same for all database
+ * queries.
+ */
+ static void fillQueryOptions(ScQueryParamBase* pParam);
+
+private:
+ ScDocument* mpDoc;
+};
+
+class ScDBInternalRange final : public ScDBRangeBase
+{
+public:
+ explicit ScDBInternalRange(ScDocument* pDoc, const ScRange& rRange);
+ virtual ~ScDBInternalRange() override;
+
+ const ScRange& getRange() const { return maRange; }
+
+ virtual SCCOL getColSize() const override;
+ virtual SCROW getRowSize() const override;
+ virtual SCSIZE getVisibleDataCellCount() const override;
+
+ /**
+ * Get a string value of a specified cell position. Note that the
+ * position of the upper left cell of the range is always (0, 0) even if
+ * the reference type is of internal range.
+ *
+ * @param nCol column position (0 to column size-1)
+ * @param nRow row position (0 to row size-1)
+ */
+ virtual OUString getString(SCCOL nCol, SCROW nRow) const override;
+
+ virtual SCCOL getFirstFieldColumn() const override;
+ /**
+ * Get a <i>0-based</i> column index that corresponds with the passed field
+ * index. Note that the field index passed as the 1st parameter is
+ * <i>1-based.</i>
+ *
+ * @param nIndex 1-based field index.
+ *
+ * @return 0-based column index
+ */
+ virtual SCCOL findFieldColumn(SCCOL nIndex) const override;
+ virtual SCCOL findFieldColumn(const OUString& rStr,
+ FormulaError* pErr = nullptr) const override;
+ virtual std::unique_ptr<ScDBQueryParamBase>
+ createQueryParam(const ScDBRangeBase* pQueryRef) const override;
+ virtual bool isRangeEqual(const ScRange& rRange) const override;
+
+private:
+ ScRange maRange;
+};
+
+class ScDBExternalRange final : public ScDBRangeBase
+{
+public:
+ explicit ScDBExternalRange(ScDocument* pDoc, const ScMatrixRef& pMat);
+ virtual ~ScDBExternalRange() override;
+
+ virtual SCCOL getColSize() const override;
+ virtual SCROW getRowSize() const override;
+ virtual SCSIZE getVisibleDataCellCount() const override;
+
+ /**
+ * Get a string value of a specified cell position. Note that the
+ * position of the upper left cell of the range is always (0, 0) even if
+ * the reference type is of internal range.
+ *
+ * @param nCol column position (0 to column size-1)
+ * @param nRow row position (0 to row size-1)
+ */
+ virtual OUString getString(SCCOL nCol, SCROW nRow) const override;
+
+ virtual SCCOL getFirstFieldColumn() const override;
+
+ /**
+ * Get a <i>0-based</i> column index that corresponds with the passed field
+ * index. Note that the field index passed as the 1st parameter is
+ * <i>1-based.</i>
+ *
+ * @param nIndex 1-based field index.
+ *
+ * @return 0-based column index
+ */
+ virtual SCCOL findFieldColumn(SCCOL nIndex) const override;
+ virtual SCCOL findFieldColumn(const OUString& rStr,
+ FormulaError* pErr = nullptr) const override;
+ virtual std::unique_ptr<ScDBQueryParamBase>
+ createQueryParam(const ScDBRangeBase* pQueryRef) const override;
+ virtual bool isRangeEqual(const ScRange& rRange) const override;
+
+private:
+ const ScMatrixRef mpMatrix;
+ SCCOL mnCols;
+ SCROW mnRows;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/inc/formulagroupcl.hxx b/sc/source/core/inc/formulagroupcl.hxx
new file mode 100644
index 000000000..586b21f4c
--- /dev/null
+++ b/sc/source/core/inc/formulagroupcl.hxx
@@ -0,0 +1,29 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include <formulagroup.hxx>
+
+namespace sc::opencl {
+
+class FormulaGroupInterpreterOpenCL final : public FormulaGroupInterpreter
+{
+public:
+ FormulaGroupInterpreterOpenCL();
+ virtual ~FormulaGroupInterpreterOpenCL() override;
+
+ virtual ScMatrixRef inverseMatrix( const ScMatrix& rMat ) override;
+ virtual bool interpret( ScDocument& rDoc, const ScAddress& rTopPos,
+ ScFormulaCellGroupRef& xGroup, ScTokenArray& rCode ) override;
+};
+
+} // namespace sc::opencl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/inc/grouptokenconverter.hxx b/sc/source/core/inc/grouptokenconverter.hxx
new file mode 100644
index 000000000..0d2f02227
--- /dev/null
+++ b/sc/source/core/inc/grouptokenconverter.hxx
@@ -0,0 +1,38 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include <types.hxx>
+#include <formulalogger.hxx>
+
+class ScDocument;
+class ScFormulaCell;
+class ScTokenArray;
+namespace sc { struct FormulaGroupContext; }
+
+class ScGroupTokenConverter
+{
+ ScTokenArray& mrGroupTokens;
+ ScDocument& mrDoc;
+ std::shared_ptr<sc::FormulaGroupContext> mxFormulaGroupContext;
+ const ScFormulaCell& mrCell;
+ const ScAddress& mrPos;
+
+ bool isSelfReferenceRelative(const ScAddress& rRefPos, SCROW nRelRow);
+ bool isSelfReferenceAbsolute(const ScAddress& rRefPos);
+ SCROW trimLength(SCTAB nTab, SCCOL nCol1, SCCOL nCol2, SCROW nRow, SCROW nRowLen);
+
+public:
+ ScGroupTokenConverter(ScTokenArray& rGroupTokens, ScDocument& rDoc, const ScFormulaCell& rCell, const ScAddress& rPos);
+
+ bool convert( const ScTokenArray& rCode, sc::FormulaLogger::GroupScope& rScope );
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/inc/interpre.hxx b/sc/source/core/inc/interpre.hxx
new file mode 100644
index 000000000..c1fc5f458
--- /dev/null
+++ b/sc/source/core/inc/interpre.hxx
@@ -0,0 +1,1165 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <rtl/math.hxx>
+#include <rtl/ustring.hxx>
+#include <unotools/textsearch.hxx>
+#include <formula/errorcodes.hxx>
+#include <formula/tokenarray.hxx>
+#include <types.hxx>
+#include <externalrefmgr.hxx>
+#include <calcconfig.hxx>
+#include <token.hxx>
+#include <math.hxx>
+#include <kahan.hxx>
+#include "parclass.hxx"
+
+#include <map>
+#include <memory>
+#include <vector>
+#include <limits>
+#include <ostream>
+
+namespace sfx2 { class LinkManager; }
+
+class ScDocument;
+class SbxVariable;
+class ScFormulaCell;
+class ScDBRangeBase;
+struct ScQueryParam;
+struct ScDBQueryParamBase;
+struct ScQueryEntry;
+
+struct ScSingleRefData;
+struct ScComplexRefData;
+struct ScInterpreterContext;
+
+class ScJumpMatrix;
+struct ScRefCellValue;
+
+namespace sc {
+
+struct CompareOptions;
+
+struct ParamIfsResult
+{
+ KahanSum mfSum = 0.0;
+ double mfCount = 0.0;
+ double mfMin = std::numeric_limits<double>::max();
+ double mfMax = std::numeric_limits<double>::lowest();
+};
+
+template<typename charT, typename traits>
+inline std::basic_ostream<charT, traits> & operator <<(std::basic_ostream<charT, traits> & stream, const ParamIfsResult& rRes)
+{
+ stream << "{" <<
+ "sum=" << rRes.mfSum.get() << "," <<
+ "count=" << rRes.mfCount << "," <<
+ "min=" << rRes.mfMin << "," <<
+ "max=" << rRes.mfMax << "," <<
+ "}";
+
+ return stream;
+}
+
+}
+
+namespace svl {
+
+class SharedStringPool;
+
+}
+
+/// Arbitrary 256MB result string length limit.
+constexpr sal_Int32 kScInterpreterMaxStrLen = SAL_MAX_INT32 / 8;
+
+#define MAXSTACK (4096 / sizeof(formula::FormulaToken*))
+
+class ScTokenStack
+{
+public:
+ const formula::FormulaToken* pPointer[ MAXSTACK ];
+};
+
+enum ScIterFunc {
+ ifSUM, // Add up
+ ifSUMSQ, // Sums of squares
+ ifPRODUCT, // Product
+ ifAVERAGE, // Average
+ ifCOUNT, // Count Values
+ ifCOUNT2, // Count Values (not empty)
+ ifMIN, // Minimum
+ ifMAX // Maximum
+};
+
+enum ScIterFuncIf
+{
+ ifSUMIF, // Conditional sum
+ ifAVERAGEIF // Conditional average
+};
+
+enum ScETSType
+{
+ etsAdd,
+ etsMult,
+ etsSeason,
+ etsPIAdd,
+ etsPIMult,
+ etsStatAdd,
+ etsStatMult
+};
+
+struct FormulaTokenRef_less
+{
+ bool operator () ( const formula::FormulaConstTokenRef& r1, const formula::FormulaConstTokenRef& r2 ) const
+ { return r1.get() < r2.get(); }
+};
+typedef ::std::map< const formula::FormulaConstTokenRef, formula::FormulaConstTokenRef, FormulaTokenRef_less> ScTokenMatrixMap;
+
+class ScInterpreter
+{
+ // distribution function objects need the GetxxxDist methods
+ friend class ScGammaDistFunction;
+ friend class ScBetaDistFunction;
+ friend class ScTDistFunction;
+ friend class ScFDistFunction;
+ friend class ScChiDistFunction;
+ friend class ScChiSqDistFunction;
+
+public:
+ static void SetGlobalConfig(const ScCalcConfig& rConfig);
+ static const ScCalcConfig& GetGlobalConfig();
+
+ static void GlobalExit(); // called by ScGlobal::Clear()
+
+ /** Detect if string should be used as regular expression or wildcard
+ expression or literal string.
+ */
+ static utl::SearchParam::SearchType DetectSearchType(std::u16string_view rStr, const ScDocument& rDoc );
+
+ /// Fail safe division, returning a FormulaError::DivisionByZero coded into a double
+ /// if denominator is 0.0
+ static inline double div( const double& fNumerator, const double& fDenominator );
+
+ ScMatrixRef GetNewMat(SCSIZE nC, SCSIZE nR, bool bEmpty = false);
+
+ ScMatrixRef GetNewMat(SCSIZE nC, SCSIZE nR, const std::vector<double>& rValues);
+
+ enum VolatileType {
+ VOLATILE,
+ VOLATILE_MACRO,
+ NOT_VOLATILE
+ };
+
+ VolatileType GetVolatileType() const { return meVolatileType;}
+
+private:
+ static ScCalcConfig& GetOrCreateGlobalConfig();
+ static ScCalcConfig *mpGlobalConfig;
+
+ static thread_local std::unique_ptr<ScTokenStack> pGlobalStack;
+ static thread_local bool bGlobalStackInUse;
+
+ ScCalcConfig maCalcConfig;
+ formula::FormulaTokenIterator aCode;
+ ScAddress aPos;
+ ScTokenArray* pArr;
+ ScInterpreterContext& mrContext;
+ ScDocument& mrDoc;
+ sfx2::LinkManager* mpLinkManager;
+ svl::SharedStringPool& mrStrPool;
+ formula::FormulaConstTokenRef xResult;
+ ScJumpMatrix* pJumpMatrix; // currently active array condition, if any
+ ScTokenMatrixMap maTokenMatrixMap; // map FormulaToken* to formula::FormulaTokenRef if in array condition
+ ScFormulaCell* pMyFormulaCell; // the cell of this formula expression
+ SvNumberFormatter* pFormatter;
+
+ const formula::FormulaToken* pCur; // current token
+ ScTokenStack* pStackObj; // contains the stacks
+ const formula::FormulaToken ** pStack; // the current stack
+ FormulaError nGlobalError; // global (local to this formula expression) error
+ sal_uInt16 sp; // stack pointer
+ sal_uInt16 maxsp; // the maximal used stack pointer
+ sal_uInt32 nFuncFmtIndex; // NumberFormatIndex of a function
+ sal_uInt32 nCurFmtIndex; // current NumberFormatIndex
+ sal_uInt32 nRetFmtIndex; // NumberFormatIndex of an expression, if any
+ SvNumFormatType nFuncFmtType; // NumberFormatType of a function
+ SvNumFormatType nCurFmtType; // current NumberFormatType
+ SvNumFormatType nRetFmtType; // NumberFormatType of an expression
+ FormulaError mnStringNoValueError; // the error set in ConvertStringToValue() if no value
+ SubtotalFlags mnSubTotalFlags; // flags for subtotal and aggregate functions
+ sal_uInt8 cPar; // current count of parameters
+ bool bCalcAsShown; // precision as shown
+ bool bMatrixFormula; // formula cell is a matrix formula
+
+ VolatileType meVolatileType;
+
+ void MakeMatNew(ScMatrixRef& rMat, SCSIZE nC, SCSIZE nR);
+
+ /// Merge global and document specific settings.
+ void MergeCalcConfig();
+
+ // nMust <= nAct <= nMax ? ok : PushError
+ inline bool MustHaveParamCount( short nAct, short nMust );
+ inline bool MustHaveParamCount( short nAct, short nMust, short nMax );
+ inline bool MustHaveParamCountMin( short nAct, short nMin );
+ inline bool MustHaveParamCountMinWithStackCheck( short nAct, short nMin );
+ void PushParameterExpected();
+ void PushIllegalParameter();
+ void PushIllegalArgument();
+ void PushNoValue();
+ void PushNA();
+
+ // Functions for accessing a document
+
+ void ReplaceCell( ScAddress& ); // for TableOp
+ bool IsTableOpInRange( const ScRange& );
+ sal_uInt32 GetCellNumberFormat( const ScAddress& rPos, ScRefCellValue& rCell );
+ double ConvertStringToValue( const OUString& );
+public:
+ static double ScGetGCD(double fx, double fy);
+ /** For matrix back calls into the current interpreter.
+ Uses rError instead of nGlobalError and rCurFmtType instead of nCurFmtType. */
+ double ConvertStringToValue( const OUString&, FormulaError& rError, SvNumFormatType& rCurFmtType );
+private:
+ double GetCellValue( const ScAddress&, ScRefCellValue& rCell );
+ double GetCellValueOrZero( const ScAddress&, ScRefCellValue& rCell );
+ double GetValueCellValue( const ScAddress&, double fOrig );
+ void GetCellString( svl::SharedString& rStr, ScRefCellValue& rCell );
+ static FormulaError GetCellErrCode( const ScRefCellValue& rCell );
+
+ bool CreateDoubleArr(SCCOL nCol1, SCROW nRow1, SCTAB nTab1,
+ SCCOL nCol2, SCROW nRow2, SCTAB nTab2, sal_uInt8* pCellArr);
+ bool CreateStringArr(SCCOL nCol1, SCROW nRow1, SCTAB nTab1,
+ SCCOL nCol2, SCROW nRow2, SCTAB nTab2, sal_uInt8* pCellArr);
+ bool CreateCellArr(SCCOL nCol1, SCROW nRow1, SCTAB nTab1,
+ SCCOL nCol2, SCROW nRow2, SCTAB nTab2, sal_uInt8* pCellArr);
+
+ // Stack operations
+
+ /** Does substitute with formula::FormulaErrorToken in case nGlobalError is set and the token
+ passed is not formula::FormulaErrorToken.
+ Increments RefCount of the original token if not substituted. */
+ void Push( const formula::FormulaToken& r );
+
+ /** Does not substitute with formula::FormulaErrorToken in case nGlobalError is set.
+ Used to push RPN tokens or from within Push() or tokens that are already
+ explicit formula::FormulaErrorToken. Increments RefCount. */
+ void PushWithoutError( const formula::FormulaToken& r );
+
+ /** Does substitute with formula::FormulaErrorToken in case nGlobalError is set and the token
+ passed is not formula::FormulaErrorToken.
+ Increments RefCount of the original token if not substituted.
+ ATTENTION! The token had to be allocated with `new' and must not be used
+ after this call if no RefCount was set because possibly it gets immediately
+ deleted in case of a FormulaError::StackOverflow or if substituted with formula::FormulaErrorToken! */
+ void PushTempToken( formula::FormulaToken* );
+
+ /** Pushes the token or substitutes with formula::FormulaErrorToken in case
+ nGlobalError is set and the token passed is not formula::FormulaErrorToken.
+ Increments RefCount of the original token if not substituted. */
+ void PushTokenRef( const formula::FormulaConstTokenRef& );
+
+ /** Does not substitute with formula::FormulaErrorToken in case nGlobalError is set.
+ Used to push tokens from within PushTempToken() or tokens that are already
+ explicit formula::FormulaErrorToken. Increments RefCount.
+ ATTENTION! The token had to be allocated with `new' and must not be used
+ after this call if no RefCount was set because possibly it gets immediately
+ decremented again and thus deleted in case of a FormulaError::StackOverflow! */
+ void PushTempTokenWithoutError( const formula::FormulaToken* );
+
+ /** If nGlobalError is set push formula::FormulaErrorToken.
+ If nGlobalError is not set do nothing.
+ Used in PushTempToken() and alike to simplify handling.
+ @return: <TRUE/> if nGlobalError. */
+ bool IfErrorPushError()
+ {
+ if (nGlobalError != FormulaError::NONE)
+ {
+ PushTempTokenWithoutError( new formula::FormulaErrorToken( nGlobalError));
+ return true;
+ }
+ return false;
+ }
+
+ /** Obtain cell result / content from address and push as temp token.
+
+ @param bDisplayEmptyAsString
+ is passed to ScEmptyCell in case of an empty cell result.
+
+ @param pRetTypeExpr
+ @param pRetIndexExpr
+ Obtain number format and type if _both_, type and index pointer,
+ are not NULL.
+
+ @param bFinalResult
+ If TRUE, only a standard FormulaDoubleToken is pushed.
+ If FALSE, PushDouble() is used that may push either a
+ FormulaDoubleToken or a FormulaTypedDoubleToken.
+ */
+ void PushCellResultToken( bool bDisplayEmptyAsString, const ScAddress & rAddress,
+ SvNumFormatType * pRetTypeExpr, sal_uInt32 * pRetIndexExpr, bool bFinalResult = false );
+
+ formula::FormulaConstTokenRef PopToken();
+ void Pop();
+ void PopError();
+ double PopDouble();
+ svl::SharedString PopString();
+ void ValidateRef( const ScSingleRefData & rRef );
+ void ValidateRef( const ScComplexRefData & rRef );
+ void ValidateRef( const ScRefList & rRefList );
+ void SingleRefToVars( const ScSingleRefData & rRef, SCCOL & rCol, SCROW & rRow, SCTAB & rTab );
+ void PopSingleRef( ScAddress& );
+ void PopSingleRef(SCCOL& rCol, SCROW &rRow, SCTAB& rTab);
+ void DoubleRefToRange( const ScComplexRefData&, ScRange&, bool bDontCheckForTableOp = false );
+ /** If formula::StackVar formula::svDoubleRef pop ScDoubleRefToken and return values of
+ ScComplexRefData.
+ Else if StackVar svRefList return values of the ScComplexRefData where
+ rRefInList is pointing to. rRefInList is incremented. If rRefInList was the
+ last element in list pop ScRefListToken and set rRefInList to 0, else
+ rParam is incremented (!) to allow usage as in
+ while(nParamCount--) PopDoubleRef(aRange,nParamCount,nRefInList);
+ */
+ void PopDoubleRef( ScRange & rRange, short & rParam, size_t & rRefInList );
+ void PopDoubleRef( ScRange&, bool bDontCheckForTableOp = false );
+ void DoubleRefToVars( const formula::FormulaToken* p,
+ SCCOL& rCol1, SCROW &rRow1, SCTAB& rTab1,
+ SCCOL& rCol2, SCROW &rRow2, SCTAB& rTab2 );
+ ScDBRangeBase* PopDBDoubleRef();
+ void PopDoubleRef(SCCOL& rCol1, SCROW &rRow1, SCTAB& rTab1,
+ SCCOL& rCol2, SCROW &rRow2, SCTAB& rTab2 );
+ // peek double ref data
+ const ScComplexRefData* GetStackDoubleRef(size_t rRefInList = 0);
+
+ void PopExternalSingleRef(sal_uInt16& rFileId, OUString& rTabName, ScSingleRefData& rRef);
+
+ /** Guarantees that nGlobalError is set if rToken could not be obtained. */
+ void PopExternalSingleRef(ScExternalRefCache::TokenRef& rToken, ScExternalRefCache::CellFormat* pFmt = nullptr);
+
+ /** Guarantees that nGlobalError is set if rToken could not be obtained. */
+ void PopExternalSingleRef(sal_uInt16& rFileId, OUString& rTabName, ScSingleRefData& rRef,
+ ScExternalRefCache::TokenRef& rToken, ScExternalRefCache::CellFormat* pFmt = nullptr);
+
+ void PopExternalDoubleRef(sal_uInt16& rFileId, OUString& rTabName, ScComplexRefData& rRef);
+ void PopExternalDoubleRef(ScExternalRefCache::TokenArrayRef& rArray);
+ void PopExternalDoubleRef(ScMatrixRef& rMat);
+ void GetExternalDoubleRef(sal_uInt16 nFileId, const OUString& rTabName, const ScComplexRefData& aData, ScExternalRefCache::TokenArrayRef& rArray);
+ bool PopDoubleRefOrSingleRef( ScAddress& rAdr );
+ void PopDoubleRefPushMatrix();
+ void PopRefListPushMatrixOrRef();
+ // If MatrixFormula: convert svDoubleRef to svMatrix, create JumpMatrix.
+ // Else convert area reference parameters marked as ForceArray to array.
+ // Returns true if JumpMatrix created.
+ bool ConvertMatrixParameters();
+ // If MatrixFormula: ConvertMatrixJumpConditionToMatrix()
+ inline void MatrixJumpConditionToMatrix();
+ // For MatrixFormula (preconditions already checked by
+ // MatrixJumpConditionToMatrix()): convert svDoubleRef to svMatrix, or if
+ // JumpMatrix currently in effect convert also other types to svMatrix so
+ // another JumpMatrix will be created by jump commands.
+ void ConvertMatrixJumpConditionToMatrix();
+ // If MatrixFormula or ForceArray: ConvertMatrixParameters()
+ inline bool MatrixParameterConversion();
+ // If MatrixFormula or ForceArray. Can be used within spreadsheet functions
+ // that do not depend on the formula cell's matrix size, for which only
+ // bMatrixFormula can be used.
+ inline bool IsInArrayContext() const;
+ ScMatrixRef PopMatrix();
+ sc::RangeMatrix PopRangeMatrix();
+ void QueryMatrixType(const ScMatrixRef& xMat, SvNumFormatType& rRetTypeExpr, sal_uInt32& rRetIndexExpr);
+
+ formula::FormulaToken* CreateFormulaDoubleToken( double fVal, SvNumFormatType nFmt = SvNumFormatType::NUMBER );
+ formula::FormulaToken* CreateDoubleOrTypedToken( double fVal );
+
+ void PushDouble(double nVal);
+ void PushInt( int nVal );
+ void PushStringBuffer( const sal_Unicode* pString );
+ void PushString( const OUString& rStr );
+ void PushString( const svl::SharedString& rString );
+ void PushSingleRef(SCCOL nCol, SCROW nRow, SCTAB nTab);
+ void PushDoubleRef(SCCOL nCol1, SCROW nRow1, SCTAB nTab1,
+ SCCOL nCol2, SCROW nRow2, SCTAB nTab2);
+ void PushExternalSingleRef(sal_uInt16 nFileId, const OUString& rTabName,
+ SCCOL nCol, SCROW nRow, SCTAB nTab);
+ void PushExternalDoubleRef(sal_uInt16 nFileId, const OUString& rTabName,
+ SCCOL nCol1, SCROW nRow1, SCTAB nTab1,
+ SCCOL nCol2, SCROW nRow2, SCTAB nTab2);
+ void PushSingleRef( const ScRefAddress& rRef );
+ void PushDoubleRef( const ScRefAddress& rRef1, const ScRefAddress& rRef2 );
+ void PushMatrix( const sc::RangeMatrix& rMat );
+ void PushMatrix(const ScMatrixRef& pMat);
+ void PushError( FormulaError nError );
+ /// Raw stack type without default replacements.
+ formula::StackVar GetRawStackType();
+ /// Stack type with replacement of defaults, e.g. svMissing and formula::svEmptyCell will result in formula::svDouble.
+ formula::StackVar GetStackType();
+ // peek StackType of Parameter, Parameter 1 == TOS, 2 == TOS-1, ...
+ formula::StackVar GetStackType( sal_uInt8 nParam );
+ sal_uInt8 GetByte() const { return cPar; }
+ // reverse order of stack
+ void ReverseStack( sal_uInt8 nParamCount );
+ // generates a position-dependent SingleRef out of a DoubleRef
+ bool DoubleRefToPosSingleRef( const ScRange& rRange, ScAddress& rAdr );
+ double GetDoubleFromMatrix(const ScMatrixRef& pMat);
+ double GetDouble();
+ double GetDoubleWithDefault(double nDefault);
+ bool IsMissing() const;
+ sal_Int32 double_to_int32(double fVal);
+ /** if GetDouble() not within int32 limits sets nGlobalError and returns SAL_MAX_INT32 */
+ sal_Int32 GetInt32();
+ /** if GetDoubleWithDefault() not within int32 limits sets nGlobalError and returns SAL_MAX_INT32 */
+ sal_Int32 GetInt32WithDefault( sal_Int32 nDefault );
+ /** if GetDouble() not within int16 limits sets nGlobalError and returns SAL_MAX_INT16 */
+ sal_Int16 GetInt16();
+ /** if GetDouble() not within uint32 limits sets nGlobalError and returns SAL_MAX_UINT32 */
+ sal_uInt32 GetUInt32();
+ bool GetBool() { return GetDouble() != 0.0; }
+ /// returns TRUE if double (or error, check nGlobalError), else FALSE
+ bool GetDoubleOrString( double& rValue, svl::SharedString& rString );
+ svl::SharedString GetString();
+ svl::SharedString GetStringFromMatrix(const ScMatrixRef& pMat);
+ svl::SharedString GetStringFromDouble( const double fVal);
+ // pop matrix and obtain one element, upper left or according to jump matrix
+ ScMatValType GetDoubleOrStringFromMatrix( double& rDouble, svl::SharedString& rString );
+ ScMatrixRef CreateMatrixFromDoubleRef( const formula::FormulaToken* pToken,
+ SCCOL nCol1, SCROW nRow1, SCTAB nTab1,
+ SCCOL nCol2, SCROW nRow2, SCTAB nTab2 );
+ inline ScTokenMatrixMap& GetTokenMatrixMap();
+ ScMatrixRef GetMatrix();
+ ScMatrixRef GetMatrix( short & rParam, size_t & rInRefList );
+ sc::RangeMatrix GetRangeMatrix();
+
+ void ScTableOp(); // repeated operations
+
+ // common helper functions
+
+ void CurFmtToFuncFmt()
+ { nFuncFmtType = nCurFmtType; nFuncFmtIndex = nCurFmtIndex; }
+
+ /** Check if a double is suitable as string position or length argument.
+
+ If fVal is Inf or NaN it is changed to -1, if it is less than 0 it is
+ sanitized to 0, if it is greater than some implementation defined max
+ string length it is sanitized to that max.
+
+ @return TRUE if double value fVal is suitable as string argument and was
+ not sanitized.
+ FALSE if not and fVal was adapted.
+ */
+ static inline bool CheckStringPositionArgument( double & fVal );
+
+ /** Obtain a sal_Int32 suitable as string position or length argument.
+ Returns -1 if the number is Inf or NaN or less than 0 or greater than some
+ implementation defined max string length. In these cases also sets
+ nGlobalError to FormulaError::IllegalArgument, if not already set. */
+ inline sal_Int32 GetStringPositionArgument();
+
+ // Check for String overflow of rResult+rAdd and set error and erase rResult
+ // if so. Return true if ok, false if overflow
+ inline bool CheckStringResultLen( OUString& rResult, sal_Int32 nIncrease );
+
+ // Check for String overflow of rResult+rAdd and set error and erase rResult
+ // if so. Return true if ok, false if overflow
+ inline bool CheckStringResultLen( OUStringBuffer& rResult, sal_Int32 nIncrease );
+
+ // Set error according to rVal, and set rVal to 0.0 if there was an error.
+ inline void TreatDoubleError( double& rVal );
+ // Lookup using ScLookupCache, @returns true if found and result address
+ bool LookupQueryWithCache( ScAddress & o_rResultPos,
+ const ScQueryParam & rParam, const ScComplexRefData* refData ) const;
+
+ void ScIfJump();
+ void ScIfError( bool bNAonly );
+ void ScChooseJump();
+
+ // Be sure to only call this if pStack[sp-nStackLevel] really contains a
+ // ScJumpMatrixToken, no further checks are applied!
+ // Returns true if last jump was executed and result matrix pushed.
+ bool JumpMatrix( short nStackLevel );
+
+ double Compare( ScQueryOp eOp );
+ /** @param pOptions
+ NULL means case sensitivity document option is to be used!
+ */
+ sc::RangeMatrix CompareMat( ScQueryOp eOp, sc::CompareOptions* pOptions = nullptr );
+ ScMatrixRef QueryMat( const ScMatrixRef& pMat, sc::CompareOptions& rOptions );
+ void ScEqual();
+ void ScNotEqual();
+ void ScLess();
+ void ScGreater();
+ void ScLessEqual();
+ void ScGreaterEqual();
+ void ScAnd();
+ void ScOr();
+ void ScXor();
+ void ScNot();
+ void ScNeg();
+ void ScPercentSign();
+ void ScIntersect();
+ void ScRangeFunc();
+ void ScUnionFunc();
+ void ScPi();
+ void ScRandom();
+ void ScRandbetween();
+ void ScRandomImpl( const std::function<double( double fFirst, double fLast )>& RandomFunc,
+ double fFirst, double fLast );
+ void ScTrue();
+ void ScFalse();
+ void ScDeg();
+ void ScRad();
+ void ScSin();
+ void ScCos();
+ void ScTan();
+ void ScCot();
+ void ScArcSin();
+ void ScArcCos();
+ void ScArcTan();
+ void ScArcCot();
+ void ScSinHyp();
+ void ScCosHyp();
+ void ScTanHyp();
+ void ScCotHyp();
+ void ScArcSinHyp();
+ void ScArcCosHyp();
+ void ScArcTanHyp();
+ void ScArcCotHyp();
+ void ScCosecant();
+ void ScSecant();
+ void ScCosecantHyp();
+ void ScSecantHyp();
+ void ScExp();
+ void ScLn();
+ void ScLog10();
+ void ScSqrt();
+ void ScIsEmpty();
+ bool IsString();
+ void ScIsString();
+ void ScIsNonString();
+ void ScIsLogical();
+ void ScType();
+ void ScCell();
+ void ScCellExternal();
+ void ScIsRef();
+ void ScIsValue();
+ void ScIsFormula();
+ void ScFormula();
+ void ScRoman();
+ void ScArabic();
+ void ScIsNV();
+ void ScIsErr();
+ void ScIsError();
+ bool IsEven();
+ void ScIsEven();
+ void ScIsOdd();
+ void ScN();
+ void ScCode();
+ void ScTrim();
+ void ScUpper();
+ void ScProper();
+ void ScLower();
+ void ScLen();
+ void ScT();
+ void ScValue();
+ void ScNumberValue();
+ void ScClean();
+ void ScChar();
+ void ScJis();
+ void ScAsc();
+ void ScUnicode();
+ void ScUnichar();
+ void ScMin( bool bTextAsZero = false );
+ void ScMax( bool bTextAsZero = false );
+ /** Check for array of references to determine the maximum size of a return
+ column vector if in array context. */
+ size_t GetRefListArrayMaxSize( short nParamCount );
+ /** Switch to array reference list if current TOS is one and create/init or
+ update matrix and return true. Else return false. */
+ bool SwitchToArrayRefList( ScMatrixRef& xResMat, SCSIZE nMatRows, double fCurrent,
+ const std::function<void( SCSIZE i, double fCurrent )>& MatOpFunc, bool bDoMatOp );
+ void IterateParameters( ScIterFunc, bool bTextAsZero = false );
+ void ScSumSQ();
+ void ScSum();
+ void ScProduct();
+ void ScAverage( bool bTextAsZero = false );
+ void ScCount();
+ void ScCount2();
+ void GetStVarParams( bool bTextAsZero, double(*VarResult)( double fVal, size_t nValCount ) );
+ void ScVar( bool bTextAsZero = false );
+ void ScVarP( bool bTextAsZero = false );
+ void ScStDev( bool bTextAsZero = false );
+ void ScStDevP( bool bTextAsZero = false );
+ void ScRawSubtract();
+ void ScColumns();
+ void ScRows();
+ void ScSheets();
+ void ScColumn();
+ void ScRow();
+ void ScSheet();
+ void ScMatch();
+ void IterateParametersIf( ScIterFuncIf );
+ void ScCountIf();
+ void ScSumIf();
+ void ScAverageIf();
+ void IterateParametersIfs( double(*ResultFunc)( const sc::ParamIfsResult& rRes ) );
+ void ScSumIfs();
+ void ScAverageIfs();
+ void ScCountIfs();
+ void ScCountEmptyCells();
+ void ScLookup();
+ void ScHLookup();
+ void ScVLookup();
+ void ScSubTotal();
+
+ // If upon call rMissingField==true then the database field parameter may be
+ // missing (Xcl DCOUNT() syntax), or may be faked as missing by having the
+ // value 0.0 or being exactly the entire database range reference (old SO
+ // compatibility). If this was the case then rMissingField is set to true upon
+ // return. If rMissingField==false upon call all "missing cases" are considered
+ // to be an error.
+ std::unique_ptr<ScDBQueryParamBase> GetDBParams( bool& rMissingField );
+
+ void DBIterator( ScIterFunc );
+ void ScDBSum();
+ void ScDBCount();
+ void ScDBCount2();
+ void ScDBAverage();
+ void ScDBGet();
+ void ScDBMax();
+ void ScDBMin();
+ void ScDBProduct();
+ void GetDBStVarParams( double& rVal, double& rValCount );
+ void ScDBStdDev();
+ void ScDBStdDevP();
+ void ScDBVar();
+ void ScDBVarP();
+ void ScIndirect();
+ void ScAddressFunc();
+ void ScOffset();
+ void ScIndex();
+ void ScMultiArea();
+ void ScAreas();
+ void ScCurrency();
+ void ScReplace();
+ void ScFixed();
+ void ScFind();
+ void ScExact();
+ void ScLeft();
+ void ScRight();
+ void ScSearch();
+ void ScMid();
+ void ScText();
+ void ScSubstitute();
+ void ScRept();
+ void ScRegex();
+ void ScConcat();
+ void ScConcat_MS();
+ void ScTextJoin_MS();
+ void ScIfs_MS();
+ void ScSwitch_MS();
+ void ScMinIfs_MS();
+ void ScMaxIfs_MS();
+ void ScExternal();
+ void ScMissing();
+ void ScMacro();
+ bool SetSbxVariable( SbxVariable* pVar, const ScAddress& );
+ FormulaError GetErrorType();
+ void ScErrorType();
+ void ScErrorType_ODF();
+ void ScDBArea();
+ void ScColRowNameAuto();
+ void ScGetPivotData();
+ void ScHyperLink();
+ void ScBahtText();
+ void ScBitAnd();
+ void ScBitOr();
+ void ScBitXor();
+ void ScBitRshift();
+ void ScBitLshift();
+ void ScTTT();
+ void ScDebugVar();
+
+ /** Obtain the date serial number for a given date.
+ @param bStrict
+ If false, nYear < 100 takes the two-digit year setting into account,
+ and rollover of invalid calendar dates takes place, e.g. 1999-02-31 =>
+ 1999-03-03.
+ If true, the date passed must be a valid Gregorian calendar date. No
+ two-digit expanding or rollover is done.
+
+ Date must be Gregorian, i.e. >= 1582-10-15.
+ */
+ double GetDateSerial( sal_Int16 nYear, sal_Int16 nMonth, sal_Int16 nDay, bool bStrict );
+
+ void ScGetActDate();
+ void ScGetActTime();
+ void ScGetYear();
+ void ScGetMonth();
+ void ScGetDay();
+ void ScGetDayOfWeek();
+ void ScGetWeekOfYear();
+ void ScGetIsoWeekOfYear();
+ void ScWeeknumOOo();
+ void ScEasterSunday();
+ FormulaError GetWeekendAndHolidayMasks( const sal_uInt8 nParamCount, const sal_uInt32 nNullDate,
+ ::std::vector<double>& rSortArray, bool bWeekendMask[ 7 ] );
+ FormulaError GetWeekendAndHolidayMasks_MS( const sal_uInt8 nParamCount, const sal_uInt32 nNullDate,
+ ::std::vector<double>& rSortArray, bool bWeekendMask[ 7 ], bool bWorkdayFunction );
+ static inline sal_Int16 GetDayOfWeek( sal_Int32 n );
+ void ScNetWorkdays( bool bOOXML_Version );
+ void ScWorkday_MS();
+ void ScGetHour();
+ void ScGetMin();
+ void ScGetSec();
+ void ScPlusMinus();
+ void ScAbs();
+ void ScInt();
+ void ScEven();
+ void ScOdd();
+ void ScCeil( bool bODFF );
+ void ScCeil_MS();
+ void ScCeil_Precise();
+ void ScFloor( bool bODFF );
+ void ScFloor_MS();
+ void ScFloor_Precise();
+ void RoundNumber( rtl_math_RoundingMode eMode );
+ void ScRound();
+ void ScRoundUp();
+ void ScRoundDown();
+ void ScGetDateValue();
+ void ScGetTimeValue();
+ void ScArcTan2();
+ void ScLog();
+ void ScGetDate();
+ void ScGetTime();
+ void ScGetDiffDate();
+ void ScGetDiffDate360();
+ void ScGetDateDif();
+ void ScPower();
+ void ScAmpersand();
+ void ScAdd();
+ void ScSub();
+ void ScMul();
+ void ScDiv();
+ void ScPow();
+ void ScCurrent();
+ void ScStyle();
+ void ScDde();
+ void ScBase();
+ void ScDecimal();
+ void ScConvertOOo();
+ void ScEuroConvert();
+ void ScRoundSignificant();
+ static void RoundSignificant( double fX, double fDigits, double &fRes );
+
+ // financial functions
+ void ScNPV();
+ void ScIRR();
+ void ScMIRR();
+ void ScISPMT();
+
+ static double ScGetPV(double fRate, double fNper, double fPmt,
+ double fFv, bool bPayInAdvance);
+ void ScPV();
+ void ScSYD();
+ static double ScGetDDB(double fCost, double fSalvage, double fLife,
+ double fPeriod, double fFactor);
+ void ScDDB();
+ void ScDB();
+ static double ScInterVDB(double fCost, double fSalvage, double fLife, double fLife1,
+ double fPeriod, double fFactor);
+ void ScVDB();
+ void ScPDuration();
+ void ScSLN();
+ static double ScGetPMT(double fRate, double fNper, double fPv,
+ double fFv, bool bPayInAdvance);
+ void ScPMT();
+ void ScRRI();
+ static double ScGetFV(double fRate, double fNper, double fPmt,
+ double fPv, bool bPayInAdvance);
+ void ScFV();
+ void ScNper();
+ static bool RateIteration(double fNper, double fPayment, double fPv,
+ double fFv, bool bPayType, double& fGuess);
+ void ScRate();
+ double ScGetIpmt(double fRate, double fPer, double fNper, double fPv,
+ double fFv, bool bPayInAdvance, double& fPmt);
+ void ScIpmt();
+ void ScPpmt();
+ void ScCumIpmt();
+ void ScCumPrinc();
+ void ScEffect();
+ void ScNominal();
+ void ScMod();
+ void ScIntercept();
+ void ScGCD();
+ void ScLCM();
+
+ // matrix functions
+ void ScMatValue();
+ static void MEMat(const ScMatrixRef& mM, SCSIZE n);
+ void ScMatDet();
+ void ScMatInv();
+ void ScMatMult();
+ void ScMatTrans();
+ void ScEMat();
+ void ScMatRef();
+ ScMatrixRef MatConcat(const ScMatrixRef& pMat1, const ScMatrixRef& pMat2);
+ void ScSumProduct();
+ void ScSumX2MY2();
+ void ScSumX2DY2();
+ void ScSumXMY2();
+ void ScGrowth();
+ bool CalculateSkew(KahanSum& fSum, double& fCount, std::vector<double>& values);
+ void CalculateSkewOrSkewp( bool bSkewp );
+ void CalculateSlopeIntercept(bool bSlope);
+ void CalculateSmallLarge(bool bSmall);
+ void CalculatePearsonCovar( bool _bPearson, bool _bStexy, bool _bSample ); //fdo#70000 argument _bSample is ignored if _bPearson == true
+ bool 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);
+ void CalculateLookup(bool bHLookup);
+ bool FillEntry(ScQueryEntry& rEntry);
+ void CalculateAddSub(bool _bSub);
+ void CalculateTrendGrowth(bool _bGrowth);
+ void CalculateRGPRKP(bool _bRKP);
+ void CalculateSumX2MY2SumX2DY2(bool _bSumX2DY2);
+ void CalculateMatrixValue(const ScMatrix* pMat,SCSIZE nC,SCSIZE nR);
+ bool CheckMatrix(bool _bLOG,sal_uInt8& nCase,SCSIZE& nCX,SCSIZE& nCY,SCSIZE& nRX,SCSIZE& nRY,SCSIZE& M,SCSIZE& N,ScMatrixRef& pMatX,ScMatrixRef& pMatY);
+ void ScLinest();
+ void ScLogest();
+ void ScForecast();
+ void ScForecast_Ets( ScETSType eETSType );
+ void ScFourier();
+ void ScNoName();
+ void ScBadName();
+ // Statistics:
+ static double taylor(const double* pPolynom, sal_uInt16 nMax, double x);
+ static double gauss(double x);
+
+public:
+ static double phi(double x);
+ static double integralPhi(double x);
+ static double gaussinv(double x);
+ static double GetPercentile( ::std::vector<double> & rArray, double fPercentile );
+
+
+private:
+ double GetBetaDist(double x, double alpha, double beta); //cumulative distribution function
+ double GetBetaDistPDF(double fX, double fA, double fB); //probability density function)
+ double GetChiDist(double fChi, double fDF); // for LEGACY.CHIDIST, returns right tail
+ double GetChiSqDistCDF(double fX, double fDF); // for CHISQDIST, returns left tail
+ static double GetChiSqDistPDF(double fX, double fDF); // probability density function
+ double GetFDist(double x, double fF1, double fF2);
+ double GetTDist( double T, double fDF, int nType );
+ double Fakultaet(double x);
+ static double BinomKoeff(double n, double k);
+ double GetGamma(double x);
+ static double GetLogGamma(double x);
+ double GetBeta(double fAlpha, double fBeta);
+ static double GetLogBeta(double fAlpha, double fBeta);
+ double GetBinomDistPMF(double x, double n, double p); //probability mass function
+ double GetHypGeomDist( double x, double n, double M, double N );
+ void ScLogGamma();
+ void ScGamma();
+ void ScPhi();
+ void ScGauss();
+ void ScStdNormDist();
+ void ScStdNormDist_MS();
+ void ScFisher();
+ void ScFisherInv();
+ void ScFact();
+ void ScNormDist( int nMinParamCount );
+ void ScGammaDist( bool bODFF );
+ void ScGammaInv();
+ void ScExpDist();
+ void ScBinomDist();
+ void ScPoissonDist( bool bODFF );
+ void ScCombin();
+ void ScCombinA();
+ void ScPermut();
+ void ScPermutationA();
+ void ScB();
+ void ScHypGeomDist( int nMinParamCount );
+ void ScLogNormDist( int nMinParamCount );
+ void ScLogNormInv();
+ void ScTDist();
+ void ScTDist_MS();
+ void ScTDist_T( int nTails );
+ void ScFDist();
+ void ScFDist_LT();
+ void ScChiDist( bool bODFF); // for LEGACY.CHIDIST, returns right tail
+ void ScChiSqDist(); // returns left tail or density
+ void ScChiSqDist_MS();
+ void ScChiSqInv(); // inverse to CHISQDIST
+ void ScWeibull();
+ void ScBetaDist();
+ void ScBetaDist_MS();
+ void ScFInv();
+ void ScFInv_LT();
+ void ScTInv( int nType );
+ void ScChiInv();
+ void ScBetaInv();
+ void ScCritBinom();
+ void ScNegBinomDist();
+ void ScNegBinomDist_MS();
+ void ScKurt();
+ void ScHarMean();
+ void ScGeoMean();
+ void ScStandard();
+ void ScSkew();
+ void ScSkewp();
+ void ScMedian();
+ double GetMedian( ::std::vector<double> & rArray );
+ double GetPercentileExclusive( ::std::vector<double> & rArray, double fPercentile );
+ std::vector<double> GetTopNumberArray( SCSIZE& rCol, SCSIZE& rRow );
+ void GetNumberSequenceArray( sal_uInt8 nParamCount, ::std::vector<double>& rArray, bool bConvertTextInArray );
+ void GetSortArray( sal_uInt8 nParamCount, ::std::vector<double>& rSortArray, ::std::vector<tools::Long>* pIndexOrder, bool bConvertTextInArray, bool bAllowEmptyArray );
+ static void QuickSort(::std::vector<double>& rSortArray, ::std::vector<tools::Long>* pIndexOrder);
+ void ScModalValue();
+ void ScModalValue_MS( bool bSingle );
+ void ScAveDev();
+ void ScAggregate();
+ void ScDevSq();
+ void ScZTest();
+ void ScTTest();
+ void ScFTest();
+ void ScChiTest();
+ void ScRank( bool bAverage );
+ void ScPercentile( bool bInclusive );
+ void ScPercentrank( bool bInclusive );
+ static double GetPercentrank( ::std::vector<double> & rArray, double fVal, bool bInclusive );
+ void ScLarge();
+ void ScSmall();
+ void ScFrequency();
+ void ScQuartile( bool bInclusive );
+ void ScNormInv();
+ void ScSNormInv();
+ void ScConfidence();
+ void ScConfidenceT();
+ void ScTrimMean();
+ void ScProbability();
+ void ScCorrel();
+ void ScCovarianceP();
+ void ScCovarianceS();
+ void ScPearson();
+ void ScRSQ();
+ void ScSTEYX();
+ void ScSlope();
+ void ScTrend();
+ void ScInfo();
+ void ScLenB();
+ void ScRightB();
+ void ScLeftB();
+ void ScMidB();
+ void ScReplaceB();
+ void ScFindB();
+ void ScSearchB();
+
+ void ScFilterXML();
+ void ScWebservice();
+ void ScEncodeURL();
+ void ScColor();
+ void ScErf();
+ void ScErfc();
+
+ static const double fMaxGammaArgument;
+
+ double GetGammaContFraction(double fA,double fX);
+ double GetGammaSeries(double fA,double fX);
+ double GetLowRegIGamma(double fA,double fX); // lower regularized incomplete gamma function, GAMMAQ
+ double GetUpRegIGamma(double fA,double fX); // upper regularized incomplete gamma function, GAMMAP
+ // probability density function; fLambda is "scale" parameter
+ double GetGammaDistPDF(double fX, double fAlpha, double fLambda);
+ // cumulative distribution function; fLambda is "scale" parameter
+ double GetGammaDist(double fX, double fAlpha, double fLambda);
+ double GetTInv( double fAlpha, double fSize, int nType );
+
+public:
+ ScInterpreter( ScFormulaCell* pCell, ScDocument& rDoc, ScInterpreterContext& rContext,
+ const ScAddress&, ScTokenArray&, bool bForGroupThreading = false );
+ ~ScInterpreter();
+
+ // Used only for threaded formula-groups.
+ // Resets the interpreter object, allowing reuse of interpreter object for each cell
+ // in the group.
+ void Init( ScFormulaCell* pCell, const ScAddress& rPos, ScTokenArray& rTokArray );
+
+ formula::StackVar Interpret();
+
+ void SetError(FormulaError nError)
+ { if (nError != FormulaError::NONE && nGlobalError == FormulaError::NONE) nGlobalError = nError; }
+ void AssertFormulaMatrix();
+
+ void SetLinkManager(sfx2::LinkManager* pLinkMgr)
+ { mpLinkManager = pLinkMgr; }
+
+ FormulaError GetError() const { return nGlobalError; }
+ formula::StackVar GetResultType() const { return xResult->GetType(); }
+ const svl::SharedString & GetStringResult() const;
+ double GetNumResult() const { return xResult->GetDouble(); }
+ const formula::FormulaConstTokenRef& GetResultToken() const { return xResult; }
+ SvNumFormatType GetRetFormatType() const { return nRetFmtType; }
+ sal_uLong GetRetFormatIndex() const { return nRetFmtIndex; }
+};
+
+inline bool ScInterpreter::IsInArrayContext() const
+{
+ return bMatrixFormula || pCur->IsInForceArray();
+}
+
+inline void ScInterpreter::MatrixJumpConditionToMatrix()
+{
+ if (IsInArrayContext())
+ ConvertMatrixJumpConditionToMatrix();
+}
+
+inline bool ScInterpreter::MatrixParameterConversion()
+{
+ if ( (IsInArrayContext() || ScParameterClassification::HasForceArray( pCur->GetOpCode())) &&
+ !pJumpMatrix && sp > 0 )
+ return ConvertMatrixParameters();
+ return false;
+}
+
+inline ScTokenMatrixMap& ScInterpreter::GetTokenMatrixMap()
+{
+ return maTokenMatrixMap;
+}
+
+inline bool ScInterpreter::MustHaveParamCount( short nAct, short nMust )
+{
+ if ( nAct == nMust )
+ return true;
+ if ( nAct < nMust )
+ PushParameterExpected();
+ else
+ PushIllegalParameter();
+ return false;
+}
+
+inline bool ScInterpreter::MustHaveParamCount( short nAct, short nMust, short nMax )
+{
+ if ( nMust <= nAct && nAct <= nMax )
+ return true;
+ if ( nAct < nMust )
+ PushParameterExpected();
+ else
+ PushIllegalParameter();
+ return false;
+}
+
+inline bool ScInterpreter::MustHaveParamCountMin( short nAct, short nMin )
+{
+ if ( nAct >= nMin )
+ return true;
+ PushParameterExpected();
+ return false;
+}
+
+inline bool ScInterpreter::MustHaveParamCountMinWithStackCheck( short nAct, short nMin )
+{
+ assert(sp >= nAct);
+ if (sp < nAct)
+ {
+ PushParameterExpected();
+ return false;
+ }
+ return MustHaveParamCountMin( nAct, nMin);
+}
+
+inline bool ScInterpreter::CheckStringPositionArgument( double & fVal )
+{
+ if (!std::isfinite( fVal))
+ {
+ fVal = -1.0;
+ return false;
+ }
+ else if (fVal < 0.0)
+ {
+ fVal = 0.0;
+ return false;
+ }
+ else if (fVal > SAL_MAX_INT32)
+ {
+ fVal = static_cast<double>(SAL_MAX_INT32);
+ return false;
+ }
+ return true;
+}
+
+inline sal_Int32 ScInterpreter::GetStringPositionArgument()
+{
+ double fVal = rtl::math::approxFloor( GetDouble());
+ if (!CheckStringPositionArgument( fVal))
+ {
+ fVal = -1.0;
+ SetError( FormulaError::IllegalArgument);
+ }
+ return static_cast<sal_Int32>(fVal);
+}
+
+inline bool ScInterpreter::CheckStringResultLen( OUString& rResult, sal_Int32 nIncrease )
+{
+ if (nIncrease > kScInterpreterMaxStrLen - rResult.getLength())
+ {
+ SetError( FormulaError::StringOverflow );
+ rResult.clear();
+ return false;
+ }
+ return true;
+}
+
+inline bool ScInterpreter::CheckStringResultLen( OUStringBuffer& rResult, sal_Int32 nIncrease )
+{
+ if (nIncrease > kScInterpreterMaxStrLen - rResult.getLength())
+ {
+ SetError( FormulaError::StringOverflow );
+ rResult.setLength(0);
+ return false;
+ }
+ return true;
+}
+
+inline void ScInterpreter::TreatDoubleError( double& rVal )
+{
+ if ( !std::isfinite( rVal ) )
+ {
+ FormulaError nErr = GetDoubleErrorValue( rVal );
+ if ( nErr != FormulaError::NONE )
+ SetError( nErr );
+ else
+ SetError( FormulaError::NoValue );
+ rVal = 0.0;
+ }
+}
+
+inline double ScInterpreter::div( const double& fNumerator, const double& fDenominator )
+{
+ return sc::div(fNumerator, fDenominator);
+}
+
+inline sal_Int16 ScInterpreter::GetDayOfWeek( sal_Int32 n )
+{ // monday = 0, ..., sunday = 6
+ return static_cast< sal_Int16 >( ( n - 1 ) % 7 );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/inc/jumpmatrix.hxx b/sc/source/core/inc/jumpmatrix.hxx
new file mode 100644
index 000000000..f0fb9c1ab
--- /dev/null
+++ b/sc/source/core/inc/jumpmatrix.hxx
@@ -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/.
+ *
+ * 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 .
+ */
+
+#pragma once
+
+#include <limits.h>
+#include <vector>
+#include <types.hxx>
+#include <address.hxx>
+#include <token.hxx>
+
+namespace formula { class FormulaToken; }
+
+typedef ::std::vector< const formula::FormulaToken*> ScTokenVec;
+
+struct ScJumpMatrixEntry
+{
+ double fBool; // 0:= false 1:= true also if no-path
+ // other values may contain error conditions like NAN and INF
+ short nStart; // start of path (actually start-1, see formula::FormulaTokenIterator)
+ short nNext; // next after path
+ // jump path exists if nStart != nNext, else no path
+ short nStop; // optional stop of path (nPC < nStop)
+
+ void SetJump( double fBoolP, short nStartP, short nNextP, short nStopP )
+ {
+ fBool = fBoolP;
+ nStart = nStartP;
+ nNext = nNextP;
+ nStop = nStopP;
+ }
+ void GetJump( double& rBool, short& rStart, short& rNext, short& rStop ) const
+ {
+ rBool = fBool;
+ rStart = nStart;
+ rNext = nNext;
+ rStop = nStop;
+ }
+};
+
+class ScJumpMatrix
+{
+ std::vector<ScJumpMatrixEntry> mvJump; // the jumps
+ ScMatrixRef pMat; // the results
+ ScRefList mvRefList; // array of references result, if any
+ ScTokenVec mvParams; // parameter stack
+ SCSIZE nCols;
+ SCSIZE nRows;
+ SCSIZE nCurCol;
+ SCSIZE nCurRow;
+ SCSIZE nResMatCols;
+ SCSIZE nResMatRows;
+ OpCode meOp;
+ bool bStarted;
+
+ // Buffer result ranges to be able to set a range of identically typed
+ // values at the result matrix in order to avoid multiple shrinks and
+ // growths of multi_type_vector segments, which is a major performance
+ // bottleneck, see fdo#72929
+ ::std::vector< svl::SharedString > mvBufferStrings;
+ ::std::vector< double > mvBufferDoubles;
+ SCSIZE mnBufferCol;
+ SCSIZE mnBufferRowStart;
+ SCSIZE mnBufferEmptyCount;
+ SCSIZE mnBufferEmptyPathCount;
+
+ enum BufferType
+ {
+ BUFFER_NONE,
+ BUFFER_DOUBLE,
+ BUFFER_STRING,
+ BUFFER_EMPTY,
+ BUFFER_EMPTYPATH
+ };
+
+ /** Flush different types or non-consecutive buffers. */
+ void FlushBufferOtherThan( BufferType eType, SCSIZE nC, SCSIZE nR );
+
+ ScJumpMatrix( const ScJumpMatrix& ) = delete;
+ ScJumpMatrix& operator=( const ScJumpMatrix& ) = delete;
+
+public:
+ ScJumpMatrix( OpCode eOp, SCSIZE nColsP, SCSIZE nRowsP );
+ ~ScJumpMatrix();
+ void GetDimensions( SCSIZE& rCols, SCSIZE& rRows ) const;
+ void SetJump( SCSIZE nCol, SCSIZE nRow, double fBool, short nStart, short nNext );
+ void GetJump( SCSIZE nCol, SCSIZE nRow, double& rBool, short& rStart, short& rNext, short& rStop ) const;
+ void SetAllJumps( double fBool, short nStart, short nNext, short nStop = SHRT_MAX );
+ void SetJumpParameters( ScTokenVec&& p );
+ const ScTokenVec & GetJumpParameters() const { return mvParams;}
+ bool HasResultMatrix() const;
+ ScMatrix* GetResultMatrix(); ///< also applies pending buffered values
+ void GetPos( SCSIZE& rCol, SCSIZE& rRow ) const;
+ bool Next( SCSIZE& rCol, SCSIZE& rRow );
+ void GetResMatDimensions( SCSIZE& rCols, SCSIZE& rRows );
+ void SetNewResMat( SCSIZE nNewCols, SCSIZE nNewRows );
+ ScRefList& GetRefList();
+ OpCode GetOpCode() const { return meOp; }
+
+ void PutResultDouble( double fVal, SCSIZE nC, SCSIZE nR );
+ void PutResultString( const svl::SharedString& rStr, SCSIZE nC, SCSIZE nR );
+ void PutResultEmpty( SCSIZE nC, SCSIZE nR );
+ void PutResultEmptyPath( SCSIZE nC, SCSIZE nR );
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/inc/parclass.hxx b/sc/source/core/inc/parclass.hxx
new file mode 100644
index 000000000..4b043fd7f
--- /dev/null
+++ b/sc/source/core/inc/parclass.hxx
@@ -0,0 +1,144 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <formula/opcode.hxx>
+#include <formula/paramclass.hxx>
+
+/** Activate parameter classification documentation.
+
+ Building with DEBUG_SC_PARCLASSDOC 1 enables generation of parameter
+ classification documentation when instantiating the first Calc document if
+ the environment variable OOO_CALC_GENPARCLASSDOC is set and SAL_LOG
+ contains +INFO.sc.core
+
+ Generated output contains CALC_GENPARCLASSDOC that can be easily grep'ed for.
+
+ Activation adds overhead to Calc initialization time to gather information
+ that otherwise is not needed for anything else.
+ */
+#define DEBUG_SC_PARCLASSDOC 0
+
+namespace formula
+{
+ class FormulaToken;
+}
+
+class ScParameterClassification
+{
+public:
+
+ /// MUST be called once before any other method.
+ static void Init();
+
+ static void Exit();
+
+ /** Get one parameter type for function eOp.
+ @param nParameter
+ Which parameter, 0-based.
+ SAL_MAX_UINT16 for return type of eOp.
+ */
+ static formula::ParamClass GetParameterType( const formula::FormulaToken* pToken,
+ sal_uInt16 nParameter);
+
+ /** Whether OpCode has a parameter of type
+ ForceArray or ReferenceOrForceArray. */
+ static bool HasForceArray( OpCode eOp)
+ {
+ return 0 <= static_cast<short>(eOp) &&
+ eOp <= SC_OPCODE_LAST_OPCODE_ID &&
+ pData[eOp].bHasForceArray;
+ }
+
+private:
+
+ struct CommonData
+ {
+ const static sal_Int32 nMaxParams = 7;
+
+ formula::ParamClass nParam[nMaxParams];
+ sal_uInt8 nRepeatLast;
+ formula::ParamClass eReturn;
+ };
+
+ struct RawData
+ {
+ OpCode eOp;
+ CommonData aData;
+ };
+
+ struct RunData;
+ friend struct ScParameterClassification::RunData;
+ struct RunData
+ {
+ CommonData aData;
+ sal_uInt8 nMinParams; // fix or minimum, or repeat start
+ bool bHasForceArray;
+ };
+
+ static const RawData pRawData[];
+ static RunData* pData;
+
+ // ocExternal AddIns
+ static formula::ParamClass GetExternalParameterType(
+ const formula::FormulaToken* pToken, sal_uInt16 nParameter);
+
+#if DEBUG_SC_PARCLASSDOC
+ // Generate documentation to stdout if environment variable
+ // OOO_CALC_GENPARCLASSDOC is set.
+ static void GenerateDocumentation();
+
+ /* OpCodes not specified in the implementation are taken from the global
+ * function list and all parameters, if any, are assumed to be of type
+ * Value. This could also be done in the product version if needed, but we
+ * don't want to spoil startup time. However, doing so could propagate the
+ * minimum parameter count to the formula compiler, which, together with
+ * additional information about optional parameters, could react on missing
+ * parameters then. */
+ static void MergeArgumentsFromFunctionResource();
+
+ /** Minimum number of parameters, or fix number
+ of parameters if HasRepeatParameters()
+ returns sal_False. For opcodes not specified in
+ the implementation a parameter count of 1
+ is assumed, for opcodes out of range 0 is
+ assumed. If HasRepeatParameters() returns
+ sal_True, information is NOT related to whether
+ any parameters are optional, only the type
+ of parameters is significant. */
+ static inline sal_uInt8 GetMinimumParameters( OpCode eOp)
+ {
+ if ( eOp <= SC_OPCODE_LAST_OPCODE_ID )
+ return pData[eOp].aData.nParam[0]
+ == formula::ParamClass::Unknown ? 1 :
+ pData[eOp].nMinParams;
+ return 0;
+ }
+
+ /** Whether last parameter types are repeated. */
+ static inline bool HasRepeatParameters( OpCode eOp)
+ {
+ return eOp <= SC_OPCODE_LAST_OPCODE_ID
+ && pData[eOp].aData.nRepeatLast > 0;
+ }
+#endif // DEBUG_SC_PARCLASSDOC
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/inc/poolhelp.hxx b/sc/source/core/inc/poolhelp.hxx
new file mode 100644
index 000000000..ed1275da5
--- /dev/null
+++ b/sc/source/core/inc/poolhelp.hxx
@@ -0,0 +1,65 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <rtl/ref.hxx>
+#include <salhelper/simplereferenceobject.hxx>
+#include <docoptio.hxx>
+#include <svl/itempool.hxx>
+#include <mutex>
+
+class ScDocument;
+class ScDocumentPool;
+class ScStyleSheetPool;
+class SvNumberFormatter;
+class SfxItemPool;
+
+class ScPoolHelper final : public salhelper::SimpleReferenceObject
+{
+private:
+ mutable std::mutex maMtxCreateNumFormatter;
+ ScDocOptions aOpt;
+ rtl::Reference<ScDocumentPool> pDocPool;
+ rtl::Reference< ScStyleSheetPool > mxStylePool;
+ mutable std::unique_ptr<SvNumberFormatter> pFormTable;
+ mutable rtl::Reference<SfxItemPool> pEditPool; // EditTextObjectPool
+ mutable rtl::Reference<SfxItemPool> pEnginePool; // EditEnginePool
+ ScDocument& m_rSourceDoc;
+
+public:
+ ScPoolHelper( ScDocument& rSourceDoc );
+ virtual ~ScPoolHelper() override;
+
+ // called in dtor of main document
+ void SourceDocumentGone();
+
+ // access to pointers (are never 0):
+ ScDocumentPool* GetDocPool() const { return pDocPool.get(); }
+ ScStyleSheetPool* GetStylePool() const { return mxStylePool.get(); }
+ SvNumberFormatter* GetFormTable() const;
+ SfxItemPool* GetEditPool() const;
+ SfxItemPool* GetEnginePool() const;
+
+ void SetFormTableOpt(const ScDocOptions& rOpt);
+
+ std::unique_ptr<SvNumberFormatter> CreateNumberFormatter() const;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/inc/refupdat.hxx b/sc/source/core/inc/refupdat.hxx
new file mode 100644
index 000000000..cd3ab69df
--- /dev/null
+++ b/sc/source/core/inc/refupdat.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <global.hxx>
+
+class ScDocument;
+class ScBigRange;
+struct ScComplexRefData;
+class ScAddress;
+class ScRange;
+
+enum ScRefUpdateRes {
+ UR_NOTHING = 0, ///< Reference not affected, no change at all.
+ UR_UPDATED = 1, ///< Reference was adjusted/updated.
+ UR_INVALID = 2, ///< Some part of the reference became invalid.
+ UR_STICKY = 3 /**< Not updated because the reference is sticky,
+ but would had been updated if it wasn't. For
+ entire columns/rows. Essentially the same as
+ not UR_NOTHING for the caller but allows
+ differentiation. */
+};
+
+class ScRefUpdate
+{
+public:
+
+ static ScRefUpdateRes 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 );
+
+ static ScRefUpdateRes Update( UpdateRefMode eUpdateRefMode,
+ const ScBigRange& rWhere,
+ sal_Int32 nDx, sal_Int32 nDy, sal_Int32 nDz,
+ ScBigRange& rWhat );
+
+ static void MoveRelWrap( const ScDocument& rDoc, const ScAddress& rPos,
+ SCCOL nMaxCol, SCROW nMaxRow, ScComplexRefData& rRef );
+
+ static ScRefUpdateRes UpdateTranspose(
+ const ScDocument& rDoc, const ScRange& rSource, const ScAddress& rDest, ScRange& rRef );
+
+ static void DoTranspose( SCCOL& rCol, SCROW& rRow, SCTAB& rTab, const ScDocument& rDoc,
+ const ScRange& rSource, const ScAddress& rDest );
+
+ static ScRefUpdateRes UpdateGrow(
+ const ScRange& rArea, SCCOL nGrowX, SCROW nGrowY, ScRange& rRef );
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/inc/scrdata.hxx b/sc/source/core/inc/scrdata.hxx
new file mode 100644
index 000000000..0698e1257
--- /dev/null
+++ b/sc/source/core/inc/scrdata.hxx
@@ -0,0 +1,37 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <com/sun/star/uno/Reference.hxx>
+
+namespace com::sun::star::i18n
+{
+class XBreakIterator;
+}
+
+class ScScriptTypeData
+{
+public:
+ css::uno::Reference<css::i18n::XBreakIterator> xBreakIter;
+
+ ScScriptTypeData() {}
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/inc/sharedstringpoolpurge.hxx b/sc/source/core/inc/sharedstringpoolpurge.hxx
new file mode 100644
index 000000000..76ed42b7e
--- /dev/null
+++ b/sc/source/core/inc/sharedstringpoolpurge.hxx
@@ -0,0 +1,48 @@
+/* -*- 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/.
+ *
+ */
+
+#pragma once
+
+#include <memory>
+#include <vector>
+
+#include <svl/sharedstringpool.hxx>
+#include <vcl/timer.hxx>
+
+namespace svl
+{
+class SharedStringPool;
+}
+
+namespace sc
+{
+/*
+Calls svl::SharedStringPool::purge() after a delay when idle. Can be
+used to compress repeated calls, as purge() may be somewhat expensive
+with large documents. And since vcl links to svl, it's not possible
+to use VCL timers in svl, so a separate class is needed.
+*/
+class SharedStringPoolPurge
+{
+public:
+ SharedStringPoolPurge();
+ ~SharedStringPoolPurge();
+ void delayedPurge(const std::shared_ptr<svl::SharedStringPool>& pool);
+
+private:
+ void cleanup();
+ std::vector<std::shared_ptr<svl::SharedStringPool>> mPoolsToPurge;
+ Timer mTimer;
+ static SharedStringPoolPurge* self;
+ DECL_LINK(timerHandler, Timer*, void);
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/inc/webservicelink.hxx b/sc/source/core/inc/webservicelink.hxx
new file mode 100644
index 000000000..7f1d1e913
--- /dev/null
+++ b/sc/source/core/inc/webservicelink.hxx
@@ -0,0 +1,49 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include <sfx2/lnkbase.hxx>
+#include <svl/broadcast.hxx>
+
+namespace com::sun::star::uno
+{
+class Any;
+}
+
+class ScDocument;
+
+class ScWebServiceLink final : public ::sfx2::SvBaseLink, public SvtBroadcaster
+{
+private:
+ ScDocument* pDoc;
+ OUString aURL; // connection/ link data
+ bool bHasResult; // is set aResult is useful
+ OUString aResult;
+
+public:
+ ScWebServiceLink(ScDocument* pD, const OUString& rURL);
+ virtual ~ScWebServiceLink() override;
+
+ // SvBaseLink override:
+ virtual ::sfx2::SvBaseLink::UpdateResult DataChanged(const OUString& rMimeType,
+ const css::uno::Any& rValue) override;
+
+ // SvtBroadcaster override:
+ virtual void ListenersGone() override;
+
+ // for interpreter:
+
+ const OUString& GetResult() const { return aResult; }
+ bool HasResult() const { return bHasResult; }
+
+ const OUString& GetURL() const { return aURL; }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/sc/source/core/opencl/cl-test.ods b/sc/source/core/opencl/cl-test.ods
new file mode 100644
index 000000000..7e2bae4cb
--- /dev/null
+++ b/sc/source/core/opencl/cl-test.ods
Binary files differ
diff --git a/sc/source/core/opencl/formulagroupcl.cxx b/sc/source/core/opencl/formulagroupcl.cxx
new file mode 100644
index 000000000..f9ce31297
--- /dev/null
+++ b/sc/source/core/opencl/formulagroupcl.cxx
@@ -0,0 +1,4438 @@
+/* -*- 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 <formulagroup.hxx>
+#include <formulagroupcl.hxx>
+#include <document.hxx>
+#include <formulacell.hxx>
+#include <tokenarray.hxx>
+#include <compiler.hxx>
+#include <comphelper/random.hxx>
+#include <formula/vectortoken.hxx>
+#include <scmatrix.hxx>
+#include <sal/log.hxx>
+
+#include <opencl/openclwrapper.hxx>
+#include <opencl/OpenCLZone.hxx>
+
+#include "op_financial.hxx"
+#include "op_database.hxx"
+#include "op_math.hxx"
+#include "op_logical.hxx"
+#include "op_statistical.hxx"
+#include "op_array.hxx"
+#include "op_spreadsheet.hxx"
+#include "op_addin.hxx"
+
+#include <limits>
+
+#include <com/sun/star/sheet/FormulaLanguage.hpp>
+
+// FIXME: The idea that somebody would bother to (now and then? once a year? once a month?) manually
+// edit a source file and change the value of some #defined constant and run some ill-defined
+// "correctness test" is of course ludicrous. Either things are checked in normal unit tests, in
+// every 'make check', or not at all. The below comments are ridiculous.
+
+#define REDUCE_THRESHOLD 201 // set to 4 for correctness testing. priority 1
+#define UNROLLING_FACTOR 16 // set to 4 for correctness testing (if no reduce)
+
+const char* const publicFunc =
+ "\n"
+ "#define IllegalArgument 502\n"
+ "#define IllegalFPOperation 503 // #NUM!\n"
+ "#define NoValue 519 // #VALUE!\n"
+ "#define NoConvergence 523\n"
+ "#define DivisionByZero 532 // #DIV/0!\n"
+ "#define NOTAVAILABLE 0x7fff // #N/A\n"
+ "\n"
+ "double CreateDoubleError(ulong nErr)\n"
+ "{\n"
+ // At least nVidia on Linux and Intel on Windows seem to ignore the argument to nan(),
+ // so using that would not propagate the type of error, work that around
+ // by directly constructing the proper IEEE double NaN value
+ // TODO: maybe use a better way to detect such systems?
+ " return as_double(0x7FF8000000000000+nErr);\n"
+// " return nan(nErr);\n"
+ "}\n"
+ "\n"
+ "uint GetDoubleErrorValue(double fVal)\n"
+ "{\n"
+ " if (isfinite(fVal))\n"
+ " return 0;\n"
+ " if (isinf(fVal))\n"
+ " return IllegalFPOperation; // normal INF\n"
+ " if (as_ulong(fVal) & 0XFFFF0000u)\n"
+ " return NoValue; // just a normal NAN\n"
+ " return (as_ulong(fVal) & 0XFFFF); // any other error\n"
+ "}\n"
+ "\n"
+ "double fsum_count(double a, double b, __private int *p) {\n"
+ " bool t = isnan(a);\n"
+ " (*p) += t?0:1;\n"
+ " return t?b:a+b;\n"
+ "}\n"
+ "double fmin_count(double a, double b, __private int *p) {\n"
+ " double result = fmin(a, b);\n"
+ " bool t = isnan(result);\n"
+ " (*p) += t?0:1;\n"
+ " return result;\n"
+ "}\n"
+ "double fmax_count(double a, double b, __private int *p) {\n"
+ " double result = fmax(a, b);\n"
+ " bool t = isnan(result);\n"
+ " (*p) += t?0:1;\n"
+ " return result;\n"
+ "}\n"
+ "double fsum(double a, double b) { return isnan(a)?b:a+b; }\n"
+ "double legalize(double a, double b) { return isnan(a)?b:a;}\n"
+ "double fsub(double a, double b) { return a-b; }\n"
+ "double fdiv(double a, double b) { return a/b; }\n"
+ "double strequal(unsigned a, unsigned b) { return (a==b)?1.0:0; }\n"
+ "int is_representable_integer(double a) {\n"
+ " long kMaxInt = (1L << 53) - 1;\n"
+ " if (a <= as_double(kMaxInt))\n"
+ " {\n"
+ " long nInt = as_long(a);\n"
+ " double fInt;\n"
+ " return (nInt <= kMaxInt &&\n"
+ " (!((fInt = as_double(nInt)) < a) && !(fInt > a)));\n"
+ " }\n"
+ " return 0;\n"
+ "}\n"
+ "int approx_equal(double a, double b) {\n"
+ " double e48 = 1.0 / (16777216.0 * 16777216.0);\n"
+ " double e44 = e48 * 16.0;\n"
+ " if (a == b)\n"
+ " return 1;\n"
+ " if (a == 0.0 || b == 0.0)\n"
+ " return 0;\n"
+ " double d = fabs(a - b);\n"
+ " if (!isfinite(d))\n"
+ " return 0; // Nan or Inf involved\n"
+ " if (d > ((a = fabs(a)) * e44) || d > ((b = fabs(b)) * e44))\n"
+ " return 0;\n"
+ " if (is_representable_integer(d) && is_representable_integer(a) && is_representable_integer(b))\n"
+ " return 0; // special case for representable integers.\n"
+ " return (d < a * e48 && d < b * e48);\n"
+ "}\n"
+ "double fsum_approx(double a, double b) {\n"
+ " if ( ((a < 0.0 && b > 0.0) || (b < 0.0 && a > 0.0))\n"
+ " && approx_equal( a, -b ) )\n"
+ " return 0.0;\n"
+ " return a + b;\n"
+ "}\n"
+ "double fsub_approx(double a, double b) {\n"
+ " if ( ((a < 0.0 && b < 0.0) || (a > 0.0 && b > 0.0)) && approx_equal( a, b ) )\n"
+ " return 0.0;\n"
+ " return a - b;\n"
+ "}\n"
+ ;
+
+#include <vector>
+#include <map>
+#include <iostream>
+#include <algorithm>
+
+#include <rtl/digest.h>
+
+#include <memory>
+
+using namespace formula;
+
+namespace sc::opencl {
+
+namespace {
+
+std::string linenumberify(const std::string& s)
+{
+ std::stringstream ss;
+ int linenumber = 1;
+ size_t start = 0;
+ size_t newline;
+ while ((newline = s.find('\n', start)) != std::string::npos)
+ {
+ ss << "/*" << std::setw(4) << linenumber++ << "*/ " << s.substr(start, newline-start+1);
+ start = newline + 1;
+ }
+ if (start < s.size())
+ ss << "/*" << std::setw(4) << linenumber++ << "*/ " << s.substr(start, std::string::npos);
+ return ss.str();
+}
+
+bool AllStringsAreNull(const rtl_uString* const* pStringArray, size_t nLength)
+{
+ if (pStringArray == nullptr)
+ return true;
+
+ for (size_t i = 0; i < nLength; i++)
+ if (pStringArray[i] != nullptr)
+ return false;
+
+ return true;
+}
+
+OUString LimitedString( const OUString& str )
+{
+ if( str.getLength() < 20 )
+ return "\"" + str + "\"";
+ else
+ return OUString::Concat("\"") + str.subView( 0, 20 ) + "\"...";
+}
+
+// Returns formatted contents of the data (possibly shortened), to be used in debug output.
+OUString DebugPeekData(const FormulaToken* ref, int doubleRefIndex = 0)
+{
+ if (ref->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast<const formula::SingleVectorRefToken*>(ref);
+ OUStringBuffer buf = "SingleRef {";
+ for( size_t i = 0; i < std::min< size_t >( 4, pSVR->GetArrayLength()); ++i )
+ {
+ if( i != 0 )
+ buf.append( "," );
+ if( pSVR->GetArray().mpNumericArray != nullptr )
+ buf.append( pSVR->GetArray().mpNumericArray[ i ] );
+ else if( pSVR->GetArray().mpStringArray != nullptr )
+ buf.append( LimitedString( OUString( pSVR->GetArray().mpStringArray[ i ] )));
+ }
+ if( pSVR->GetArrayLength() > 4 )
+ buf.append( ",..." );
+ buf.append( "}" );
+ return buf.makeStringAndClear();
+ }
+ else if (ref->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken*>(ref);
+ OUStringBuffer buf = "DoubleRef {";
+ for( size_t i = 0; i < std::min< size_t >( 4, pDVR->GetArrayLength()); ++i )
+ {
+ if( i != 0 )
+ buf.append( "," );
+ if( pDVR->GetArrays()[doubleRefIndex].mpNumericArray != nullptr )
+ buf.append( pDVR->GetArrays()[doubleRefIndex].mpNumericArray[ i ] );
+ else if( pDVR->GetArrays()[doubleRefIndex].mpStringArray != nullptr )
+ buf.append( LimitedString( OUString( pDVR->GetArrays()[doubleRefIndex].mpStringArray[ i ] )));
+ }
+ if( pDVR->GetArrayLength() > 4 )
+ buf.append( ",..." );
+ buf.append( "}" );
+ return buf.makeStringAndClear();
+ }
+ else if (ref->GetType() == formula::svString)
+ {
+ return "String " + LimitedString( ref->GetString().getString());
+ }
+ else if (ref->GetType() == formula::svDouble)
+ {
+ return OUString::number(ref->GetDouble());
+ }
+ else
+ {
+ return "?";
+ }
+}
+
+// Returns formatted contents of a doubles buffer, to be used in debug output.
+OUString DebugPeekDoubles(const double* data, int size)
+{
+ OUStringBuffer buf = "{";
+ for( int i = 0; i < std::min( 4, size ); ++i )
+ {
+ if( i != 0 )
+ buf.append( "," );
+ buf.append( data[ i ] );
+ }
+ if( size > 4 )
+ buf.append( ",..." );
+ buf.append( "}" );
+ return buf.makeStringAndClear();
+}
+
+} // anonymous namespace
+
+/// Map the buffer used by an argument and do necessary argument setting
+size_t VectorRef::Marshal( cl_kernel k, int argno, int, cl_program )
+{
+ OpenCLZone zone;
+ FormulaToken* ref = mFormulaTree->GetFormulaToken();
+ double* pHostBuffer = nullptr;
+ size_t szHostBuffer = 0;
+ if (ref->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast<const formula::SingleVectorRefToken*>(ref);
+
+ SAL_INFO("sc.opencl", "SingleVectorRef len=" << pSVR->GetArrayLength() << " mpNumericArray=" << pSVR->GetArray().mpNumericArray << " (mpStringArray=" << pSVR->GetArray().mpStringArray << ")");
+
+ pHostBuffer = const_cast<double*>(pSVR->GetArray().mpNumericArray);
+ szHostBuffer = pSVR->GetArrayLength() * sizeof(double);
+ }
+ else if (ref->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken*>(ref);
+
+ SAL_INFO("sc.opencl", "DoubleVectorRef index=" << mnIndex << " len=" << pDVR->GetArrayLength() << " mpNumericArray=" << pDVR->GetArrays()[mnIndex].mpNumericArray << " (mpStringArray=" << pDVR->GetArrays()[mnIndex].mpStringArray << ")");
+
+ pHostBuffer = const_cast<double*>(
+ pDVR->GetArrays()[mnIndex].mpNumericArray);
+ szHostBuffer = pDVR->GetArrayLength() * sizeof(double);
+ }
+ else
+ {
+ throw Unhandled(__FILE__, __LINE__);
+ }
+
+ openclwrapper::KernelEnv kEnv;
+ openclwrapper::setKernelEnv(&kEnv);
+ cl_int err;
+ if (pHostBuffer)
+ {
+ mpClmem = clCreateBuffer(kEnv.mpkContext,
+ cl_mem_flags(CL_MEM_READ_ONLY) | CL_MEM_USE_HOST_PTR,
+ szHostBuffer,
+ pHostBuffer, &err);
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clCreateBuffer", err, __FILE__, __LINE__);
+ SAL_INFO("sc.opencl", "Created buffer " << mpClmem << " size " << szHostBuffer << " using host buffer " << pHostBuffer);
+ }
+ else
+ {
+ if (szHostBuffer == 0)
+ szHostBuffer = sizeof(double); // a dummy small value
+ // Marshal as a buffer of NANs
+ mpClmem = clCreateBuffer(kEnv.mpkContext,
+ cl_mem_flags(CL_MEM_READ_ONLY) | CL_MEM_ALLOC_HOST_PTR,
+ szHostBuffer, nullptr, &err);
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clCreateBuffer", err, __FILE__, __LINE__);
+ SAL_INFO("sc.opencl", "Created buffer " << mpClmem << " size " << szHostBuffer);
+
+ double* pNanBuffer = static_cast<double*>(clEnqueueMapBuffer(
+ kEnv.mpkCmdQueue, mpClmem, CL_TRUE, CL_MAP_WRITE, 0,
+ szHostBuffer, 0, nullptr, nullptr, &err));
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clEnqueueMapBuffer", err, __FILE__, __LINE__);
+
+ for (size_t i = 0; i < szHostBuffer / sizeof(double); i++)
+ pNanBuffer[i] = std::numeric_limits<double>::quiet_NaN();
+ err = clEnqueueUnmapMemObject(kEnv.mpkCmdQueue, mpClmem,
+ pNanBuffer, 0, nullptr, nullptr);
+ // FIXME: Is it intentional to not throw an OpenCLError even if the clEnqueueUnmapMemObject() fails?
+ if (CL_SUCCESS != err)
+ SAL_WARN("sc.opencl", "clEnqueueUnmapMemObject failed: " << openclwrapper::errorString(err));
+ }
+
+ SAL_INFO("sc.opencl", "Kernel " << k << " arg " << argno << ": cl_mem: " << mpClmem << " (" << DebugPeekData(ref, mnIndex) << ")");
+ err = clSetKernelArg(k, argno, sizeof(cl_mem), static_cast<void*>(&mpClmem));
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clSetKernelArg", err, __FILE__, __LINE__);
+ return 1;
+}
+
+/// Arguments that are actually compile-time constant string
+/// Currently, only the hash is passed.
+/// TBD(IJSUNG): pass also length and the actual string if there is a
+/// hash function collision
+
+/// FIXME: This idea of passing of hashes of uppercased strings into OpenCL code is fairly potent
+/// crack. It is hopefully not used at all any more, but noticing that there are string arguments
+/// automatically disables use of OpenCL for a formula group. If at some point there are resources
+/// to drain the OpenCL swamp, this should go away.
+
+namespace {
+
+class ConstStringArgument : public DynamicKernelArgument
+{
+public:
+ ConstStringArgument( const ScCalcConfig& config, const std::string& s,
+ const FormulaTreeNodeRef& ft ) :
+ DynamicKernelArgument(config, s, ft) { }
+ /// Generate declaration
+ virtual void GenDecl( std::stringstream& ss ) const override
+ {
+ ss << "unsigned " << mSymName;
+ }
+ virtual void GenDeclRef( std::stringstream& ss ) const override
+ {
+ ss << GenSlidingWindowDeclRef();
+ }
+ virtual void GenSlidingWindowDecl( std::stringstream& ss ) const override
+ {
+ GenDecl(ss);
+ }
+ virtual std::string GenSlidingWindowDeclRef( bool = false ) const override
+ {
+ std::stringstream ss;
+ if (GetFormulaToken()->GetType() != formula::svString)
+ throw Unhandled(__FILE__, __LINE__);
+ FormulaToken* Tok = GetFormulaToken();
+ ss << Tok->GetString().getString().toAsciiUpperCase().hashCode() << "U";
+ return ss.str();
+ }
+ virtual size_t GetWindowSize() const override
+ {
+ return 1;
+ }
+ /// Pass the 32-bit hash of the string to the kernel
+ virtual size_t Marshal( cl_kernel k, int argno, int, cl_program ) override
+ {
+ OpenCLZone zone;
+ FormulaToken* ref = mFormulaTree->GetFormulaToken();
+ cl_uint hashCode = 0;
+ if (ref->GetType() != formula::svString)
+ {
+ throw Unhandled(__FILE__, __LINE__);
+ }
+
+ const OUString s = ref->GetString().getString().toAsciiUpperCase();
+ hashCode = s.hashCode();
+
+ // Pass the scalar result back to the rest of the formula kernel
+ SAL_INFO("sc.opencl", "Kernel " << k << " arg " << argno << ": cl_uint: " << hashCode << "(" << DebugPeekData(ref) << ")" );
+ cl_int err = clSetKernelArg(k, argno, sizeof(cl_uint), static_cast<void*>(&hashCode));
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clSetKernelArg", err, __FILE__, __LINE__);
+ return 1;
+ }
+};
+
+/// Arguments that are actually compile-time constants
+class DynamicKernelConstantArgument : public DynamicKernelArgument
+{
+public:
+ DynamicKernelConstantArgument( const ScCalcConfig& config, const std::string& s,
+ const FormulaTreeNodeRef& ft ) :
+ DynamicKernelArgument(config, s, ft) { }
+ /// Generate declaration
+ virtual void GenDecl( std::stringstream& ss ) const override
+ {
+ ss << "double " << mSymName;
+ }
+ virtual void GenDeclRef( std::stringstream& ss ) const override
+ {
+ ss << mSymName;
+ }
+ virtual void GenSlidingWindowDecl( std::stringstream& ss ) const override
+ {
+ GenDecl(ss);
+ }
+ virtual std::string GenSlidingWindowDeclRef( bool = false ) const override
+ {
+ if (GetFormulaToken()->GetType() != formula::svDouble)
+ throw Unhandled(__FILE__, __LINE__);
+ return mSymName;
+ }
+ virtual size_t GetWindowSize() const override
+ {
+ return 1;
+ }
+ double GetDouble() const
+ {
+ FormulaToken* Tok = GetFormulaToken();
+ if (Tok->GetType() != formula::svDouble)
+ throw Unhandled(__FILE__, __LINE__);
+ return Tok->GetDouble();
+ }
+ /// Create buffer and pass the buffer to a given kernel
+ virtual size_t Marshal( cl_kernel k, int argno, int, cl_program ) override
+ {
+ OpenCLZone zone;
+ double tmp = GetDouble();
+ // Pass the scalar result back to the rest of the formula kernel
+ SAL_INFO("sc.opencl", "Kernel " << k << " arg " << argno << ": double: " << tmp);
+ cl_int err = clSetKernelArg(k, argno, sizeof(double), static_cast<void*>(&tmp));
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clSetKernelArg", err, __FILE__, __LINE__);
+ return 1;
+ }
+};
+
+class DynamicKernelPiArgument : public DynamicKernelArgument
+{
+public:
+ DynamicKernelPiArgument( const ScCalcConfig& config, const std::string& s,
+ const FormulaTreeNodeRef& ft ) :
+ DynamicKernelArgument(config, s, ft) { }
+ /// Generate declaration
+ virtual void GenDecl( std::stringstream& ss ) const override
+ {
+ ss << "double " << mSymName;
+ }
+ virtual void GenDeclRef( std::stringstream& ss ) const override
+ {
+ ss << "3.14159265358979";
+ }
+ virtual void GenSlidingWindowDecl( std::stringstream& ss ) const override
+ {
+ GenDecl(ss);
+ }
+ virtual std::string GenSlidingWindowDeclRef( bool = false ) const override
+ {
+ return mSymName;
+ }
+ virtual size_t GetWindowSize() const override
+ {
+ return 1;
+ }
+ /// Create buffer and pass the buffer to a given kernel
+ virtual size_t Marshal( cl_kernel k, int argno, int, cl_program ) override
+ {
+ OpenCLZone zone;
+ double tmp = 0.0;
+ // Pass the scalar result back to the rest of the formula kernel
+ SAL_INFO("sc.opencl", "Kernel " << k << " arg " << argno << ": double: " << tmp << " (PI)");
+ cl_int err = clSetKernelArg(k, argno, sizeof(double), static_cast<void*>(&tmp));
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clSetKernelArg", err, __FILE__, __LINE__);
+ return 1;
+ }
+};
+
+class DynamicKernelRandomArgument : public DynamicKernelArgument
+{
+public:
+ DynamicKernelRandomArgument( const ScCalcConfig& config, const std::string& s,
+ const FormulaTreeNodeRef& ft ) :
+ DynamicKernelArgument(config, s, ft) { }
+ /// Generate declaration
+ virtual void GenDecl( std::stringstream& ss ) const override
+ {
+ ss << "double " << mSymName;
+ }
+ virtual void GenDeclRef( std::stringstream& ss ) const override
+ {
+ ss << mSymName;
+ }
+ virtual void GenSlidingWindowDecl( std::stringstream& ss ) const override
+ {
+ ss << "int " << mSymName;
+ }
+ virtual std::string GenSlidingWindowDeclRef( bool = false ) const override
+ {
+ return mSymName + "_Random(" + mSymName + ")";
+ }
+ virtual void GenSlidingWindowFunction( std::stringstream& ss ) override
+ {
+ // This string is from the pi_opencl_kernel.i file as
+ // generated when building the Random123 examples. Unused
+ // stuff has been removed, and the actual kernel is not the
+ // same as in the totally different use case of that example,
+ // of course. Only the code that calculates the counter-based
+ // random number and what it needs is left.
+ ss << "\
+\n\
+#ifndef DEFINED_RANDOM123_STUFF\n\
+#define DEFINED_RANDOM123_STUFF\n\
+\n\
+/*\n\
+Copyright 2010-2011, D. E. Shaw Research.\n\
+All rights reserved.\n\
+\n\
+Redistribution and use in source and binary forms, with or without\n\
+modification, are permitted provided that the following conditions are\n\
+met:\n\
+\n\
+* Redistributions of source code must retain the above copyright\n\
+ notice, this list of conditions, and the following disclaimer.\n\
+\n\
+* Redistributions in binary form must reproduce the above copyright\n\
+ notice, this list of conditions, and the following disclaimer in the\n\
+ documentation and/or other materials provided with the distribution.\n\
+\n\
+* Neither the name of D. E. Shaw Research nor the names of its\n\
+ contributors may be used to endorse or promote products derived from\n\
+ this software without specific prior written permission.\n\
+\n\
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\
+\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n\
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n\
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n\
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n\
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n\
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n\
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n\
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n\
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n\
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\
+*/\n\
+\n\
+typedef uint uint32_t;\n\
+struct r123array2x32\n\
+{\n\
+ uint32_t v[2];\n\
+};\n\
+enum r123_enum_threefry32x2\n\
+{\n\
+ R_32x2_0_0 = 13,\n\
+ R_32x2_1_0 = 15,\n\
+ R_32x2_2_0 = 26,\n\
+ R_32x2_3_0 = 6,\n\
+ R_32x2_4_0 = 17,\n\
+ R_32x2_5_0 = 29,\n\
+ R_32x2_6_0 = 16,\n\
+ R_32x2_7_0 = 24\n\
+};\n\
+inline uint32_t RotL_32 (uint32_t x, unsigned int N)\n\
+ __attribute__ ((always_inline));\n\
+inline uint32_t\n\
+RotL_32 (uint32_t x, unsigned int N)\n\
+{\n\
+ return (x << (N & 31)) | (x >> ((32 - N) & 31));\n\
+}\n\
+\n\
+typedef struct r123array2x32 threefry2x32_ctr_t;\n\
+typedef struct r123array2x32 threefry2x32_key_t;\n\
+typedef struct r123array2x32 threefry2x32_ukey_t;\n\
+inline threefry2x32_key_t\n\
+threefry2x32keyinit (threefry2x32_ukey_t uk)\n\
+{\n\
+ return uk;\n\
+}\n\
+\n\
+inline threefry2x32_ctr_t threefry2x32_R (unsigned int Nrounds,\n\
+ threefry2x32_ctr_t in,\n\
+ threefry2x32_key_t k)\n\
+ __attribute__ ((always_inline));\n\
+inline threefry2x32_ctr_t\n\
+threefry2x32_R (unsigned int Nrounds, threefry2x32_ctr_t in,\n\
+ threefry2x32_key_t k)\n\
+{\n\
+ threefry2x32_ctr_t X;\n\
+ uint32_t ks[2 + 1];\n\
+ int i;\n\
+ ks[2] = 0x1BD11BDA;\n\
+ for (i = 0; i < 2; i++) {\n\
+ ks[i] = k.v[i];\n\
+ X.v[i] = in.v[i];\n\
+ ks[2] ^= k.v[i];\n\
+ }\n\
+ X.v[0] += ks[0];\n\
+ X.v[1] += ks[1];\n\
+ if (Nrounds > 0) {\n\
+ X.v[0] += X.v[1];\n\
+ X.v[1] = RotL_32 (X.v[1], R_32x2_0_0);\n\
+ X.v[1] ^= X.v[0];\n\
+ }\n\
+ if (Nrounds > 1) {\n\
+ X.v[0] += X.v[1];\n\
+ X.v[1] = RotL_32 (X.v[1], R_32x2_1_0);\n\
+ X.v[1] ^= X.v[0];\n\
+ }\n\
+ if (Nrounds > 2) {\n\
+ X.v[0] += X.v[1];\n\
+ X.v[1] = RotL_32 (X.v[1], R_32x2_2_0);\n\
+ X.v[1] ^= X.v[0];\n\
+ }\n\
+ if (Nrounds > 3) {\n\
+ X.v[0] += X.v[1];\n\
+ X.v[1] = RotL_32 (X.v[1], R_32x2_3_0);\n\
+ X.v[1] ^= X.v[0];\n\
+ }\n\
+ if (Nrounds > 3) {\n\
+ X.v[0] += ks[1];\n\
+ X.v[1] += ks[2];\n\
+ X.v[1] += 1;\n\
+ }\n\
+ if (Nrounds > 4) {\n\
+ X.v[0] += X.v[1];\n\
+ X.v[1] = RotL_32 (X.v[1], R_32x2_4_0);\n\
+ X.v[1] ^= X.v[0];\n\
+ }\n\
+ if (Nrounds > 5) {\n\
+ X.v[0] += X.v[1];\n\
+ X.v[1] = RotL_32 (X.v[1], R_32x2_5_0);\n\
+ X.v[1] ^= X.v[0];\n\
+ }\n\
+ if (Nrounds > 6) {\n\
+ X.v[0] += X.v[1];\n\
+ X.v[1] = RotL_32 (X.v[1], R_32x2_6_0);\n\
+ X.v[1] ^= X.v[0];\n\
+ }\n\
+ if (Nrounds > 7) {\n\
+ X.v[0] += X.v[1];\n\
+ X.v[1] = RotL_32 (X.v[1], R_32x2_7_0);\n\
+ X.v[1] ^= X.v[0];\n\
+ }\n\
+ if (Nrounds > 7) {\n\
+ X.v[0] += ks[2];\n\
+ X.v[1] += ks[0];\n\
+ X.v[1] += 2;\n\
+ }\n\
+ if (Nrounds > 8) {\n\
+ X.v[0] += X.v[1];\n\
+ X.v[1] = RotL_32 (X.v[1], R_32x2_0_0);\n\
+ X.v[1] ^= X.v[0];\n\
+ }\n\
+ if (Nrounds > 9) {\n\
+ X.v[0] += X.v[1];\n\
+ X.v[1] = RotL_32 (X.v[1], R_32x2_1_0);\n\
+ X.v[1] ^= X.v[0];\n\
+ }\n\
+ if (Nrounds > 10) {\n\
+ X.v[0] += X.v[1];\n\
+ X.v[1] = RotL_32 (X.v[1], R_32x2_2_0);\n\
+ X.v[1] ^= X.v[0];\n\
+ }\n\
+ if (Nrounds > 11) {\n\
+ X.v[0] += X.v[1];\n\
+ X.v[1] = RotL_32 (X.v[1], R_32x2_3_0);\n\
+ X.v[1] ^= X.v[0];\n\
+ }\n\
+ if (Nrounds > 11) {\n\
+ X.v[0] += ks[0];\n\
+ X.v[1] += ks[1];\n\
+ X.v[1] += 3;\n\
+ }\n\
+ if (Nrounds > 12) {\n\
+ X.v[0] += X.v[1];\n\
+ X.v[1] = RotL_32 (X.v[1], R_32x2_4_0);\n\
+ X.v[1] ^= X.v[0];\n\
+ }\n\
+ if (Nrounds > 13) {\n\
+ X.v[0] += X.v[1];\n\
+ X.v[1] = RotL_32 (X.v[1], R_32x2_5_0);\n\
+ X.v[1] ^= X.v[0];\n\
+ }\n\
+ if (Nrounds > 14) {\n\
+ X.v[0] += X.v[1];\n\
+ X.v[1] = RotL_32 (X.v[1], R_32x2_6_0);\n\
+ X.v[1] ^= X.v[0];\n\
+ }\n\
+ if (Nrounds > 15) {\n\
+ X.v[0] += X.v[1];\n\
+ X.v[1] = RotL_32 (X.v[1], R_32x2_7_0);\n\
+ X.v[1] ^= X.v[0];\n\
+ }\n\
+ if (Nrounds > 15) {\n\
+ X.v[0] += ks[1];\n\
+ X.v[1] += ks[2];\n\
+ X.v[1] += 4;\n\
+ }\n\
+ if (Nrounds > 16) {\n\
+ X.v[0] += X.v[1];\n\
+ X.v[1] = RotL_32 (X.v[1], R_32x2_0_0);\n\
+ X.v[1] ^= X.v[0];\n\
+ }\n\
+ if (Nrounds > 17) {\n\
+ X.v[0] += X.v[1];\n\
+ X.v[1] = RotL_32 (X.v[1], R_32x2_1_0);\n\
+ X.v[1] ^= X.v[0];\n\
+ }\n\
+ if (Nrounds > 18) {\n\
+ X.v[0] += X.v[1];\n\
+ X.v[1] = RotL_32 (X.v[1], R_32x2_2_0);\n\
+ X.v[1] ^= X.v[0];\n\
+ }\n\
+ if (Nrounds > 19) {\n\
+ X.v[0] += X.v[1];\n\
+ X.v[1] = RotL_32 (X.v[1], R_32x2_3_0);\n\
+ X.v[1] ^= X.v[0];\n\
+ }\n\
+ if (Nrounds > 19) {\n\
+ X.v[0] += ks[2];\n\
+ X.v[1] += ks[0];\n\
+ X.v[1] += 5;\n\
+ }\n\
+ if (Nrounds > 20) {\n\
+ X.v[0] += X.v[1];\n\
+ X.v[1] = RotL_32 (X.v[1], R_32x2_4_0);\n\
+ X.v[1] ^= X.v[0];\n\
+ }\n\
+ if (Nrounds > 21) {\n\
+ X.v[0] += X.v[1];\n\
+ X.v[1] = RotL_32 (X.v[1], R_32x2_5_0);\n\
+ X.v[1] ^= X.v[0];\n\
+ }\n\
+ if (Nrounds > 22) {\n\
+ X.v[0] += X.v[1];\n\
+ X.v[1] = RotL_32 (X.v[1], R_32x2_6_0);\n\
+ X.v[1] ^= X.v[0];\n\
+ }\n\
+ if (Nrounds > 23) {\n\
+ X.v[0] += X.v[1];\n\
+ X.v[1] = RotL_32 (X.v[1], R_32x2_7_0);\n\
+ X.v[1] ^= X.v[0];\n\
+ }\n\
+ if (Nrounds > 23) {\n\
+ X.v[0] += ks[0];\n\
+ X.v[1] += ks[1];\n\
+ X.v[1] += 6;\n\
+ }\n\
+ if (Nrounds > 24) {\n\
+ X.v[0] += X.v[1];\n\
+ X.v[1] = RotL_32 (X.v[1], R_32x2_0_0);\n\
+ X.v[1] ^= X.v[0];\n\
+ }\n\
+ if (Nrounds > 25) {\n\
+ X.v[0] += X.v[1];\n\
+ X.v[1] = RotL_32 (X.v[1], R_32x2_1_0);\n\
+ X.v[1] ^= X.v[0];\n\
+ }\n\
+ if (Nrounds > 26) {\n\
+ X.v[0] += X.v[1];\n\
+ X.v[1] = RotL_32 (X.v[1], R_32x2_2_0);\n\
+ X.v[1] ^= X.v[0];\n\
+ }\n\
+ if (Nrounds > 27) {\n\
+ X.v[0] += X.v[1];\n\
+ X.v[1] = RotL_32 (X.v[1], R_32x2_3_0);\n\
+ X.v[1] ^= X.v[0];\n\
+ }\n\
+ if (Nrounds > 27) {\n\
+ X.v[0] += ks[1];\n\
+ X.v[1] += ks[2];\n\
+ X.v[1] += 7;\n\
+ }\n\
+ if (Nrounds > 28) {\n\
+ X.v[0] += X.v[1];\n\
+ X.v[1] = RotL_32 (X.v[1], R_32x2_4_0);\n\
+ X.v[1] ^= X.v[0];\n\
+ }\n\
+ if (Nrounds > 29) {\n\
+ X.v[0] += X.v[1];\n\
+ X.v[1] = RotL_32 (X.v[1], R_32x2_5_0);\n\
+ X.v[1] ^= X.v[0];\n\
+ }\n\
+ if (Nrounds > 30) {\n\
+ X.v[0] += X.v[1];\n\
+ X.v[1] = RotL_32 (X.v[1], R_32x2_6_0);\n\
+ X.v[1] ^= X.v[0];\n\
+ }\n\
+ if (Nrounds > 31) {\n\
+ X.v[0] += X.v[1];\n\
+ X.v[1] = RotL_32 (X.v[1], R_32x2_7_0);\n\
+ X.v[1] ^= X.v[0];\n\
+ }\n\
+ if (Nrounds > 31) {\n\
+ X.v[0] += ks[2];\n\
+ X.v[1] += ks[0];\n\
+ X.v[1] += 8;\n\
+ }\n\
+ return X;\n\
+}\n\
+\n\
+enum r123_enum_threefry2x32\n\
+{ threefry2x32_rounds = 20 };\n\
+inline threefry2x32_ctr_t threefry2x32 (threefry2x32_ctr_t in,\n\
+ threefry2x32_key_t k)\n\
+ __attribute__ ((always_inline));\n\
+inline threefry2x32_ctr_t\n\
+threefry2x32 (threefry2x32_ctr_t in, threefry2x32_key_t k)\n\
+{\n\
+ return threefry2x32_R (threefry2x32_rounds, in, k);\n\
+}\n\
+#endif\n\
+\n\
+";
+ ss << "double " << mSymName << "_Random (int seed)\n\
+{\n\
+ unsigned tid = get_global_id(0);\n\
+ threefry2x32_key_t k = { {tid, 0xdecafbad} };\n\
+ threefry2x32_ctr_t c = { {seed, 0xf00dcafe} };\n\
+ c = threefry2x32_R(threefry2x32_rounds, c, k);\n\
+ const double factor = 1./(" << SAL_MAX_UINT32 << ".0 + 1.0);\n\
+ const double halffactor = 0.5*factor;\n\
+ return c.v[0] * factor + halffactor;\n\
+}\n\
+";
+ }
+ virtual size_t GetWindowSize() const override
+ {
+ return 1;
+ }
+ /// Create buffer and pass the buffer to a given kernel
+ virtual size_t Marshal( cl_kernel k, int argno, int, cl_program ) override
+ {
+ OpenCLZone zone;
+ cl_int seed = comphelper::rng::uniform_int_distribution(0, SAL_MAX_INT32);
+ // Pass the scalar result back to the rest of the formula kernel
+ SAL_INFO("sc.opencl", "Kernel " << k << " arg " << argno << ": cl_int: " << seed << "(RANDOM)");
+ cl_int err = clSetKernelArg(k, argno, sizeof(cl_int), static_cast<void*>(&seed));
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clSetKernelArg", err, __FILE__, __LINE__);
+ return 1;
+ }
+};
+
+/// A vector of strings
+class DynamicKernelStringArgument : public VectorRef
+{
+public:
+ DynamicKernelStringArgument( const ScCalcConfig& config, const std::string& s,
+ const FormulaTreeNodeRef& ft, int index = 0 ) :
+ VectorRef(config, s, ft, index) { }
+
+ virtual void GenSlidingWindowFunction( std::stringstream& ) override { }
+ /// Generate declaration
+ virtual void GenDecl( std::stringstream& ss ) const override
+ {
+ ss << "__global unsigned int *" << mSymName;
+ }
+ virtual void GenSlidingWindowDecl( std::stringstream& ss ) const override
+ {
+ DynamicKernelStringArgument::GenDecl(ss);
+ }
+ virtual size_t Marshal( cl_kernel, int, int, cl_program ) override;
+};
+
+}
+
+/// Marshal a string vector reference
+size_t DynamicKernelStringArgument::Marshal( cl_kernel k, int argno, int, cl_program )
+{
+ OpenCLZone zone;
+ FormulaToken* ref = mFormulaTree->GetFormulaToken();
+
+ openclwrapper::KernelEnv kEnv;
+ openclwrapper::setKernelEnv(&kEnv);
+ cl_int err;
+ formula::VectorRefArray vRef;
+ size_t nStrings = 0;
+ if (ref->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast<const formula::SingleVectorRefToken*>(ref);
+ nStrings = pSVR->GetArrayLength();
+ vRef = pSVR->GetArray();
+ }
+ else if (ref->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken*>(ref);
+ nStrings = pDVR->GetArrayLength();
+ vRef = pDVR->GetArrays()[mnIndex];
+ }
+ size_t szHostBuffer = nStrings * sizeof(cl_int);
+ cl_uint* pHashBuffer = nullptr;
+
+ if (vRef.mpStringArray != nullptr)
+ {
+ // Marshal strings. Right now we pass hashes of these string
+ mpClmem = clCreateBuffer(kEnv.mpkContext,
+ cl_mem_flags(CL_MEM_READ_ONLY) | CL_MEM_ALLOC_HOST_PTR,
+ szHostBuffer, nullptr, &err);
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clCreateBuffer", err, __FILE__, __LINE__);
+ SAL_INFO("sc.opencl", "Created buffer " << mpClmem << " size " << szHostBuffer);
+
+ pHashBuffer = static_cast<cl_uint*>(clEnqueueMapBuffer(
+ kEnv.mpkCmdQueue, mpClmem, CL_TRUE, CL_MAP_WRITE, 0,
+ szHostBuffer, 0, nullptr, nullptr, &err));
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clEnqueueMapBuffer", err, __FILE__, __LINE__);
+
+ for (size_t i = 0; i < nStrings; i++)
+ {
+ if (vRef.mpStringArray[i])
+ {
+ const OUString tmp(vRef.mpStringArray[i]);
+ pHashBuffer[i] = tmp.hashCode();
+ }
+ else
+ {
+ pHashBuffer[i] = 0;
+ }
+ }
+ }
+ else
+ {
+ if (nStrings == 0)
+ szHostBuffer = sizeof(cl_int); // a dummy small value
+ // Marshal as a buffer of NANs
+ mpClmem = clCreateBuffer(kEnv.mpkContext,
+ cl_mem_flags(CL_MEM_READ_ONLY) | CL_MEM_ALLOC_HOST_PTR,
+ szHostBuffer, nullptr, &err);
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clCreateBuffer", err, __FILE__, __LINE__);
+ SAL_INFO("sc.opencl", "Created buffer " << mpClmem << " size " << szHostBuffer);
+
+ pHashBuffer = static_cast<cl_uint*>(clEnqueueMapBuffer(
+ kEnv.mpkCmdQueue, mpClmem, CL_TRUE, CL_MAP_WRITE, 0,
+ szHostBuffer, 0, nullptr, nullptr, &err));
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clEnqueueMapBuffer", err, __FILE__, __LINE__);
+
+ for (size_t i = 0; i < szHostBuffer / sizeof(cl_int); i++)
+ pHashBuffer[i] = 0;
+ }
+ err = clEnqueueUnmapMemObject(kEnv.mpkCmdQueue, mpClmem,
+ pHashBuffer, 0, nullptr, nullptr);
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clEnqueueUnmapMemObject", err, __FILE__, __LINE__);
+
+ SAL_INFO("sc.opencl", "Kernel " << k << " arg " << argno << ": cl_mem: " << mpClmem << " (" << DebugPeekData(ref,mnIndex) << ")");
+ err = clSetKernelArg(k, argno, sizeof(cl_mem), static_cast<void*>(&mpClmem));
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clSetKernelArg", err, __FILE__, __LINE__);
+ return 1;
+}
+
+namespace {
+
+/// A mixed string/numeric vector
+class DynamicKernelMixedArgument : public VectorRef
+{
+public:
+ DynamicKernelMixedArgument( const ScCalcConfig& config, const std::string& s,
+ const FormulaTreeNodeRef& ft ) :
+ VectorRef(config, s, ft), mStringArgument(config, s + "s", ft) { }
+ virtual void GenSlidingWindowDecl( std::stringstream& ss ) const override
+ {
+ VectorRef::GenSlidingWindowDecl(ss);
+ ss << ", ";
+ mStringArgument.GenSlidingWindowDecl(ss);
+ }
+ virtual void GenSlidingWindowFunction( std::stringstream& ) override { }
+ /// Generate declaration
+ virtual void GenDecl( std::stringstream& ss ) const override
+ {
+ VectorRef::GenDecl(ss);
+ ss << ", ";
+ mStringArgument.GenDecl(ss);
+ }
+ virtual void GenDeclRef( std::stringstream& ss ) const override
+ {
+ VectorRef::GenDeclRef(ss);
+ ss << ",";
+ mStringArgument.GenDeclRef(ss);
+ }
+ virtual std::string GenSlidingWindowDeclRef( bool nested ) const override
+ {
+ std::stringstream ss;
+ ss << "(!isnan(" << VectorRef::GenSlidingWindowDeclRef();
+ ss << ")?" << VectorRef::GenSlidingWindowDeclRef();
+ ss << ":" << mStringArgument.GenSlidingWindowDeclRef(nested);
+ ss << ")";
+ return ss.str();
+ }
+ virtual std::string GenDoubleSlidingWindowDeclRef( bool = false ) const override
+ {
+ std::stringstream ss;
+ ss << VectorRef::GenSlidingWindowDeclRef();
+ return ss.str();
+ }
+ virtual std::string GenStringSlidingWindowDeclRef( bool = false ) const override
+ {
+ std::stringstream ss;
+ ss << mStringArgument.GenSlidingWindowDeclRef();
+ return ss.str();
+ }
+ virtual size_t Marshal( cl_kernel k, int argno, int vw, cl_program p ) override
+ {
+ int i = VectorRef::Marshal(k, argno, vw, p);
+ i += mStringArgument.Marshal(k, argno + i, vw, p);
+ return i;
+ }
+
+protected:
+ DynamicKernelStringArgument mStringArgument;
+};
+
+/// Handling a Double Vector that is used as a sliding window input
+/// to either a sliding window average or sum-of-products
+/// Generate a sequential loop for reductions
+template<class Base>
+class DynamicKernelSlidingArgument : public Base
+{
+public:
+ DynamicKernelSlidingArgument(const ScCalcConfig& config, const std::string& s,
+ const FormulaTreeNodeRef& ft,
+ const std::shared_ptr<SlidingFunctionBase>& CodeGen, int index)
+ : Base(config, s, ft, index)
+ , mpCodeGen(CodeGen)
+ {
+ FormulaToken* t = ft->GetFormulaToken();
+ if (t->GetType() != formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+ mpDVR = static_cast<const formula::DoubleVectorRefToken*>(t);
+ bIsStartFixed = mpDVR->IsStartFixed();
+ bIsEndFixed = mpDVR->IsEndFixed();
+ }
+
+ // Should only be called by SumIfs. Yikes!
+ virtual bool NeedParallelReduction() const
+ {
+ assert(dynamic_cast<OpSumIfs*>(mpCodeGen.get()));
+ return GetWindowSize() > 100 &&
+ ((GetStartFixed() && GetEndFixed()) ||
+ (!GetStartFixed() && !GetEndFixed()));
+ }
+
+ virtual void GenSlidingWindowFunction( std::stringstream& ) { }
+
+ std::string GenSlidingWindowDeclRef( bool nested = false ) const
+ {
+ size_t nArrayLength = mpDVR->GetArrayLength();
+ std::stringstream ss;
+ if (!bIsStartFixed && !bIsEndFixed)
+ {
+ if (nested)
+ ss << "((i+gid0) <" << nArrayLength << "?";
+ ss << Base::GetName() << "[i + gid0]";
+ if (nested)
+ ss << ":NAN)";
+ }
+ else
+ {
+ if (nested)
+ ss << "(i <" << nArrayLength << "?";
+ ss << Base::GetName() << "[i]";
+ if (nested)
+ ss << ":NAN)";
+ }
+ return ss.str();
+ }
+ /// Controls how the elements in the DoubleVectorRef are traversed
+ size_t GenReductionLoopHeader(
+ std::stringstream& ss, bool& needBody )
+ {
+ assert(mpDVR);
+ size_t nCurWindowSize = mpDVR->GetRefRowSize();
+
+ {
+ if (!mpDVR->IsStartFixed() && mpDVR->IsEndFixed())
+ {
+ ss << "for (int i = ";
+ ss << "gid0; i < " << mpDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++){\n\t\t";
+ needBody = true;
+ return nCurWindowSize;
+ }
+ else if (mpDVR->IsStartFixed() && !mpDVR->IsEndFixed())
+ {
+ ss << "for (int i = ";
+ ss << "0; i < " << mpDVR->GetArrayLength();
+ ss << " && i < gid0+" << nCurWindowSize << "; i++){\n\t\t";
+ needBody = true;
+ return nCurWindowSize;
+ }
+ else if (!mpDVR->IsStartFixed() && !mpDVR->IsEndFixed())
+ {
+ ss << "tmpBottom = " << mpCodeGen->GetBottom() << ";\n\t";
+ ss << "{int i;\n\t";
+ std::stringstream temp1, temp2;
+ int outLoopSize = UNROLLING_FACTOR;
+ if (nCurWindowSize / outLoopSize != 0)
+ {
+ ss << "for(int outLoop=0; outLoop<" << nCurWindowSize / outLoopSize << "; outLoop++){\n\t";
+ for (int count = 0; count < outLoopSize; count++)
+ {
+ ss << "i = outLoop*" << outLoopSize << "+" << count << ";\n\t";
+ if (count == 0)
+ {
+ temp1 << "if(i + gid0 < " << mpDVR->GetArrayLength();
+ temp1 << "){\n\t\t";
+ temp1 << "tmp = legalize(";
+ temp1 << mpCodeGen->Gen2(GenSlidingWindowDeclRef(), "tmp");
+ temp1 << ", tmp);\n\t\t\t";
+ temp1 << "}\n\t";
+ }
+ ss << temp1.str();
+ }
+ ss << "}\n\t";
+ }
+ // The residual of mod outLoopSize
+ for (size_t count = nCurWindowSize / outLoopSize * outLoopSize; count < nCurWindowSize; count++)
+ {
+ ss << "i = " << count << ";\n\t";
+ if (count == nCurWindowSize / outLoopSize * outLoopSize)
+ {
+ temp2 << "if(i + gid0 < " << mpDVR->GetArrayLength();
+ temp2 << "){\n\t\t";
+ temp2 << "tmp = legalize(";
+ temp2 << mpCodeGen->Gen2(GenSlidingWindowDeclRef(), "tmp");
+ temp2 << ", tmp);\n\t\t\t";
+ temp2 << "}\n\t";
+ }
+ ss << temp2.str();
+ }
+ ss << "}\n";
+ needBody = false;
+ return nCurWindowSize;
+ }
+ // (mpDVR->IsStartFixed() && mpDVR->IsEndFixed())
+ else
+ {
+ ss << "\n\t";
+ ss << "tmpBottom = " << mpCodeGen->GetBottom() << ";\n\t";
+ ss << "{int i;\n\t";
+ std::stringstream temp1, temp2;
+ int outLoopSize = UNROLLING_FACTOR;
+ if (nCurWindowSize / outLoopSize != 0)
+ {
+ ss << "for(int outLoop=0; outLoop<" << nCurWindowSize / outLoopSize << "; outLoop++){\n\t";
+ for (int count = 0; count < outLoopSize; count++)
+ {
+ ss << "i = outLoop*" << outLoopSize << "+" << count << ";\n\t";
+ if (count == 0)
+ {
+ temp1 << "if(i < " << mpDVR->GetArrayLength();
+ temp1 << "){\n\t\t";
+ temp1 << "tmp = legalize(";
+ temp1 << mpCodeGen->Gen2(GenSlidingWindowDeclRef(), "tmp");
+ temp1 << ", tmp);\n\t\t\t";
+ temp1 << "}\n\t";
+ }
+ ss << temp1.str();
+ }
+ ss << "}\n\t";
+ }
+ // The residual of mod outLoopSize
+ for (size_t count = nCurWindowSize / outLoopSize * outLoopSize; count < nCurWindowSize; count++)
+ {
+ ss << "i = " << count << ";\n\t";
+ if (count == nCurWindowSize / outLoopSize * outLoopSize)
+ {
+ temp2 << "if(i < " << mpDVR->GetArrayLength();
+ temp2 << "){\n\t\t";
+ temp2 << "tmp = legalize(";
+ temp2 << mpCodeGen->Gen2(GenSlidingWindowDeclRef(), "tmp");
+ temp2 << ", tmp);\n\t\t\t";
+ temp2 << "}\n\t";
+ }
+ ss << temp2.str();
+ }
+ ss << "}\n";
+ needBody = false;
+ return nCurWindowSize;
+ }
+ }
+ }
+
+ size_t GetArrayLength() const { return mpDVR->GetArrayLength(); }
+
+ size_t GetWindowSize() const { return mpDVR->GetRefRowSize(); }
+
+ bool GetStartFixed() const { return bIsStartFixed; }
+
+ bool GetEndFixed() const { return bIsEndFixed; }
+
+protected:
+ bool bIsStartFixed, bIsEndFixed;
+ const formula::DoubleVectorRefToken* mpDVR;
+ // from parent nodes
+ std::shared_ptr<SlidingFunctionBase> mpCodeGen;
+};
+
+/// A mixed string/numeric vector
+class DynamicKernelMixedSlidingArgument : public VectorRef
+{
+public:
+ DynamicKernelMixedSlidingArgument( const ScCalcConfig& config, const std::string& s,
+ const FormulaTreeNodeRef& ft, const std::shared_ptr<SlidingFunctionBase>& CodeGen,
+ int index ) :
+ VectorRef(config, s, ft),
+ mDoubleArgument(mCalcConfig, s, ft, CodeGen, index),
+ mStringArgument(mCalcConfig, s + "s", ft, CodeGen, index) { }
+ virtual void GenSlidingWindowDecl( std::stringstream& ss ) const override
+ {
+ mDoubleArgument.GenSlidingWindowDecl(ss);
+ ss << ", ";
+ mStringArgument.GenSlidingWindowDecl(ss);
+ }
+ virtual void GenSlidingWindowFunction( std::stringstream& ) override { }
+ /// Generate declaration
+ virtual void GenDecl( std::stringstream& ss ) const override
+ {
+ mDoubleArgument.GenDecl(ss);
+ ss << ", ";
+ mStringArgument.GenDecl(ss);
+ }
+ virtual void GenDeclRef( std::stringstream& ss ) const override
+ {
+ mDoubleArgument.GenDeclRef(ss);
+ ss << ",";
+ mStringArgument.GenDeclRef(ss);
+ }
+ virtual std::string GenSlidingWindowDeclRef( bool nested ) const override
+ {
+ std::stringstream ss;
+ ss << "(!isnan(" << mDoubleArgument.GenSlidingWindowDeclRef();
+ ss << ")?" << mDoubleArgument.GenSlidingWindowDeclRef();
+ ss << ":" << mStringArgument.GenSlidingWindowDeclRef(nested);
+ ss << ")";
+ return ss.str();
+ }
+ virtual std::string GenDoubleSlidingWindowDeclRef( bool = false ) const override
+ {
+ std::stringstream ss;
+ ss << mDoubleArgument.GenSlidingWindowDeclRef();
+ return ss.str();
+ }
+ virtual std::string GenStringSlidingWindowDeclRef( bool = false ) const override
+ {
+ std::stringstream ss;
+ ss << mStringArgument.GenSlidingWindowDeclRef();
+ return ss.str();
+ }
+ virtual size_t Marshal( cl_kernel k, int argno, int vw, cl_program p ) override
+ {
+ int i = mDoubleArgument.Marshal(k, argno, vw, p);
+ i += mStringArgument.Marshal(k, argno + i, vw, p);
+ return i;
+ }
+
+protected:
+ DynamicKernelSlidingArgument<VectorRef> mDoubleArgument;
+ DynamicKernelSlidingArgument<DynamicKernelStringArgument> mStringArgument;
+};
+
+/// Holds the symbol table for a given dynamic kernel
+class SymbolTable
+{
+public:
+ typedef std::map<const formula::FormulaToken*, DynamicKernelArgumentRef> ArgumentMap;
+ // This avoids instability caused by using pointer as the key type
+ SymbolTable() : mCurId(0) { }
+ template <class T>
+ const DynamicKernelArgument* DeclRefArg(const ScCalcConfig& config, const FormulaTreeNodeRef&,
+ std::shared_ptr<SlidingFunctionBase> pCodeGen, int nResultSize);
+ /// Used to generate sliding window helpers
+ void DumpSlidingWindowFunctions( std::stringstream& ss )
+ {
+ for (auto const& argument : mParams)
+ {
+ argument->GenSlidingWindowFunction(ss);
+ ss << "\n";
+ }
+ }
+ /// Memory mapping from host to device and pass buffers to the given kernel as
+ /// arguments
+ void Marshal( cl_kernel, int, cl_program );
+
+private:
+ unsigned int mCurId;
+ ArgumentMap mSymbols;
+ std::vector<DynamicKernelArgumentRef> mParams;
+};
+
+}
+
+void SymbolTable::Marshal( cl_kernel k, int nVectorWidth, cl_program pProgram )
+{
+ int i = 1; //The first argument is reserved for results
+ for (auto const& argument : mParams)
+ {
+ i += argument->Marshal(k, i, nVectorWidth, pProgram);
+ }
+}
+
+namespace {
+
+/// Handling a Double Vector that is used as a sliding window input
+/// Performs parallel reduction based on given operator
+template<class Base>
+class ParallelReductionVectorRef : public Base
+{
+public:
+ ParallelReductionVectorRef(const ScCalcConfig& config, const std::string& s,
+ const FormulaTreeNodeRef& ft,
+ const std::shared_ptr<SlidingFunctionBase>& CodeGen, int index)
+ : Base(config, s, ft, index)
+ , mpCodeGen(CodeGen)
+ , mpClmem2(nullptr)
+ {
+ FormulaToken* t = ft->GetFormulaToken();
+ if (t->GetType() != formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+ mpDVR = static_cast<const formula::DoubleVectorRefToken*>(t);
+ bIsStartFixed = mpDVR->IsStartFixed();
+ bIsEndFixed = mpDVR->IsEndFixed();
+ }
+
+ /// Emit the definition for the auxiliary reduction kernel
+ virtual void GenSlidingWindowFunction( std::stringstream& ss );
+
+ virtual std::string GenSlidingWindowDeclRef( bool ) const
+ {
+ std::stringstream ss;
+ if (!bIsStartFixed && !bIsEndFixed)
+ ss << Base::GetName() << "[i + gid0]";
+ else
+ ss << Base::GetName() << "[i]";
+ return ss.str();
+ }
+
+ /// Controls how the elements in the DoubleVectorRef are traversed
+ size_t GenReductionLoopHeader(
+ std::stringstream& ss, int nResultSize, bool& needBody );
+
+ virtual size_t Marshal( cl_kernel k, int argno, int w, cl_program mpProgram );
+
+ ~ParallelReductionVectorRef()
+ {
+ if (mpClmem2)
+ {
+ cl_int err;
+ err = clReleaseMemObject(mpClmem2);
+ SAL_WARN_IF(err != CL_SUCCESS, "sc.opencl", "clReleaseMemObject failed: " << openclwrapper::errorString(err));
+ mpClmem2 = nullptr;
+ }
+ }
+
+ size_t GetArrayLength() const { return mpDVR->GetArrayLength(); }
+
+ size_t GetWindowSize() const { return mpDVR->GetRefRowSize(); }
+
+ bool GetStartFixed() const { return bIsStartFixed; }
+
+ bool GetEndFixed() const { return bIsEndFixed; }
+
+protected:
+ bool bIsStartFixed, bIsEndFixed;
+ const formula::DoubleVectorRefToken* mpDVR;
+ // from parent nodes
+ std::shared_ptr<SlidingFunctionBase> mpCodeGen;
+ // controls whether to invoke the reduction kernel during marshaling or not
+ cl_mem mpClmem2;
+};
+
+class Reduction : public SlidingFunctionBase
+{
+ int mnResultSize;
+public:
+ explicit Reduction(int nResultSize) : mnResultSize(nResultSize) {}
+
+ typedef DynamicKernelSlidingArgument<VectorRef> NumericRange;
+ typedef DynamicKernelSlidingArgument<DynamicKernelStringArgument> StringRange;
+ typedef ParallelReductionVectorRef<VectorRef> ParallelNumericRange;
+
+ virtual bool HandleNaNArgument( std::stringstream&, unsigned, SubArguments& ) const
+ {
+ return false;
+ }
+
+ virtual void GenSlidingWindowFunction( std::stringstream& ss,
+ const std::string& sSymName, SubArguments& vSubArguments ) override
+ {
+ ss << "\ndouble " << sSymName;
+ ss << "_" << BinFuncName() << "(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ", ";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << "double tmp = " << GetBottom() << ";\n";
+ ss << "int gid0 = get_global_id(0);\n";
+ if (isAverage() || isMinOrMax())
+ ss << "int nCount = 0;\n";
+ ss << "double tmpBottom;\n";
+ unsigned i = vSubArguments.size();
+ while (i--)
+ {
+ if (NumericRange* NR =
+ dynamic_cast<NumericRange*>(vSubArguments[i].get()))
+ {
+ bool needBody;
+ NR->GenReductionLoopHeader(ss, needBody);
+ if (!needBody)
+ continue;
+ }
+ else if (ParallelNumericRange* PNR =
+ dynamic_cast<ParallelNumericRange*>(vSubArguments[i].get()))
+ {
+ //did not handle yet
+ bool bNeedBody = false;
+ PNR->GenReductionLoopHeader(ss, mnResultSize, bNeedBody);
+ if (!bNeedBody)
+ continue;
+ }
+ else if (StringRange* SR =
+ dynamic_cast<StringRange*>(vSubArguments[i].get()))
+ {
+ //did not handle yet
+ bool needBody;
+ SR->GenReductionLoopHeader(ss, needBody);
+ if (!needBody)
+ continue;
+ }
+ else
+ {
+ FormulaToken* pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ assert(pCur->GetType() != formula::svDoubleVectorRef);
+
+ if (pCur->GetType() == formula::svSingleVectorRef ||
+ pCur->GetType() == formula::svDouble)
+ {
+ ss << "{\n";
+ }
+ }
+ if (ocPush == vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ bool bNanHandled = HandleNaNArgument(ss, i, vSubArguments);
+
+ ss << "tmpBottom = " << GetBottom() << ";\n";
+
+ if (!bNanHandled)
+ {
+ ss << "if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ if (ZeroReturnZero())
+ ss << " return 0;\n";
+ else
+ {
+ ss << " tmp = ";
+ ss << Gen2("tmpBottom", "tmp") << ";\n";
+ }
+ ss << "else\n";
+ }
+ ss << "{";
+ ss << " tmp = ";
+ ss << Gen2(vSubArguments[i]->GenSlidingWindowDeclRef(), "tmp");
+ ss << ";\n";
+ ss << " }\n";
+ ss << "}\n";
+ }
+ else
+ {
+ ss << "tmp = ";
+ ss << Gen2(vSubArguments[i]->GenSlidingWindowDeclRef(), "tmp");
+ ss << ";\n";
+ }
+ }
+ if (isAverage())
+ ss <<
+ "if (nCount==0)\n"
+ " return CreateDoubleError(DivisionByZero);\n";
+ else if (isMinOrMax())
+ ss <<
+ "if (nCount==0)\n"
+ " return 0;\n";
+ ss << "return tmp";
+ if (isAverage())
+ ss << "*pow((double)nCount,-1.0)";
+ ss << ";\n}";
+ }
+ virtual bool isAverage() const { return false; }
+ virtual bool isMinOrMax() const { return false; }
+ virtual bool takeString() const override { return false; }
+ virtual bool takeNumeric() const override { return true; }
+};
+
+// Strictly binary operators
+class Binary : public SlidingFunctionBase
+{
+public:
+ virtual void GenSlidingWindowFunction( std::stringstream& ss,
+ const std::string& sSymName, SubArguments& vSubArguments ) override
+ {
+ ss << "\ndouble " << sSymName;
+ ss << "_" << BinFuncName() << "(";
+ assert(vSubArguments.size() == 2);
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ", ";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n\t";
+ ss << "int gid0 = get_global_id(0), i = 0;\n\t";
+ ss << "double tmp = ";
+ ss << Gen2(vSubArguments[0]->GenSlidingWindowDeclRef(),
+ vSubArguments[1]->GenSlidingWindowDeclRef()) << ";\n\t";
+ ss << "return tmp;\n}";
+ }
+ virtual bool takeString() const override { return true; }
+ virtual bool takeNumeric() const override { return true; }
+};
+
+class SumOfProduct : public SlidingFunctionBase
+{
+public:
+ virtual void GenSlidingWindowFunction( std::stringstream& ss,
+ const std::string& sSymName, SubArguments& vSubArguments ) override
+ {
+ size_t nCurWindowSize = 0;
+ FormulaToken* tmpCur = nullptr;
+ const formula::DoubleVectorRefToken* pCurDVR = nullptr;
+ ss << "\ndouble " << sSymName;
+ ss << "_" << BinFuncName() << "(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ size_t nCurChildWindowSize = vSubArguments[i]->GetWindowSize();
+ nCurWindowSize = (nCurWindowSize < nCurChildWindowSize) ?
+ nCurChildWindowSize : nCurWindowSize;
+ tmpCur = vSubArguments[i]->GetFormulaToken();
+ if (ocPush == tmpCur->GetOpCode())
+ {
+
+ pCurDVR = static_cast<const formula::DoubleVectorRefToken*>(tmpCur);
+ if (pCurDVR->IsStartFixed() != pCurDVR->IsEndFixed())
+ throw Unhandled(__FILE__, __LINE__);
+ }
+ }
+ ss << ") {\n";
+ ss << " double tmp = 0.0;\n";
+ ss << " int gid0 = get_global_id(0);\n";
+
+ ss << "\tint i;\n\t";
+ ss << "int currentCount0;\n";
+ for (size_t i = 0; i < vSubArguments.size() - 1; i++)
+ ss << "int currentCount" << i + 1 << ";\n";
+ std::stringstream temp3, temp4;
+ int outLoopSize = UNROLLING_FACTOR;
+ if (nCurWindowSize / outLoopSize != 0)
+ {
+ ss << "for(int outLoop=0; outLoop<" <<
+ nCurWindowSize / outLoopSize << "; outLoop++){\n\t";
+ for (int count = 0; count < outLoopSize; count++)
+ {
+ ss << "i = outLoop*" << outLoopSize << "+" << count << ";\n";
+ if (count == 0)
+ {
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ tmpCur = vSubArguments[i]->GetFormulaToken();
+ if (ocPush == tmpCur->GetOpCode())
+ {
+ pCurDVR = static_cast<const formula::DoubleVectorRefToken*>(tmpCur);
+ if (!pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed())
+ {
+ temp3 << " currentCount";
+ temp3 << i;
+ temp3 << " =i+gid0+1;\n";
+ }
+ else
+ {
+ temp3 << " currentCount";
+ temp3 << i;
+ temp3 << " =i+1;\n";
+ }
+ }
+ }
+
+ temp3 << "tmp = fsum(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ temp3 << "*";
+ if (ocPush == vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ temp3 << "(";
+ temp3 << "(currentCount";
+ temp3 << i;
+ temp3 << ">";
+ if (vSubArguments[i]->GetFormulaToken()->GetType() ==
+ formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast<const formula::SingleVectorRefToken*>
+ (vSubArguments[i]->GetFormulaToken());
+ temp3 << pSVR->GetArrayLength();
+ temp3 << ")||isnan(" << vSubArguments[i]
+ ->GenSlidingWindowDeclRef();
+ temp3 << ")?0:";
+ temp3 << vSubArguments[i]->GenSlidingWindowDeclRef();
+ temp3 << ")";
+ }
+ else if (vSubArguments[i]->GetFormulaToken()->GetType() ==
+ formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pSVR =
+ static_cast<const formula::DoubleVectorRefToken*>
+ (vSubArguments[i]->GetFormulaToken());
+ temp3 << pSVR->GetArrayLength();
+ temp3 << ")||isnan(" << vSubArguments[i]
+ ->GenSlidingWindowDeclRef(true);
+ temp3 << ")?0:";
+ temp3 << vSubArguments[i]->GenSlidingWindowDeclRef(true);
+ temp3 << ")";
+ }
+
+ }
+ else
+ temp3 << vSubArguments[i]->GenSlidingWindowDeclRef(true);
+ }
+ temp3 << ", tmp);\n\t";
+ }
+ ss << temp3.str();
+ }
+ ss << "}\n\t";
+ }
+ //The residual of mod outLoopSize
+ for (size_t count = nCurWindowSize / outLoopSize * outLoopSize;
+ count < nCurWindowSize; count++)
+ {
+ ss << "i =" << count << ";\n";
+ if (count == nCurWindowSize / outLoopSize * outLoopSize)
+ {
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ tmpCur = vSubArguments[i]->GetFormulaToken();
+ if (ocPush == tmpCur->GetOpCode())
+ {
+ pCurDVR = static_cast<const formula::DoubleVectorRefToken*>(tmpCur);
+ if (!pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed())
+ {
+ temp4 << " currentCount";
+ temp4 << i;
+ temp4 << " =i+gid0+1;\n";
+ }
+ else
+ {
+ temp4 << " currentCount";
+ temp4 << i;
+ temp4 << " =i+1;\n";
+ }
+ }
+ }
+
+ temp4 << "tmp = fsum(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ temp4 << "*";
+ if (ocPush == vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ temp4 << "(";
+ temp4 << "(currentCount";
+ temp4 << i;
+ temp4 << ">";
+ if (vSubArguments[i]->GetFormulaToken()->GetType() ==
+ formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast<const formula::SingleVectorRefToken*>
+ (vSubArguments[i]->GetFormulaToken());
+ temp4 << pSVR->GetArrayLength();
+ temp4 << ")||isnan(" << vSubArguments[i]
+ ->GenSlidingWindowDeclRef();
+ temp4 << ")?0:";
+ temp4 << vSubArguments[i]->GenSlidingWindowDeclRef();
+ temp4 << ")";
+ }
+ else if (vSubArguments[i]->GetFormulaToken()->GetType() ==
+ formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pSVR =
+ static_cast<const formula::DoubleVectorRefToken*>
+ (vSubArguments[i]->GetFormulaToken());
+ temp4 << pSVR->GetArrayLength();
+ temp4 << ")||isnan(" << vSubArguments[i]
+ ->GenSlidingWindowDeclRef(true);
+ temp4 << ")?0:";
+ temp4 << vSubArguments[i]->GenSlidingWindowDeclRef(true);
+ temp4 << ")";
+ }
+
+ }
+ else
+ {
+ temp4 << vSubArguments[i]
+ ->GenSlidingWindowDeclRef(true);
+ }
+ }
+ temp4 << ", tmp);\n\t";
+ }
+ ss << temp4.str();
+ }
+ ss << "return tmp;\n";
+ ss << "}";
+ }
+ virtual bool takeString() const override { return false; }
+ virtual bool takeNumeric() const override { return true; }
+};
+
+/// operator traits
+class OpNop : public Reduction
+{
+public:
+ explicit OpNop(int nResultSize) : Reduction(nResultSize) {}
+
+ virtual std::string GetBottom() override { return "0"; }
+ virtual std::string Gen2( const std::string& lhs, const std::string& ) const override
+ {
+ return lhs;
+ }
+ virtual std::string BinFuncName() const override { return "nop"; }
+};
+
+class OpCount : public Reduction
+{
+public:
+ explicit OpCount(int nResultSize) : Reduction(nResultSize) {}
+
+ virtual std::string GetBottom() override { return "0"; }
+ virtual std::string Gen2( const std::string& lhs, const std::string& rhs ) const override
+ {
+ std::stringstream ss;
+ ss << "(isnan(" << lhs << ")?" << rhs << ":" << rhs << "+1.0)";
+ return ss.str();
+ }
+ virtual std::string BinFuncName() const override { return "fcount"; }
+ virtual bool canHandleMultiVector() const override { return true; }
+};
+
+class OpEqual : public Binary
+{
+public:
+ virtual std::string GetBottom() override { return "0"; }
+ virtual std::string Gen2( const std::string& lhs, const std::string& rhs ) const override
+ {
+ std::stringstream ss;
+ ss << "strequal(" << lhs << "," << rhs << ")";
+ return ss.str();
+ }
+ virtual std::string BinFuncName() const override { return "eq"; }
+};
+
+class OpLessEqual : public Binary
+{
+public:
+ virtual std::string GetBottom() override { return "0"; }
+ virtual std::string Gen2( const std::string& lhs, const std::string& rhs ) const override
+ {
+ std::stringstream ss;
+ ss << "(" << lhs << "<=" << rhs << ")";
+ return ss.str();
+ }
+ virtual std::string BinFuncName() const override { return "leq"; }
+};
+
+class OpLess : public Binary
+{
+public:
+ virtual std::string GetBottom() override { return "0"; }
+ virtual std::string Gen2( const std::string& lhs, const std::string& rhs ) const override
+ {
+ std::stringstream ss;
+ ss << "(" << lhs << "<" << rhs << ")";
+ return ss.str();
+ }
+ virtual std::string BinFuncName() const override { return "less"; }
+};
+
+class OpGreater : public Binary
+{
+public:
+ virtual std::string GetBottom() override { return "0"; }
+ virtual std::string Gen2( const std::string& lhs, const std::string& rhs ) const override
+ {
+ std::stringstream ss;
+ ss << "(" << lhs << ">" << rhs << ")";
+ return ss.str();
+ }
+ virtual std::string BinFuncName() const override { return "gt"; }
+};
+
+class OpSum : public Reduction
+{
+public:
+ explicit OpSum(int nResultSize) : Reduction(nResultSize) {}
+
+ virtual std::string GetBottom() override { return "0"; }
+ virtual std::string Gen2( const std::string& lhs, const std::string& rhs ) const override
+ {
+ std::stringstream ss;
+ ss << "fsum_approx((" << lhs << "),(" << rhs << "))";
+ return ss.str();
+ }
+ virtual std::string BinFuncName() const override { return "fsum"; }
+ // All arguments are simply summed, so it doesn't matter if SvDoubleVector is split.
+ virtual bool canHandleMultiVector() const override { return true; }
+};
+
+class OpAverage : public Reduction
+{
+public:
+ explicit OpAverage(int nResultSize) : Reduction(nResultSize) {}
+
+ virtual std::string GetBottom() override { return "0"; }
+ virtual std::string Gen2( const std::string& lhs, const std::string& rhs ) const override
+ {
+ std::stringstream ss;
+ ss << "fsum_count(" << lhs << "," << rhs << ", &nCount)";
+ return ss.str();
+ }
+ virtual std::string BinFuncName() const override { return "average"; }
+ virtual bool isAverage() const override { return true; }
+ virtual bool canHandleMultiVector() const override { return true; }
+};
+
+class OpSub : public Reduction
+{
+public:
+ explicit OpSub(int nResultSize) : Reduction(nResultSize) {}
+
+ virtual std::string GetBottom() override { return "0"; }
+ virtual std::string Gen2( const std::string& lhs, const std::string& rhs ) const override
+ {
+ return "fsub_approx(" + lhs + "," + rhs + ")";
+ }
+ virtual std::string BinFuncName() const override { return "fsub"; }
+};
+
+class OpMul : public Reduction
+{
+public:
+ explicit OpMul(int nResultSize) : Reduction(nResultSize) {}
+
+ virtual std::string GetBottom() override { return "1"; }
+ virtual std::string Gen2( const std::string& lhs, const std::string& rhs ) const override
+ {
+ return lhs + "*" + rhs;
+ }
+ virtual std::string BinFuncName() const override { return "fmul"; }
+ virtual bool ZeroReturnZero() override { return true; }
+};
+
+/// Technically not a reduction, but fits the framework.
+class OpDiv : public Reduction
+{
+public:
+ explicit OpDiv(int nResultSize) : Reduction(nResultSize) {}
+
+ virtual std::string GetBottom() override { return "1.0"; }
+ virtual std::string Gen2( const std::string& lhs, const std::string& rhs ) const override
+ {
+ return "(" + rhs + "==0 ? CreateDoubleError(DivisionByZero) : (" + lhs + "/" + rhs + ") )";
+ }
+ virtual std::string BinFuncName() const override { return "fdiv"; }
+
+ virtual bool HandleNaNArgument( std::stringstream& ss, unsigned argno, SubArguments& vSubArguments ) const override
+ {
+ if (argno == 1)
+ {
+ ss <<
+ "if (isnan(" << vSubArguments[argno]->GenSlidingWindowDeclRef() << ")) {\n"
+ " return CreateDoubleError(DivisionByZero);\n"
+ "}\n";
+ return true;
+ }
+ else if (argno == 0)
+ {
+ ss <<
+ "if (isnan(" << vSubArguments[argno]->GenSlidingWindowDeclRef() << ") &&\n"
+ " !(isnan(" << vSubArguments[1]->GenSlidingWindowDeclRef() << ") || " << vSubArguments[1]->GenSlidingWindowDeclRef() << " == 0)) {\n"
+ " return 0;\n"
+ "}\n";
+ }
+ return false;
+ }
+
+};
+
+class OpMin : public Reduction
+{
+public:
+ explicit OpMin(int nResultSize) : Reduction(nResultSize) {}
+
+ virtual std::string GetBottom() override { return "NAN"; }
+ virtual std::string Gen2( const std::string& lhs, const std::string& rhs ) const override
+ {
+ return "fmin_count(" + lhs + "," + rhs + ", &nCount)";
+ }
+ virtual std::string BinFuncName() const override { return "min"; }
+ virtual bool isMinOrMax() const override { return true; }
+ virtual bool canHandleMultiVector() const override { return true; }
+};
+
+class OpMax : public Reduction
+{
+public:
+ explicit OpMax(int nResultSize) : Reduction(nResultSize) {}
+
+ virtual std::string GetBottom() override { return "NAN"; }
+ virtual std::string Gen2( const std::string& lhs, const std::string& rhs ) const override
+ {
+ return "fmax_count(" + lhs + "," + rhs + ", &nCount)";
+ }
+ virtual std::string BinFuncName() const override { return "max"; }
+ virtual bool isMinOrMax() const override { return true; }
+ virtual bool canHandleMultiVector() const override { return true; }
+};
+
+class OpSumProduct : public SumOfProduct
+{
+public:
+ virtual std::string GetBottom() override { return "0"; }
+ virtual std::string Gen2( const std::string& lhs, const std::string& rhs ) const override
+ {
+ return lhs + "*" + rhs;
+ }
+ virtual std::string BinFuncName() const override { return "fsop"; }
+};
+
+template<class Base>
+void ParallelReductionVectorRef<Base>::GenSlidingWindowFunction( std::stringstream& ss )
+{
+ if (!dynamic_cast<OpAverage*>(mpCodeGen.get()))
+ {
+ std::string name = Base::GetName();
+ ss << "__kernel void " << name;
+ ss << "_reduction(__global double* A, "
+ "__global double *result,int arrayLength,int windowSize){\n";
+ ss << " double tmp, current_result =" <<
+ mpCodeGen->GetBottom();
+ ss << ";\n";
+ ss << " int writePos = get_group_id(1);\n";
+ ss << " int lidx = get_local_id(0);\n";
+ ss << " __local double shm_buf[256];\n";
+ if (mpDVR->IsStartFixed())
+ ss << " int offset = 0;\n";
+ else // if (!mpDVR->IsStartFixed())
+ ss << " int offset = get_group_id(1);\n";
+ if (mpDVR->IsStartFixed() && mpDVR->IsEndFixed())
+ ss << " int end = windowSize;\n";
+ else if (!mpDVR->IsStartFixed() && !mpDVR->IsEndFixed())
+ ss << " int end = offset + windowSize;\n";
+ else if (mpDVR->IsStartFixed() && !mpDVR->IsEndFixed())
+ ss << " int end = windowSize + get_group_id(1);\n";
+ else if (!mpDVR->IsStartFixed() && mpDVR->IsEndFixed())
+ ss << " int end = windowSize;\n";
+ ss << " end = min(end, arrayLength);\n";
+
+ ss << " barrier(CLK_LOCAL_MEM_FENCE);\n";
+ ss << " int loop = arrayLength/512 + 1;\n";
+ ss << " for (int l=0; l<loop; l++){\n";
+ ss << " tmp = " << mpCodeGen->GetBottom() << ";\n";
+ ss << " int loopOffset = l*512;\n";
+ ss << " if((loopOffset + lidx + offset + 256) < end) {\n";
+ ss << " tmp = legalize(" << mpCodeGen->Gen2(
+ "A[loopOffset + lidx + offset]", "tmp") << ", tmp);\n";
+ ss << " tmp = legalize(" << mpCodeGen->Gen2(
+ "A[loopOffset + lidx + offset + 256]", "tmp") << ", tmp);\n";
+ ss << " } else if ((loopOffset + lidx + offset) < end)\n";
+ ss << " tmp = legalize(" << mpCodeGen->Gen2(
+ "A[loopOffset + lidx + offset]", "tmp") << ", tmp);\n";
+ ss << " shm_buf[lidx] = tmp;\n";
+ ss << " barrier(CLK_LOCAL_MEM_FENCE);\n";
+ ss << " for (int i = 128; i >0; i/=2) {\n";
+ ss << " if (lidx < i)\n";
+ ss << " shm_buf[lidx] = ";
+ // Special case count
+ if (dynamic_cast<OpCount*>(mpCodeGen.get()))
+ ss << "shm_buf[lidx] + shm_buf[lidx + i];\n";
+ else
+ ss << mpCodeGen->Gen2("shm_buf[lidx]", "shm_buf[lidx + i]") << ";\n";
+ ss << " barrier(CLK_LOCAL_MEM_FENCE);\n";
+ ss << " }\n";
+ ss << " if (lidx == 0)\n";
+ ss << " current_result =";
+ if (dynamic_cast<OpCount*>(mpCodeGen.get()))
+ ss << "current_result + shm_buf[0]";
+ else
+ ss << mpCodeGen->Gen2("current_result", "shm_buf[0]");
+ ss << ";\n";
+ ss << " barrier(CLK_LOCAL_MEM_FENCE);\n";
+ ss << " }\n";
+ ss << " if (lidx == 0)\n";
+ ss << " result[writePos] = current_result;\n";
+ ss << "}\n";
+ }
+ else
+ {
+ std::string name = Base::GetName();
+ /*sum reduction*/
+ ss << "__kernel void " << name << "_sum";
+ ss << "_reduction(__global double* A, "
+ "__global double *result,int arrayLength,int windowSize){\n";
+ ss << " double tmp, current_result =" <<
+ mpCodeGen->GetBottom();
+ ss << ";\n";
+ ss << " int writePos = get_group_id(1);\n";
+ ss << " int lidx = get_local_id(0);\n";
+ ss << " __local double shm_buf[256];\n";
+ if (mpDVR->IsStartFixed())
+ ss << " int offset = 0;\n";
+ else // if (!mpDVR->IsStartFixed())
+ ss << " int offset = get_group_id(1);\n";
+ if (mpDVR->IsStartFixed() && mpDVR->IsEndFixed())
+ ss << " int end = windowSize;\n";
+ else if (!mpDVR->IsStartFixed() && !mpDVR->IsEndFixed())
+ ss << " int end = offset + windowSize;\n";
+ else if (mpDVR->IsStartFixed() && !mpDVR->IsEndFixed())
+ ss << " int end = windowSize + get_group_id(1);\n";
+ else if (!mpDVR->IsStartFixed() && mpDVR->IsEndFixed())
+ ss << " int end = windowSize;\n";
+ ss << " end = min(end, arrayLength);\n";
+ ss << " barrier(CLK_LOCAL_MEM_FENCE);\n";
+ ss << " int loop = arrayLength/512 + 1;\n";
+ ss << " for (int l=0; l<loop; l++){\n";
+ ss << " tmp = " << mpCodeGen->GetBottom() << ";\n";
+ ss << " int loopOffset = l*512;\n";
+ ss << " if((loopOffset + lidx + offset + 256) < end) {\n";
+ ss << " tmp = legalize(";
+ ss << "(A[loopOffset + lidx + offset]+ tmp)";
+ ss << ", tmp);\n";
+ ss << " tmp = legalize((A[loopOffset + lidx + offset + 256]+ tmp)";
+ ss << ", tmp);\n";
+ ss << " } else if ((loopOffset + lidx + offset) < end)\n";
+ ss << " tmp = legalize((A[loopOffset + lidx + offset] + tmp)";
+ ss << ", tmp);\n";
+ ss << " shm_buf[lidx] = tmp;\n";
+ ss << " barrier(CLK_LOCAL_MEM_FENCE);\n";
+ ss << " for (int i = 128; i >0; i/=2) {\n";
+ ss << " if (lidx < i)\n";
+ ss << " shm_buf[lidx] = ";
+ ss << "shm_buf[lidx] + shm_buf[lidx + i];\n";
+ ss << " barrier(CLK_LOCAL_MEM_FENCE);\n";
+ ss << " }\n";
+ ss << " if (lidx == 0)\n";
+ ss << " current_result =";
+ ss << "current_result + shm_buf[0]";
+ ss << ";\n";
+ ss << " barrier(CLK_LOCAL_MEM_FENCE);\n";
+ ss << " }\n";
+ ss << " if (lidx == 0)\n";
+ ss << " result[writePos] = current_result;\n";
+ ss << "}\n";
+ /*count reduction*/
+ ss << "__kernel void " << name << "_count";
+ ss << "_reduction(__global double* A, "
+ "__global double *result,int arrayLength,int windowSize){\n";
+ ss << " double tmp, current_result =" <<
+ mpCodeGen->GetBottom();
+ ss << ";\n";
+ ss << " int writePos = get_group_id(1);\n";
+ ss << " int lidx = get_local_id(0);\n";
+ ss << " __local double shm_buf[256];\n";
+ if (mpDVR->IsStartFixed())
+ ss << " int offset = 0;\n";
+ else // if (!mpDVR->IsStartFixed())
+ ss << " int offset = get_group_id(1);\n";
+ if (mpDVR->IsStartFixed() && mpDVR->IsEndFixed())
+ ss << " int end = windowSize;\n";
+ else if (!mpDVR->IsStartFixed() && !mpDVR->IsEndFixed())
+ ss << " int end = offset + windowSize;\n";
+ else if (mpDVR->IsStartFixed() && !mpDVR->IsEndFixed())
+ ss << " int end = windowSize + get_group_id(1);\n";
+ else if (!mpDVR->IsStartFixed() && mpDVR->IsEndFixed())
+ ss << " int end = windowSize;\n";
+ ss << " end = min(end, arrayLength);\n";
+ ss << " barrier(CLK_LOCAL_MEM_FENCE);\n";
+ ss << " int loop = arrayLength/512 + 1;\n";
+ ss << " for (int l=0; l<loop; l++){\n";
+ ss << " tmp = " << mpCodeGen->GetBottom() << ";\n";
+ ss << " int loopOffset = l*512;\n";
+ ss << " if((loopOffset + lidx + offset + 256) < end) {\n";
+ ss << " tmp = legalize((isnan(A[loopOffset + lidx + offset])?tmp:tmp+1.0)";
+ ss << ", tmp);\n";
+ ss << " tmp = legalize((isnan(A[loopOffset + lidx + offset+256])?tmp:tmp+1.0)";
+ ss << ", tmp);\n";
+ ss << " } else if ((loopOffset + lidx + offset) < end)\n";
+ ss << " tmp = legalize((isnan(A[loopOffset + lidx + offset])?tmp:tmp+1.0)";
+ ss << ", tmp);\n";
+ ss << " shm_buf[lidx] = tmp;\n";
+ ss << " barrier(CLK_LOCAL_MEM_FENCE);\n";
+ ss << " for (int i = 128; i >0; i/=2) {\n";
+ ss << " if (lidx < i)\n";
+ ss << " shm_buf[lidx] = ";
+ ss << "shm_buf[lidx] + shm_buf[lidx + i];\n";
+ ss << " barrier(CLK_LOCAL_MEM_FENCE);\n";
+ ss << " }\n";
+ ss << " if (lidx == 0)\n";
+ ss << " current_result =";
+ ss << "current_result + shm_buf[0];";
+ ss << ";\n";
+ ss << " barrier(CLK_LOCAL_MEM_FENCE);\n";
+ ss << " }\n";
+ ss << " if (lidx == 0)\n";
+ ss << " result[writePos] = current_result;\n";
+ ss << "}\n";
+ }
+
+}
+
+template<class Base>
+size_t ParallelReductionVectorRef<Base>::GenReductionLoopHeader(
+ std::stringstream& ss, int nResultSize, bool& needBody )
+{
+ assert(mpDVR);
+ size_t nCurWindowSize = mpDVR->GetRefRowSize();
+ std::string temp = Base::GetName() + "[gid0]";
+ ss << "tmp = ";
+ // Special case count
+ if (dynamic_cast<OpAverage*>(mpCodeGen.get()))
+ {
+ ss << mpCodeGen->Gen2(temp, "tmp") << ";\n";
+ ss << "nCount = nCount-1;\n";
+ ss << "nCount = nCount +"; /*re-assign nCount from count reduction*/
+ ss << Base::GetName() << "[gid0+" << nResultSize << "]" << ";\n";
+ }
+ else if (dynamic_cast<OpCount*>(mpCodeGen.get()))
+ ss << temp << "+ tmp";
+ else
+ ss << mpCodeGen->Gen2(temp, "tmp");
+ ss << ";\n\t";
+ needBody = false;
+ return nCurWindowSize;
+}
+
+template<class Base>
+size_t ParallelReductionVectorRef<Base>::Marshal( cl_kernel k, int argno, int w, cl_program mpProgram )
+{
+ assert(Base::mpClmem == nullptr);
+
+ OpenCLZone zone;
+ openclwrapper::KernelEnv kEnv;
+ openclwrapper::setKernelEnv(&kEnv);
+ cl_int err;
+ size_t nInput = mpDVR->GetArrayLength();
+ size_t nCurWindowSize = mpDVR->GetRefRowSize();
+ // create clmem buffer
+ if (mpDVR->GetArrays()[Base::mnIndex].mpNumericArray == nullptr)
+ throw Unhandled(__FILE__, __LINE__);
+ double* pHostBuffer = const_cast<double*>(
+ mpDVR->GetArrays()[Base::mnIndex].mpNumericArray);
+ size_t szHostBuffer = nInput * sizeof(double);
+ Base::mpClmem = clCreateBuffer(kEnv.mpkContext,
+ cl_mem_flags(CL_MEM_READ_ONLY) | CL_MEM_USE_HOST_PTR,
+ szHostBuffer,
+ pHostBuffer, &err);
+ SAL_INFO("sc.opencl", "Created buffer " << Base::mpClmem << " size " << nInput << "*" << sizeof(double) << "=" << szHostBuffer << " using host buffer " << pHostBuffer);
+
+ mpClmem2 = clCreateBuffer(kEnv.mpkContext,
+ CL_MEM_READ_WRITE | CL_MEM_ALLOC_HOST_PTR,
+ sizeof(double) * w, nullptr, nullptr);
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clCreateBuffer", err, __FILE__, __LINE__);
+ SAL_INFO("sc.opencl", "Created buffer " << mpClmem2 << " size " << sizeof(double) << "*" << w << "=" << (sizeof(double)*w));
+
+ // reproduce the reduction function name
+ std::string kernelName;
+ if (!dynamic_cast<OpAverage*>(mpCodeGen.get()))
+ kernelName = Base::GetName() + "_reduction";
+ else
+ kernelName = Base::GetName() + "_sum_reduction";
+ cl_kernel redKernel = clCreateKernel(mpProgram, kernelName.c_str(), &err);
+ if (err != CL_SUCCESS)
+ throw OpenCLError("clCreateKernel", err, __FILE__, __LINE__);
+ SAL_INFO("sc.opencl", "Created kernel " << redKernel << " with name " << kernelName << " in program " << mpProgram);
+
+ // set kernel arg of reduction kernel
+ // TODO(Wei Wei): use unique name for kernel
+ cl_mem buf = Base::GetCLBuffer();
+ SAL_INFO("sc.opencl", "Kernel " << redKernel << " arg " << 0 << ": cl_mem: " << buf);
+ err = clSetKernelArg(redKernel, 0, sizeof(cl_mem),
+ static_cast<void*>(&buf));
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clSetKernelArg", err, __FILE__, __LINE__);
+
+ SAL_INFO("sc.opencl", "Kernel " << redKernel << " arg " << 1 << ": cl_mem: " << mpClmem2);
+ err = clSetKernelArg(redKernel, 1, sizeof(cl_mem), &mpClmem2);
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clSetKernelArg", err, __FILE__, __LINE__);
+
+ SAL_INFO("sc.opencl", "Kernel " << redKernel << " arg " << 2 << ": cl_int: " << nInput);
+ err = clSetKernelArg(redKernel, 2, sizeof(cl_int), static_cast<void*>(&nInput));
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clSetKernelArg", err, __FILE__, __LINE__);
+
+ SAL_INFO("sc.opencl", "Kernel " << redKernel << " arg " << 3 << ": cl_int: " << nCurWindowSize);
+ err = clSetKernelArg(redKernel, 3, sizeof(cl_int), static_cast<void*>(&nCurWindowSize));
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clSetKernelArg", err, __FILE__, __LINE__);
+
+ // set work group size and execute
+ size_t global_work_size[] = { 256, static_cast<size_t>(w) };
+ size_t const local_work_size[] = { 256, 1 };
+ SAL_INFO("sc.opencl", "Enqueuing kernel " << redKernel);
+ err = clEnqueueNDRangeKernel(kEnv.mpkCmdQueue, redKernel, 2, nullptr,
+ global_work_size, local_work_size, 0, nullptr, nullptr);
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clEnqueueNDRangeKernel", err, __FILE__, __LINE__);
+ err = clFinish(kEnv.mpkCmdQueue);
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clFinish", err, __FILE__, __LINE__);
+ if (dynamic_cast<OpAverage*>(mpCodeGen.get()))
+ {
+ /*average need more reduction kernel for count computing*/
+ std::unique_ptr<double[]> pAllBuffer(new double[2 * w]);
+ double* resbuf = static_cast<double*>(clEnqueueMapBuffer(kEnv.mpkCmdQueue,
+ mpClmem2,
+ CL_TRUE, CL_MAP_READ, 0,
+ sizeof(double) * w, 0, nullptr, nullptr,
+ &err));
+ if (err != CL_SUCCESS)
+ throw OpenCLError("clEnqueueMapBuffer", err, __FILE__, __LINE__);
+
+ for (int i = 0; i < w; i++)
+ pAllBuffer[i] = resbuf[i];
+ err = clEnqueueUnmapMemObject(kEnv.mpkCmdQueue, mpClmem2, resbuf, 0, nullptr, nullptr);
+ if (err != CL_SUCCESS)
+ throw OpenCLError("clEnqueueUnmapMemObject", err, __FILE__, __LINE__);
+
+ kernelName = Base::GetName() + "_count_reduction";
+ redKernel = clCreateKernel(mpProgram, kernelName.c_str(), &err);
+ if (err != CL_SUCCESS)
+ throw OpenCLError("clCreateKernel", err, __FILE__, __LINE__);
+ SAL_INFO("sc.opencl", "Created kernel " << redKernel << " with name " << kernelName << " in program " << mpProgram);
+
+ // set kernel arg of reduction kernel
+ buf = Base::GetCLBuffer();
+ SAL_INFO("sc.opencl", "Kernel " << redKernel << " arg " << 0 << ": cl_mem: " << buf);
+ err = clSetKernelArg(redKernel, 0, sizeof(cl_mem),
+ static_cast<void*>(&buf));
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clSetKernelArg", err, __FILE__, __LINE__);
+
+ SAL_INFO("sc.opencl", "Kernel " << redKernel << " arg " << 1 << ": cl_mem: " << mpClmem2);
+ err = clSetKernelArg(redKernel, 1, sizeof(cl_mem), &mpClmem2);
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clSetKernelArg", err, __FILE__, __LINE__);
+
+ SAL_INFO("sc.opencl", "Kernel " << redKernel << " arg " << 2 << ": cl_int: " << nInput);
+ err = clSetKernelArg(redKernel, 2, sizeof(cl_int), static_cast<void*>(&nInput));
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clSetKernelArg", err, __FILE__, __LINE__);
+
+ SAL_INFO("sc.opencl", "Kernel " << redKernel << " arg " << 3 << ": cl_int: " << nCurWindowSize);
+ err = clSetKernelArg(redKernel, 3, sizeof(cl_int), static_cast<void*>(&nCurWindowSize));
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clSetKernelArg", err, __FILE__, __LINE__);
+
+ // set work group size and execute
+ size_t global_work_size1[] = { 256, static_cast<size_t>(w) };
+ size_t const local_work_size1[] = { 256, 1 };
+ SAL_INFO("sc.opencl", "Enqueuing kernel " << redKernel);
+ err = clEnqueueNDRangeKernel(kEnv.mpkCmdQueue, redKernel, 2, nullptr,
+ global_work_size1, local_work_size1, 0, nullptr, nullptr);
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clEnqueueNDRangeKernel", err, __FILE__, __LINE__);
+ err = clFinish(kEnv.mpkCmdQueue);
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clFinish", err, __FILE__, __LINE__);
+ resbuf = static_cast<double*>(clEnqueueMapBuffer(kEnv.mpkCmdQueue,
+ mpClmem2,
+ CL_TRUE, CL_MAP_READ, 0,
+ sizeof(double) * w, 0, nullptr, nullptr,
+ &err));
+ if (err != CL_SUCCESS)
+ throw OpenCLError("clEnqueueMapBuffer", err, __FILE__, __LINE__);
+ for (int i = 0; i < w; i++)
+ pAllBuffer[i + w] = resbuf[i];
+ err = clEnqueueUnmapMemObject(kEnv.mpkCmdQueue, mpClmem2, resbuf, 0, nullptr, nullptr);
+ // FIXME: Is it intentional to not throw an OpenCLError even if the clEnqueueUnmapMemObject() fails?
+ if (CL_SUCCESS != err)
+ SAL_WARN("sc.opencl", "clEnqueueUnmapMemObject failed: " << openclwrapper::errorString(err));
+ if (mpClmem2)
+ {
+ err = clReleaseMemObject(mpClmem2);
+ SAL_WARN_IF(err != CL_SUCCESS, "sc.opencl", "clReleaseMemObject failed: " << openclwrapper::errorString(err));
+ mpClmem2 = nullptr;
+ }
+ mpClmem2 = clCreateBuffer(kEnv.mpkContext,
+ cl_mem_flags(CL_MEM_READ_WRITE) | CL_MEM_COPY_HOST_PTR,
+ w * sizeof(double) * 2, pAllBuffer.get(), &err);
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clCreateBuffer", err, __FILE__, __LINE__);
+ SAL_INFO("sc.opencl", "Created buffer " << mpClmem2 << " size " << w << "*" << sizeof(double) << "=" << (w*sizeof(double)) << " copying host buffer " << pAllBuffer.get());
+ }
+ // set kernel arg
+ SAL_INFO("sc.opencl", "Kernel " << k << " arg " << argno << ": cl_mem: " << mpClmem2);
+ err = clSetKernelArg(k, argno, sizeof(cl_mem), &mpClmem2);
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clSetKernelArg", err, __FILE__, __LINE__);
+ return 1;
+}
+
+struct SumIfsArgs
+{
+ explicit SumIfsArgs(cl_mem x) : mCLMem(x), mConst(0.0) { }
+ explicit SumIfsArgs(double x) : mCLMem(nullptr), mConst(x) { }
+ cl_mem mCLMem;
+ double mConst;
+};
+
+/// Helper functions that have multiple buffers
+class DynamicKernelSoPArguments : public DynamicKernelArgument
+{
+public:
+ typedef std::vector<DynamicKernelArgumentRef> SubArgumentsType;
+
+ DynamicKernelSoPArguments( const ScCalcConfig& config,
+ const std::string& s, const FormulaTreeNodeRef& ft,
+ std::shared_ptr<SlidingFunctionBase> pCodeGen, int nResultSize );
+
+ /// Create buffer and pass the buffer to a given kernel
+ virtual size_t Marshal( cl_kernel k, int argno, int nVectorWidth, cl_program pProgram ) override
+ {
+ OpenCLZone zone;
+ unsigned i = 0;
+ for (const auto& rxSubArgument : mvSubArguments)
+ {
+ i += rxSubArgument->Marshal(k, argno + i, nVectorWidth, pProgram);
+ }
+ if (dynamic_cast<OpGeoMean*>(mpCodeGen.get()))
+ {
+ openclwrapper::KernelEnv kEnv;
+ openclwrapper::setKernelEnv(&kEnv);
+ cl_int err;
+ cl_mem pClmem2;
+
+ std::vector<cl_mem> vclmem;
+ for (const auto& rxSubArgument : mvSubArguments)
+ {
+ if (VectorRef* VR = dynamic_cast<VectorRef*>(rxSubArgument.get()))
+ vclmem.push_back(VR->GetCLBuffer());
+ else
+ vclmem.push_back(nullptr);
+ }
+ pClmem2 = clCreateBuffer(kEnv.mpkContext, CL_MEM_READ_WRITE,
+ sizeof(double) * nVectorWidth, nullptr, &err);
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clCreateBuffer", err, __FILE__, __LINE__);
+ SAL_INFO("sc.opencl", "Created buffer " << pClmem2 << " size " << sizeof(double) << "*" << nVectorWidth << "=" << (sizeof(double)*nVectorWidth));
+
+ std::string kernelName = "GeoMean_reduction";
+ cl_kernel redKernel = clCreateKernel(pProgram, kernelName.c_str(), &err);
+ if (err != CL_SUCCESS)
+ throw OpenCLError("clCreateKernel", err, __FILE__, __LINE__);
+ SAL_INFO("sc.opencl", "Created kernel " << redKernel << " with name " << kernelName << " in program " << pProgram);
+
+ // set kernel arg of reduction kernel
+ for (size_t j = 0; j < vclmem.size(); j++)
+ {
+ SAL_INFO("sc.opencl", "Kernel " << redKernel << " arg " << j << ": " << (vclmem[j] ? "cl_mem" : "double") << ": " << vclmem[j]);
+ err = clSetKernelArg(redKernel, j,
+ vclmem[j] ? sizeof(cl_mem) : sizeof(double),
+ static_cast<void*>(&vclmem[j]));
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clSetKernelArg", err, __FILE__, __LINE__);
+ }
+ SAL_INFO("sc.opencl", "Kernel " << redKernel << " arg " << vclmem.size() << ": cl_mem: " << pClmem2);
+ err = clSetKernelArg(redKernel, vclmem.size(), sizeof(cl_mem), static_cast<void*>(&pClmem2));
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clSetKernelArg", err, __FILE__, __LINE__);
+
+ // set work group size and execute
+ size_t global_work_size[] = { 256, static_cast<size_t>(nVectorWidth) };
+ size_t const local_work_size[] = { 256, 1 };
+ SAL_INFO("sc.opencl", "Enqueuing kernel " << redKernel);
+ err = clEnqueueNDRangeKernel(kEnv.mpkCmdQueue, redKernel, 2, nullptr,
+ global_work_size, local_work_size, 0, nullptr, nullptr);
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clEnqueueNDRangeKernel", err, __FILE__, __LINE__);
+ err = clFinish(kEnv.mpkCmdQueue);
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clFinish", err, __FILE__, __LINE__);
+
+ // Pass pClmem2 to the "real" kernel
+ SAL_INFO("sc.opencl", "Kernel " << k << " arg " << argno << ": cl_mem: " << pClmem2);
+ err = clSetKernelArg(k, argno, sizeof(cl_mem), static_cast<void*>(&pClmem2));
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clSetKernelArg", err, __FILE__, __LINE__);
+ }
+ if (OpSumIfs* OpSumCodeGen = dynamic_cast<OpSumIfs*>(mpCodeGen.get()))
+ {
+ openclwrapper::KernelEnv kEnv;
+ openclwrapper::setKernelEnv(&kEnv);
+ cl_int err;
+ DynamicKernelArgument* Arg = mvSubArguments[0].get();
+ DynamicKernelSlidingArgument<VectorRef>* slidingArgPtr =
+ static_cast<DynamicKernelSlidingArgument<VectorRef>*>(Arg);
+ mpClmem2 = nullptr;
+
+ if (OpSumCodeGen->NeedReductionKernel())
+ {
+ size_t nInput = slidingArgPtr->GetArrayLength();
+ size_t nCurWindowSize = slidingArgPtr->GetWindowSize();
+ std::vector<SumIfsArgs> vclmem;
+
+ for (const auto& rxSubArgument : mvSubArguments)
+ {
+ if (VectorRef* VR = dynamic_cast<VectorRef*>(rxSubArgument.get()))
+ vclmem.emplace_back(VR->GetCLBuffer());
+ else if (DynamicKernelConstantArgument* CA = dynamic_cast<DynamicKernelConstantArgument*>(rxSubArgument.get()))
+ vclmem.emplace_back(CA->GetDouble());
+ else
+ vclmem.emplace_back(nullptr);
+ }
+ mpClmem2 = clCreateBuffer(kEnv.mpkContext, CL_MEM_READ_WRITE,
+ sizeof(double) * nVectorWidth, nullptr, &err);
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clCreateBuffer", err, __FILE__, __LINE__);
+ SAL_INFO("sc.opencl", "Created buffer " << mpClmem2 << " size " << sizeof(double) << "*" << nVectorWidth << "=" << (sizeof(double)*nVectorWidth));
+
+ std::string kernelName = mvSubArguments[0]->GetName() + "_SumIfs_reduction";
+ cl_kernel redKernel = clCreateKernel(pProgram, kernelName.c_str(), &err);
+ if (err != CL_SUCCESS)
+ throw OpenCLError("clCreateKernel", err, __FILE__, __LINE__);
+ SAL_INFO("sc.opencl", "Created kernel " << redKernel << " with name " << kernelName << " in program " << pProgram);
+
+ // set kernel arg of reduction kernel
+ for (size_t j = 0; j < vclmem.size(); j++)
+ {
+ if (vclmem[j].mCLMem)
+ SAL_INFO("sc.opencl", "Kernel " << redKernel << " arg " << j << ": cl_mem: " << vclmem[j].mCLMem);
+ else
+ SAL_INFO("sc.opencl", "Kernel " << redKernel << " arg " << j << ": double: " << vclmem[j].mConst);
+ err = clSetKernelArg(redKernel, j,
+ vclmem[j].mCLMem ? sizeof(cl_mem) : sizeof(double),
+ vclmem[j].mCLMem ? static_cast<void*>(&vclmem[j].mCLMem) :
+ static_cast<void*>(&vclmem[j].mConst));
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clSetKernelArg", err, __FILE__, __LINE__);
+ }
+ SAL_INFO("sc.opencl", "Kernel " << redKernel << " arg " << vclmem.size() << ": cl_mem: " << mpClmem2);
+ err = clSetKernelArg(redKernel, vclmem.size(), sizeof(cl_mem), static_cast<void*>(&mpClmem2));
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clSetKernelArg", err, __FILE__, __LINE__);
+
+ SAL_INFO("sc.opencl", "Kernel " << redKernel << " arg " << (vclmem.size() + 1) << ": cl_int: " << nInput);
+ err = clSetKernelArg(redKernel, vclmem.size() + 1, sizeof(cl_int), static_cast<void*>(&nInput));
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clSetKernelArg", err, __FILE__, __LINE__);
+
+ SAL_INFO("sc.opencl", "Kernel " << redKernel << " arg " << (vclmem.size() + 2) << ": cl_int: " << nCurWindowSize);
+ err = clSetKernelArg(redKernel, vclmem.size() + 2, sizeof(cl_int), static_cast<void*>(&nCurWindowSize));
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clSetKernelArg", err, __FILE__, __LINE__);
+ // set work group size and execute
+ size_t global_work_size[] = { 256, static_cast<size_t>(nVectorWidth) };
+ size_t const local_work_size[] = { 256, 1 };
+ SAL_INFO("sc.opencl", "Enqueuing kernel " << redKernel);
+ err = clEnqueueNDRangeKernel(kEnv.mpkCmdQueue, redKernel, 2, nullptr,
+ global_work_size, local_work_size, 0, nullptr, nullptr);
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clEnqueueNDRangeKernel", err, __FILE__, __LINE__);
+
+ err = clFinish(kEnv.mpkCmdQueue);
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clFinish", err, __FILE__, __LINE__);
+
+ SAL_INFO("sc.opencl", "Releasing kernel " << redKernel);
+ err = clReleaseKernel(redKernel);
+ SAL_WARN_IF(err != CL_SUCCESS, "sc.opencl", "clReleaseKernel failed: " << openclwrapper::errorString(err));
+
+ // Pass mpClmem2 to the "real" kernel
+ SAL_INFO("sc.opencl", "Kernel " << k << " arg " << argno << ": cl_mem: " << mpClmem2);
+ err = clSetKernelArg(k, argno, sizeof(cl_mem), static_cast<void*>(&mpClmem2));
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clSetKernelArg", err, __FILE__, __LINE__);
+ }
+ }
+ return i;
+ }
+
+ virtual void GenSlidingWindowFunction( std::stringstream& ss ) override
+ {
+ for (DynamicKernelArgumentRef & rArg : mvSubArguments)
+ rArg->GenSlidingWindowFunction(ss);
+ mpCodeGen->GenSlidingWindowFunction(ss, mSymName, mvSubArguments);
+ }
+ virtual void GenDeclRef( std::stringstream& ss ) const override
+ {
+ for (size_t i = 0; i < mvSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ mvSubArguments[i]->GenDeclRef(ss);
+ }
+ }
+ virtual void GenDecl( std::stringstream& ss ) const override
+ {
+ for (SubArgumentsType::const_iterator it = mvSubArguments.begin(), e = mvSubArguments.end(); it != e;
+ ++it)
+ {
+ if (it != mvSubArguments.begin())
+ ss << ", ";
+ (*it)->GenDecl(ss);
+ }
+ }
+
+ virtual size_t GetWindowSize() const override
+ {
+ size_t nCurWindowSize = 0;
+ for (const auto & rSubArgument : mvSubArguments)
+ {
+ size_t nCurChildWindowSize = rSubArgument->GetWindowSize();
+ nCurWindowSize = (nCurWindowSize < nCurChildWindowSize) ?
+ nCurChildWindowSize : nCurWindowSize;
+ }
+ return nCurWindowSize;
+ }
+
+ /// When declared as input to a sliding window function
+ virtual void GenSlidingWindowDecl( std::stringstream& ss ) const override
+ {
+ for (SubArgumentsType::const_iterator it = mvSubArguments.begin(), e = mvSubArguments.end(); it != e;
+ ++it)
+ {
+ if (it != mvSubArguments.begin())
+ ss << ", ";
+ (*it)->GenSlidingWindowDecl(ss);
+ }
+ }
+ /// Generate either a function call to each children
+ /// or directly inline it if we are already inside a loop
+ virtual std::string GenSlidingWindowDeclRef( bool nested = false ) const override
+ {
+ std::stringstream ss;
+ if (!nested)
+ {
+ ss << mSymName << "_" << mpCodeGen->BinFuncName() << "(";
+ for (size_t i = 0; i < mvSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ", ";
+ mvSubArguments[i]->GenDeclRef(ss);
+ }
+ ss << ")";
+ }
+ else
+ {
+ if (mvSubArguments.size() != 2)
+ throw Unhandled(__FILE__, __LINE__);
+ bool bArgument1_NeedNested =
+ mvSubArguments[0]->GetFormulaToken()->GetType()
+ != formula::svSingleVectorRef;
+ bool bArgument2_NeedNested =
+ mvSubArguments[1]->GetFormulaToken()->GetType()
+ != formula::svSingleVectorRef;
+ ss << "(";
+ ss << mpCodeGen->
+ Gen2(mvSubArguments[0]
+ ->GenSlidingWindowDeclRef(bArgument1_NeedNested),
+ mvSubArguments[1]
+ ->GenSlidingWindowDeclRef(bArgument2_NeedNested));
+ ss << ")";
+ }
+ return ss.str();
+ }
+ virtual std::string DumpOpName() const override
+ {
+ std::string t = "_" + mpCodeGen->BinFuncName();
+ for (const auto & rSubArgument : mvSubArguments)
+ t += rSubArgument->DumpOpName();
+ return t;
+ }
+ virtual void DumpInlineFun( std::set<std::string>& decls,
+ std::set<std::string>& funs ) const override
+ {
+ mpCodeGen->BinInlineFun(decls, funs);
+ for (const auto & rSubArgument : mvSubArguments)
+ rSubArgument->DumpInlineFun(decls, funs);
+ }
+ virtual bool IsEmpty() const override
+ {
+ for (const auto & rSubArgument : mvSubArguments)
+ if( !rSubArgument->IsEmpty())
+ return false;
+ return true;
+ }
+ virtual ~DynamicKernelSoPArguments() override
+ {
+ if (mpClmem2)
+ {
+ cl_int err;
+ err = clReleaseMemObject(mpClmem2);
+ SAL_WARN_IF(err != CL_SUCCESS, "sc.opencl", "clReleaseMemObject failed: " << openclwrapper::errorString(err));
+ mpClmem2 = nullptr;
+ }
+ }
+
+private:
+ SubArgumentsType mvSubArguments;
+ std::shared_ptr<SlidingFunctionBase> mpCodeGen;
+ cl_mem mpClmem2;
+};
+
+}
+
+static DynamicKernelArgumentRef SoPHelper( const ScCalcConfig& config,
+ const std::string& ts, const FormulaTreeNodeRef& ft, std::shared_ptr<SlidingFunctionBase> pCodeGen,
+ int nResultSize )
+{
+ return std::make_shared<DynamicKernelSoPArguments>(config, ts, ft, std::move(pCodeGen), nResultSize);
+}
+
+template<class Base>
+static std::shared_ptr<DynamicKernelArgument> VectorRefFactory( const ScCalcConfig& config, const std::string& s,
+ const FormulaTreeNodeRef& ft,
+ std::shared_ptr<SlidingFunctionBase>& pCodeGen,
+ int index )
+{
+ //Black lists ineligible classes here ..
+ // SUMIFS does not perform parallel reduction at DoubleVectorRef level
+ if (dynamic_cast<OpSumIfs*>(pCodeGen.get()))
+ {
+ // coverity[identical_branches] - only identical if Base happens to be VectorRef
+ if (index == 0) // the first argument of OpSumIfs cannot be strings anyway
+ return std::make_shared<DynamicKernelSlidingArgument<VectorRef>>(config, s, ft, pCodeGen, index);
+ return std::make_shared<DynamicKernelSlidingArgument<Base>>(config, s, ft, pCodeGen, index);
+ }
+ // AVERAGE is not supported yet
+ //Average has been supported by reduction kernel
+ /*else if (dynamic_cast<OpAverage*>(pCodeGen.get()))
+ {
+ return new DynamicKernelSlidingArgument<Base>(config, s, ft, pCodeGen, index);
+ }*/
+ // MUL is not supported yet
+ else if (dynamic_cast<OpMul*>(pCodeGen.get()))
+ {
+ return std::make_shared<DynamicKernelSlidingArgument<Base>>(config, s, ft, pCodeGen, index);
+ }
+ // Sub is not a reduction per se
+ else if (dynamic_cast<OpSub*>(pCodeGen.get()))
+ {
+ return std::make_shared<DynamicKernelSlidingArgument<Base>>(config, s, ft, pCodeGen, index);
+ }
+ // Only child class of Reduction is supported
+ else if (!dynamic_cast<Reduction*>(pCodeGen.get()))
+ {
+ return std::make_shared<DynamicKernelSlidingArgument<Base>>(config, s, ft, pCodeGen, index);
+ }
+
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken*>(
+ ft->GetFormulaToken());
+ // Window being too small to justify a parallel reduction
+ if (pDVR->GetRefRowSize() < REDUCE_THRESHOLD)
+ return std::make_shared<DynamicKernelSlidingArgument<Base>>(config, s, ft, pCodeGen, index);
+ if (pDVR->IsStartFixed() == pDVR->IsEndFixed())
+ return std::make_shared<ParallelReductionVectorRef<Base>>(config, s, ft, pCodeGen, index);
+ else // Other cases are not supported as well
+ return std::make_shared<DynamicKernelSlidingArgument<Base>>(config, s, ft, pCodeGen, index);
+}
+
+DynamicKernelSoPArguments::DynamicKernelSoPArguments(const ScCalcConfig& config,
+ const std::string& s, const FormulaTreeNodeRef& ft, std::shared_ptr<SlidingFunctionBase> pCodeGen, int nResultSize ) :
+ DynamicKernelArgument(config, s, ft), mpCodeGen(pCodeGen), mpClmem2(nullptr)
+{
+ size_t nChildren = ft->Children.size();
+
+ for (size_t i = 0; i < nChildren; i++)
+ {
+ FormulaTreeNodeRef rChild = ft->Children[i];
+ if (!rChild)
+ throw Unhandled(__FILE__, __LINE__);
+ FormulaToken* pChild = rChild->GetFormulaToken();
+ if (!pChild)
+ throw Unhandled(__FILE__, __LINE__);
+ OpCode opc = pChild->GetOpCode();
+ std::stringstream tmpname;
+ tmpname << s << "_" << i;
+ std::string ts = tmpname.str();
+ switch (opc)
+ {
+ case ocPush:
+ if (pChild->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken*>(pChild);
+
+ // The code below will split one svDoubleVectorRef into one subargument
+ // for each column of data, and then all these subarguments will be later
+ // passed to the code generating the function. Most of the code then
+ // simply treats each subargument as one argument to the function, and thus
+ // could break in this case.
+ // As a simple solution, simply prevent this case, unless the code in question
+ // explicitly claims it will handle this situation properly.
+ if( pDVR->GetArrays().size() > 1 )
+ {
+ if( !pCodeGen->canHandleMultiVector())
+ throw UnhandledToken(("Function '" + pCodeGen->BinFuncName()
+ + "' cannot handle multi-column DoubleRef").c_str(), __FILE__, __LINE__);
+
+ SAL_INFO("sc.opencl", "multi-column DoubleRef");
+
+ }
+
+ // FIXME: The Right Thing to do would be to compare the accumulated kernel
+ // parameter size against the CL_DEVICE_MAX_PARAMETER_SIZE of the device, but
+ // let's just do this sanity check for now. The kernel compilation will
+ // hopefully fail anyway if the size of parameters exceeds the limit and this
+ // sanity check is just to make us bail out a bit earlier.
+
+ // The number 50 comes from the fact that the minimum size of
+ // CL_DEVICE_MAX_PARAMETER_SIZE is 256, which for 32-bit code probably means 64
+ // of them. Round down a bit.
+
+ if (pDVR->GetArrays().size() > 50)
+ throw UnhandledToken(("Kernel would have ridiculously many parameters (" + std::to_string(2 + pDVR->GetArrays().size()) + ")").c_str(), __FILE__, __LINE__);
+
+ for (size_t j = 0; j < pDVR->GetArrays().size(); ++j)
+ {
+ SAL_INFO("sc.opencl", "i=" << i << " j=" << j <<
+ " mpNumericArray=" << pDVR->GetArrays()[j].mpNumericArray <<
+ " mpStringArray=" << pDVR->GetArrays()[j].mpStringArray <<
+ " allStringsAreNull=" << (AllStringsAreNull(pDVR->GetArrays()[j].mpStringArray, pDVR->GetArrayLength())?"YES":"NO") <<
+ " takeNumeric=" << (pCodeGen->takeNumeric()?"YES":"NO") <<
+ " takeString=" << (pCodeGen->takeString()?"YES":"NO"));
+
+ if (pDVR->GetArrays()[j].mpNumericArray &&
+ pCodeGen->takeNumeric() &&
+ pDVR->GetArrays()[j].mpStringArray &&
+ pCodeGen->takeString())
+ {
+ // Function takes numbers or strings, there are both
+ SAL_INFO("sc.opencl", "Numbers and strings");
+ mvSubArguments.push_back(
+ std::make_shared<DynamicKernelMixedSlidingArgument>(mCalcConfig,
+ ts, ft->Children[i], mpCodeGen, j));
+ }
+ else if (pDVR->GetArrays()[j].mpNumericArray &&
+ pCodeGen->takeNumeric() &&
+ (AllStringsAreNull(pDVR->GetArrays()[j].mpStringArray, pDVR->GetArrayLength()) || mCalcConfig.meStringConversion == ScCalcConfig::StringConversion::ZERO))
+ {
+ // Function takes numbers, and either there
+ // are no strings, or there are strings but
+ // they are to be treated as zero
+ SAL_INFO("sc.opencl", "Numbers (no strings or strings treated as zero)");
+ mvSubArguments.push_back(
+ VectorRefFactory<VectorRef>(mCalcConfig,
+ ts, ft->Children[i], mpCodeGen, j));
+ }
+ else if (pDVR->GetArrays()[j].mpNumericArray == nullptr &&
+ pCodeGen->takeNumeric() &&
+ pDVR->GetArrays()[j].mpStringArray &&
+ mCalcConfig.meStringConversion == ScCalcConfig::StringConversion::ZERO)
+ {
+ // Function takes numbers, and there are only
+ // strings, but they are to be treated as zero
+ SAL_INFO("sc.opencl", "Only strings even if want numbers but should be treated as zero");
+ mvSubArguments.push_back(
+ VectorRefFactory<VectorRef>(mCalcConfig,
+ ts, ft->Children[i], mpCodeGen, j));
+ }
+ else if (pDVR->GetArrays()[j].mpStringArray &&
+ pCodeGen->takeString())
+ {
+ // There are strings, and the function takes strings.
+ SAL_INFO("sc.opencl", "Strings only");
+ mvSubArguments.push_back(
+ VectorRefFactory
+ <DynamicKernelStringArgument>(mCalcConfig,
+ ts, ft->Children[i], mpCodeGen, j));
+ }
+ else if (AllStringsAreNull(pDVR->GetArrays()[j].mpStringArray, pDVR->GetArrayLength()) &&
+ pDVR->GetArrays()[j].mpNumericArray == nullptr)
+ {
+ // There are only empty cells. Push as an
+ // array of NANs
+ SAL_INFO("sc.opencl", "Only empty cells");
+ mvSubArguments.push_back(
+ VectorRefFactory<VectorRef>(mCalcConfig,
+ ts, ft->Children[i], mpCodeGen, j));
+ }
+ else
+ {
+ SAL_INFO("sc.opencl", "Unhandled case, rejecting for OpenCL");
+ throw UnhandledToken(("Unhandled numbers/strings combination for '"
+ + pCodeGen->BinFuncName() + "'").c_str(), __FILE__, __LINE__);
+ }
+ }
+ }
+ else if (pChild->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast<const formula::SingleVectorRefToken*>(pChild);
+
+ SAL_INFO("sc.opencl", "i=" << i <<
+ " mpNumericArray=" << pSVR->GetArray().mpNumericArray <<
+ " mpStringArray=" << pSVR->GetArray().mpStringArray <<
+ " allStringsAreNull=" << (AllStringsAreNull(pSVR->GetArray().mpStringArray, pSVR->GetArrayLength())?"YES":"NO") <<
+ " takeNumeric=" << (pCodeGen->takeNumeric()?"YES":"NO") <<
+ " takeString=" << (pCodeGen->takeString()?"YES":"NO"));
+
+ if (pSVR->GetArray().mpNumericArray &&
+ pCodeGen->takeNumeric() &&
+ pSVR->GetArray().mpStringArray &&
+ pCodeGen->takeString())
+ {
+ // Function takes numbers or strings, there are both
+ SAL_INFO("sc.opencl", "Numbers and strings");
+ mvSubArguments.push_back(
+ std::make_shared<DynamicKernelMixedArgument>(mCalcConfig,
+ ts, ft->Children[i]));
+ }
+ else if (pSVR->GetArray().mpNumericArray &&
+ pCodeGen->takeNumeric() &&
+ (AllStringsAreNull(pSVR->GetArray().mpStringArray, pSVR->GetArrayLength()) || mCalcConfig.meStringConversion == ScCalcConfig::StringConversion::ZERO))
+ {
+ // Function takes numbers, and either there
+ // are no strings, or there are strings but
+ // they are to be treated as zero
+ SAL_INFO("sc.opencl", "Numbers (no strings or strings treated as zero)");
+ mvSubArguments.push_back(
+ std::make_shared<VectorRef>(mCalcConfig, ts,
+ ft->Children[i]));
+ }
+ else if (pSVR->GetArray().mpNumericArray == nullptr &&
+ pCodeGen->takeNumeric() &&
+ pSVR->GetArray().mpStringArray &&
+ mCalcConfig.meStringConversion == ScCalcConfig::StringConversion::ZERO)
+ {
+ // Function takes numbers, and there are only
+ // strings, but they are to be treated as zero
+ SAL_INFO("sc.opencl", "Only strings even if want numbers but should be treated as zero");
+ mvSubArguments.push_back(
+ std::make_shared<VectorRef>(mCalcConfig, ts,
+ ft->Children[i]));
+ }
+ else if (pSVR->GetArray().mpStringArray &&
+ pCodeGen->takeString())
+ {
+ // There are strings, and the function takes strings.
+ SAL_INFO("sc.opencl", "Strings only");
+ mvSubArguments.push_back(
+ std::make_shared<DynamicKernelStringArgument>(mCalcConfig,
+ ts, ft->Children[i]));
+ }
+ else if (AllStringsAreNull(pSVR->GetArray().mpStringArray, pSVR->GetArrayLength()) &&
+ pSVR->GetArray().mpNumericArray == nullptr)
+ {
+ // There are only empty cells. Push as an
+ // array of NANs
+ SAL_INFO("sc.opencl", "Only empty cells");
+ mvSubArguments.push_back(
+ std::make_shared<VectorRef>(mCalcConfig, ts,
+ ft->Children[i]));
+ }
+ else
+ {
+ SAL_INFO("sc.opencl", "Unhandled case, rejecting for OpenCL");
+ throw UnhandledToken(("Unhandled numbers/strings combination for '"
+ + pCodeGen->BinFuncName() + "'").c_str(), __FILE__, __LINE__);
+ }
+ }
+ else if (pChild->GetType() == formula::svDouble)
+ {
+ SAL_INFO("sc.opencl", "Constant number case");
+ mvSubArguments.push_back(
+ std::make_shared<DynamicKernelConstantArgument>(mCalcConfig, ts,
+ ft->Children[i]));
+ }
+ else if (pChild->GetType() == formula::svString
+ && pCodeGen->takeString())
+ {
+ SAL_INFO("sc.opencl", "Constant string case");
+ mvSubArguments.push_back(
+ std::make_shared<ConstStringArgument>(mCalcConfig, ts,
+ ft->Children[i]));
+ }
+ else
+ {
+ SAL_INFO("sc.opencl", "Unhandled operand, rejecting for OpenCL");
+ throw UnhandledToken(("unhandled operand " + StackVarEnumToString(pChild->GetType()) + " for ocPush").c_str(), __FILE__, __LINE__);
+ }
+ break;
+ case ocDiv:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpDiv>(nResultSize), nResultSize));
+ break;
+ case ocMul:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpMul>(nResultSize), nResultSize));
+ break;
+ case ocSub:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpSub>(nResultSize), nResultSize));
+ break;
+ case ocAdd:
+ case ocSum:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpSum>(nResultSize), nResultSize));
+ break;
+ case ocAverage:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpAverage>(nResultSize), nResultSize));
+ break;
+ case ocMin:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpMin>(nResultSize), nResultSize));
+ break;
+ case ocMax:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpMax>(nResultSize), nResultSize));
+ break;
+ case ocCount:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpCount>(nResultSize), nResultSize));
+ break;
+ case ocSumProduct:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpSumProduct>(), nResultSize));
+ break;
+ case ocIRR:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpIRR>(), nResultSize));
+ break;
+ case ocMIRR:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpMIRR>(), nResultSize));
+ break;
+ case ocPMT:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpPMT>(), nResultSize));
+ break;
+ case ocRate:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpIntrate>(), nResultSize));
+ break;
+ case ocRRI:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpRRI>(), nResultSize));
+ break;
+ case ocPpmt:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpPPMT>(), nResultSize));
+ break;
+ case ocFisher:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpFisher>(), nResultSize));
+ break;
+ case ocFisherInv:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpFisherInv>(), nResultSize));
+ break;
+ case ocGamma:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpGamma>(), nResultSize));
+ break;
+ case ocSLN:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpSLN>(), nResultSize));
+ break;
+ case ocGammaLn:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpGammaLn>(), nResultSize));
+ break;
+ case ocGauss:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpGauss>(), nResultSize));
+ break;
+ /*case ocGeoMean:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpGeoMean));
+ break;*/
+ case ocHarMean:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpHarMean>(), nResultSize));
+ break;
+ case ocLessEqual:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpLessEqual>(), nResultSize));
+ break;
+ case ocLess:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpLess>(), nResultSize));
+ break;
+ case ocEqual:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpEqual>(), nResultSize));
+ break;
+ case ocGreater:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpGreater>(), nResultSize));
+ break;
+ case ocSYD:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpSYD>(), nResultSize));
+ break;
+ case ocCorrel:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpCorrel>(), nResultSize));
+ break;
+ case ocCos:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpCos>(), nResultSize));
+ break;
+ case ocNegBinomVert :
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpNegbinomdist>(), nResultSize));
+ break;
+ case ocPearson:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpPearson>(), nResultSize));
+ break;
+ case ocRSQ:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpRsq>(), nResultSize));
+ break;
+ case ocCosecant:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpCsc>(), nResultSize));
+ break;
+ case ocISPMT:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpISPMT>(), nResultSize));
+ break;
+ case ocPDuration:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpPDuration>(), nResultSize));
+ break;
+ case ocSinHyp:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpSinh>(), nResultSize));
+ break;
+ case ocAbs:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpAbs>(), nResultSize));
+ break;
+ case ocPV:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpPV>(), nResultSize));
+ break;
+ case ocSin:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpSin>(), nResultSize));
+ break;
+ case ocTan:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpTan>(), nResultSize));
+ break;
+ case ocTanHyp:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpTanH>(), nResultSize));
+ break;
+ case ocStandard:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpStandard>(), nResultSize));
+ break;
+ case ocWeibull:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpWeibull>(), nResultSize));
+ break;
+ /*case ocMedian:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i],std::make_shared<OpMedian));
+ break;*/
+ case ocDDB:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpDDB>(), nResultSize));
+ break;
+ case ocFV:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpFV>(), nResultSize));
+ break;
+ case ocSumIfs:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpSumIfs>(), nResultSize));
+ break;
+ /*case ocVBD:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i],std::make_shared<OpVDB));
+ break;*/
+ case ocKurt:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpKurt>(), nResultSize));
+ break;
+ /*case ocNper:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpNper));
+ break;*/
+ case ocNormDist:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpNormdist>(), nResultSize));
+ break;
+ case ocArcCos:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpArcCos>(), nResultSize));
+ break;
+ case ocSqrt:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpSqrt>(), nResultSize));
+ break;
+ case ocArcCosHyp:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpArcCosHyp>(), nResultSize));
+ break;
+ case ocNPV:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpNPV>(), nResultSize));
+ break;
+ case ocStdNormDist:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpNormsdist>(), nResultSize));
+ break;
+ case ocNormInv:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpNorminv>(), nResultSize));
+ break;
+ case ocSNormInv:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpNormsinv>(), nResultSize));
+ break;
+ case ocPermut:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpPermut>(), nResultSize));
+ break;
+ case ocPermutationA:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpPermutationA>(), nResultSize));
+ break;
+ case ocPhi:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpPhi>(), nResultSize));
+ break;
+ case ocIpmt:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpIPMT>(), nResultSize));
+ break;
+ case ocConfidence:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpConfidence>(), nResultSize));
+ break;
+ case ocIntercept:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpIntercept>(), nResultSize));
+ break;
+ case ocDB:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i],
+ std::make_shared<OpDB>(), nResultSize));
+ break;
+ case ocLogInv:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpLogInv>(), nResultSize));
+ break;
+ case ocArcCot:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpArcCot>(), nResultSize));
+ break;
+ case ocCosHyp:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpCosh>(), nResultSize));
+ break;
+ case ocCritBinom:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpCritBinom>(), nResultSize));
+ break;
+ case ocArcCotHyp:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpArcCotHyp>(), nResultSize));
+ break;
+ case ocArcSin:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpArcSin>(), nResultSize));
+ break;
+ case ocArcSinHyp:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpArcSinHyp>(), nResultSize));
+ break;
+ case ocArcTan:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpArcTan>(), nResultSize));
+ break;
+ case ocArcTanHyp:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpArcTanH>(), nResultSize));
+ break;
+ case ocBitAnd:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpBitAnd>(), nResultSize));
+ break;
+ case ocForecast:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpForecast>(), nResultSize));
+ break;
+ case ocLogNormDist:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpLogNormDist>(), nResultSize));
+ break;
+ /*case ocGammaDist:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpGammaDist));
+ break;*/
+ case ocLn:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpLn>(), nResultSize));
+ break;
+ case ocRound:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpRound>(), nResultSize));
+ break;
+ case ocCot:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpCot>(), nResultSize));
+ break;
+ case ocCotHyp:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpCoth>(), nResultSize));
+ break;
+ case ocFDist:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpFdist>(), nResultSize));
+ break;
+ case ocVar:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpVar>(), nResultSize));
+ break;
+ /*case ocChiDist:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i],std::make_shared<OpChiDist));
+ break;*/
+ case ocPow:
+ case ocPower:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpPower>(), nResultSize));
+ break;
+ case ocOdd:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpOdd>(), nResultSize));
+ break;
+ /*case ocChiSqDist:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i],std::make_shared<OpChiSqDist));
+ break;
+ case ocChiSqInv:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i],std::make_shared<OpChiSqInv));
+ break;
+ case ocGammaInv:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpGammaInv));
+ break;*/
+ case ocFloor:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpFloor>(), nResultSize));
+ break;
+ /*case ocFInv:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpFInv));
+ break;*/
+ case ocFTest:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpFTest>(), nResultSize));
+ break;
+ case ocB:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpB>(), nResultSize));
+ break;
+ case ocBetaDist:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpBetaDist>(), nResultSize));
+ break;
+ case ocCosecantHyp:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpCscH>(), nResultSize));
+ break;
+ case ocExp:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpExp>(), nResultSize));
+ break;
+ case ocLog10:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpLog10>(), nResultSize));
+ break;
+ case ocExpDist:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpExponDist>(), nResultSize));
+ break;
+ case ocAverageIfs:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpAverageIfs>(), nResultSize));
+ break;
+ case ocCountIfs:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpCountIfs>(), nResultSize));
+ break;
+ case ocCombinA:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpCombinA>(), nResultSize));
+ break;
+ case ocEven:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpEven>(), nResultSize));
+ break;
+ case ocLog:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpLog>(), nResultSize));
+ break;
+ case ocMod:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpMod>(), nResultSize));
+ break;
+ case ocTrunc:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpTrunc>(), nResultSize));
+ break;
+ case ocSkew:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpSkew>(), nResultSize));
+ break;
+ case ocArcTan2:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpArcTan2>(), nResultSize));
+ break;
+ case ocBitOr:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpBitOr>(), nResultSize));
+ break;
+ case ocBitLshift:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpBitLshift>(), nResultSize));
+ break;
+ case ocBitRshift:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpBitRshift>(), nResultSize));
+ break;
+ case ocBitXor:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpBitXor>(), nResultSize));
+ break;
+ /*case ocChiInv:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i],std::make_shared<OpChiInv));
+ break;*/
+ case ocPoissonDist:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpPoisson>(), nResultSize));
+ break;
+ case ocSumSQ:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpSumSQ>(), nResultSize));
+ break;
+ case ocSkewp:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpSkewp>(), nResultSize));
+ break;
+ case ocBinomDist:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpBinomdist>(), nResultSize));
+ break;
+ case ocVarP:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpVarP>(), nResultSize));
+ break;
+ case ocCeil:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpCeil>(), nResultSize));
+ break;
+ case ocCombin:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpCombin>(), nResultSize));
+ break;
+ case ocDevSq:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpDevSq>(), nResultSize));
+ break;
+ case ocStDev:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpStDev>(), nResultSize));
+ break;
+ case ocSlope:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpSlope>(), nResultSize));
+ break;
+ case ocSTEYX:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpSTEYX>(), nResultSize));
+ break;
+ case ocZTest:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpZTest>(), nResultSize));
+ break;
+ case ocPi:
+ mvSubArguments.push_back(
+ std::make_shared<DynamicKernelPiArgument>(mCalcConfig, ts,
+ ft->Children[i]));
+ break;
+ case ocRandom:
+ mvSubArguments.push_back(
+ std::make_shared<DynamicKernelRandomArgument>(mCalcConfig, ts,
+ ft->Children[i]));
+ break;
+ case ocProduct:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpProduct>(), nResultSize));
+ break;
+ /*case ocHypGeomDist:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i],std::make_shared<OpHypGeomDist));
+ break;*/
+ case ocSumX2MY2:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpSumX2MY2>(), nResultSize));
+ break;
+ case ocSumX2DY2:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpSumX2PY2>(), nResultSize));
+ break;
+ /*case ocBetaInv:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i],std::make_shared<OpBetainv));
+ break;*/
+ case ocTTest:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpTTest>(), nResultSize));
+ break;
+ case ocTDist:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpTDist>(), nResultSize));
+ break;
+ /*case ocTInv:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpTInv));
+ break;*/
+ case ocSumXMY2:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpSumXMY2>(), nResultSize));
+ break;
+ case ocStDevP:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpStDevP>(), nResultSize));
+ break;
+ case ocCovar:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpCovar>(), nResultSize));
+ break;
+ case ocAnd:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpAnd>(), nResultSize));
+ break;
+ case ocVLookup:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpVLookup>(), nResultSize));
+ break;
+ case ocOr:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpOr>(), nResultSize));
+ break;
+ case ocNot:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpNot>(), nResultSize));
+ break;
+ case ocXor:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpXor>(), nResultSize));
+ break;
+ case ocDBMax:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpDmax>(), nResultSize));
+ break;
+ case ocDBMin:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpDmin>(), nResultSize));
+ break;
+ case ocDBProduct:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpDproduct>(), nResultSize));
+ break;
+ case ocDBAverage:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpDaverage>(), nResultSize));
+ break;
+ case ocDBStdDev:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpDstdev>(), nResultSize));
+ break;
+ case ocDBStdDevP:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpDstdevp>(), nResultSize));
+ break;
+ case ocDBSum:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpDsum>(), nResultSize));
+ break;
+ case ocDBVar:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpDvar>(), nResultSize));
+ break;
+ case ocDBVarP:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpDvarp>(), nResultSize));
+ break;
+ case ocAverageIf:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpAverageIf>(), nResultSize));
+ break;
+ case ocDBCount:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpDcount>(), nResultSize));
+ break;
+ case ocDBCount2:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpDcount2>(), nResultSize));
+ break;
+ case ocDeg:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpDeg>(), nResultSize));
+ break;
+ case ocRoundUp:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpRoundUp>(), nResultSize));
+ break;
+ case ocRoundDown:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpRoundDown>(), nResultSize));
+ break;
+ case ocInt:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpInt>(), nResultSize));
+ break;
+ case ocRad:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpRadians>(), nResultSize));
+ break;
+ case ocCountIf:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpCountIf>(), nResultSize));
+ break;
+ case ocIsEven:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpIsEven>(), nResultSize));
+ break;
+ case ocIsOdd:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpIsOdd>(), nResultSize));
+ break;
+ case ocFact:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpFact>(), nResultSize));
+ break;
+ case ocMinA:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpMinA>(), nResultSize));
+ break;
+ case ocCount2:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpCountA>(), nResultSize));
+ break;
+ case ocMaxA:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpMaxA>(), nResultSize));
+ break;
+ case ocAverageA:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpAverageA>(), nResultSize));
+ break;
+ case ocVarA:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpVarA>(), nResultSize));
+ break;
+ case ocVarPA:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpVarPA>(), nResultSize));
+ break;
+ case ocStDevA:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpStDevA>(), nResultSize));
+ break;
+ case ocStDevPA:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpStDevPA>(), nResultSize));
+ break;
+ case ocSecant:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpSec>(), nResultSize));
+ break;
+ case ocSecantHyp:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpSecH>(), nResultSize));
+ break;
+ case ocSumIf:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpSumIf>(), nResultSize));
+ break;
+ case ocNegSub:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpNegSub>(), nResultSize));
+ break;
+ case ocAveDev:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpAveDev>(), nResultSize));
+ break;
+ case ocIf:
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpIf>(), nResultSize));
+ break;
+ case ocExternal:
+ if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getEffect")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpEffective>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getCumipmt")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpCumipmt>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getNominal")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpNominal>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getCumprinc")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpCumprinc>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getXnpv")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpXNPV>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getPricemat")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpPriceMat>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getReceived")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpReceived>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getTbilleq")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpTbilleq>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getTbillprice")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpTbillprice>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getTbillyield")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpTbillyield>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getFvschedule")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpFvschedule>(), nResultSize));
+ }
+ /*else if ( pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getYield")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpYield));
+ }*/
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getYielddisc")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpYielddisc>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getYieldmat")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpYieldmat>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getAccrintm")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpAccrintm>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getCoupdaybs")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpCoupdaybs>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getDollarde")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpDollarde>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getDollarfr")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpDollarfr>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getCoupdays")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpCoupdays>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getCoupdaysnc")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpCoupdaysnc>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getDisc")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpDISC>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getIntrate")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpINTRATE>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getPrice")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpPrice>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getCoupnum")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i],
+ std::make_shared<OpCoupnum>(), nResultSize));
+ }
+ /*else if pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getDuration"))
+ {
+ mvSubArguments.push_back(
+ SoPHelper(mCalcConfig, ts, ft->Children[i], std::make_shared<OpDuration_ADD));
+ }*/
+ /*else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getAmordegrc")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i],
+ std::make_shared<OpAmordegrc, nResultSize));
+ }*/
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getAmorlinc")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i],
+ std::make_shared<OpAmorlinc>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getMduration")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i],
+ std::make_shared<OpMDuration>(), nResultSize));
+ }
+ /*else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getXirr")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i],
+ std::make_shared<OpXirr, nResultSize));
+ }*/
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getOddlprice")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpOddlprice>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getOddlyield")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i],
+ std::make_shared<OpOddlyield>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getPricedisc")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts,
+ ft->Children[i], std::make_shared<OpPriceDisc>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getCouppcd")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i],
+ std::make_shared<OpCouppcd>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getCoupncd")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i],
+ std::make_shared<OpCoupncd>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getAccrint")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i],
+ std::make_shared<OpAccrint>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getSqrtpi")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i],
+ std::make_shared<OpSqrtPi>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getConvert")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i],
+ std::make_shared<OpConvert>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getIseven")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i],
+ std::make_shared<OpIsEven>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getIsodd")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i],
+ std::make_shared<OpIsOdd>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getMround")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i],
+ std::make_shared<OpMROUND>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getQuotient")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i],
+ std::make_shared<OpQuotient>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getSeriessum")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i],
+ std::make_shared<OpSeriesSum>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getBesselj")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i],
+ std::make_shared<OpBesselj>(), nResultSize));
+ }
+ else if (pChild->GetExternal() == "com.sun.star.sheet.addin.Analysis.getGestep")
+ {
+ mvSubArguments.push_back(SoPHelper(mCalcConfig, ts, ft->Children[i],
+ std::make_shared<OpGestep>(), nResultSize));
+ }
+ else
+ throw UnhandledToken(OUString("unhandled external " + pChild->GetExternal()).toUtf8().getStr(), __FILE__, __LINE__);
+ break;
+
+ default:
+ throw UnhandledToken(OUString("unhandled opcode "
+ + formula::FormulaCompiler().GetOpCodeMap(com::sun::star::sheet::FormulaLanguage::ENGLISH)->getSymbol(opc)
+ + "(" + OUString::number(opc) + ")").toUtf8().getStr(), __FILE__, __LINE__);
+ }
+ }
+}
+
+namespace {
+
+class DynamicKernel : public CompiledFormula
+{
+public:
+ DynamicKernel( const ScCalcConfig& config, const FormulaTreeNodeRef& r, int nResultSize );
+ virtual ~DynamicKernel() override;
+
+ static std::shared_ptr<DynamicKernel> create( const ScCalcConfig& config, const ScTokenArray& rCode, int nResultSize );
+
+ /// OpenCL code generation
+ void CodeGen();
+
+ /// Produce kernel hash
+ std::string const & GetMD5();
+
+ /// Create program, build, and create kernel
+ /// TODO cache results based on kernel body hash
+ /// TODO: abstract OpenCL part out into OpenCL wrapper.
+ void CreateKernel();
+
+ /// Prepare buffers, marshal them to GPU, and launch the kernel
+ /// TODO: abstract OpenCL part out into OpenCL wrapper.
+ void Launch( size_t nr );
+
+ cl_mem GetResultBuffer() const { return mpResClmem; }
+
+private:
+ ScCalcConfig mCalcConfig;
+ FormulaTreeNodeRef mpRoot;
+ SymbolTable mSyms;
+ std::string mKernelSignature, mKernelHash;
+ std::string mFullProgramSrc;
+ cl_program mpProgram;
+ cl_kernel mpKernel;
+ cl_mem mpResClmem; // Results
+ std::set<std::string> inlineDecl;
+ std::set<std::string> inlineFun;
+
+ int mnResultSize;
+};
+
+}
+
+DynamicKernel::DynamicKernel( const ScCalcConfig& config, const FormulaTreeNodeRef& r, int nResultSize ) :
+ mCalcConfig(config),
+ mpRoot(r),
+ mpProgram(nullptr),
+ mpKernel(nullptr),
+ mpResClmem(nullptr),
+ mnResultSize(nResultSize) {}
+
+DynamicKernel::~DynamicKernel()
+{
+ cl_int err;
+ if (mpResClmem)
+ {
+ err = clReleaseMemObject(mpResClmem);
+ SAL_WARN_IF(err != CL_SUCCESS, "sc.opencl", "clReleaseMemObject failed: " << openclwrapper::errorString(err));
+ }
+ if (mpKernel)
+ {
+ SAL_INFO("sc.opencl", "Releasing kernel " << mpKernel);
+ err = clReleaseKernel(mpKernel);
+ SAL_WARN_IF(err != CL_SUCCESS, "sc.opencl", "clReleaseKernel failed: " << openclwrapper::errorString(err));
+ }
+ // mpProgram is not going to be released here -- it's cached.
+}
+
+void DynamicKernel::CodeGen()
+{
+ // Traverse the tree of expression and declare symbols used
+ const DynamicKernelArgument* DK = mSyms.DeclRefArg<DynamicKernelSoPArguments>(mCalcConfig, mpRoot, std::make_shared<OpNop>(mnResultSize), mnResultSize);
+
+ std::stringstream decl;
+ if (openclwrapper::gpuEnv.mnKhrFp64Flag)
+ {
+ decl << "#if __OPENCL_VERSION__ < 120\n";
+ decl << "#pragma OPENCL EXTENSION cl_khr_fp64: enable\n";
+ decl << "#endif\n";
+ }
+ else if (openclwrapper::gpuEnv.mnAmdFp64Flag)
+ {
+ decl << "#pragma OPENCL EXTENSION cl_amd_fp64: enable\n";
+ }
+ // preambles
+ decl << publicFunc;
+ DK->DumpInlineFun(inlineDecl, inlineFun);
+ for (const auto& rItem : inlineDecl)
+ {
+ decl << rItem;
+ }
+
+ for (const auto& rItem : inlineFun)
+ {
+ decl << rItem;
+ }
+ mSyms.DumpSlidingWindowFunctions(decl);
+ mKernelSignature = DK->DumpOpName();
+ decl << "__kernel void DynamicKernel" << mKernelSignature;
+ decl << "(__global double *result";
+ if( !DK->IsEmpty())
+ {
+ decl << ", ";
+ DK->GenSlidingWindowDecl(decl);
+ }
+ decl << ") {\n\tint gid0 = get_global_id(0);\n\tresult[gid0] = " <<
+ DK->GenSlidingWindowDeclRef() << ";\n}\n";
+ mFullProgramSrc = decl.str();
+ SAL_INFO(
+ "sc.opencl.source",
+ (mKernelSignature[0] == '_'
+ ? mKernelSignature.substr(1, std::string::npos) : mKernelSignature)
+ << " program to be compiled:\n" << linenumberify(mFullProgramSrc));
+}
+
+std::string const & DynamicKernel::GetMD5()
+{
+ if (mKernelHash.empty())
+ {
+ std::stringstream md5s;
+ // Compute MD5SUM of kernel body to obtain the name
+ sal_uInt8 result[RTL_DIGEST_LENGTH_MD5];
+ rtl_digest_MD5(
+ mFullProgramSrc.c_str(),
+ mFullProgramSrc.length(), result,
+ RTL_DIGEST_LENGTH_MD5);
+ for (sal_uInt8 i : result)
+ {
+ md5s << std::hex << static_cast<int>(i);
+ }
+ mKernelHash = md5s.str();
+ }
+ return mKernelHash;
+}
+
+/// Build code
+void DynamicKernel::CreateKernel()
+{
+ if (mpKernel)
+ // already created.
+ return;
+
+ cl_int err;
+ std::string kname = "DynamicKernel" + mKernelSignature;
+ // Compile kernel here!!!
+
+ OpenCLZone zone;
+ openclwrapper::KernelEnv kEnv;
+ openclwrapper::setKernelEnv(&kEnv);
+ const char* src = mFullProgramSrc.c_str();
+ static std::string lastOneKernelHash;
+ static std::string lastSecondKernelHash;
+ static cl_program lastOneProgram = nullptr;
+ static cl_program lastSecondProgram = nullptr;
+ std::string KernelHash = mKernelSignature + GetMD5();
+ if (lastOneKernelHash == KernelHash && lastOneProgram)
+ {
+ mpProgram = lastOneProgram;
+ }
+ else if (lastSecondKernelHash == KernelHash && lastSecondProgram)
+ {
+ mpProgram = lastSecondProgram;
+ }
+ else
+ { // doesn't match the last compiled formula.
+
+ if (lastSecondProgram)
+ {
+ SAL_INFO("sc.opencl", "Releasing program " << lastSecondProgram);
+ err = clReleaseProgram(lastSecondProgram);
+ SAL_WARN_IF(err != CL_SUCCESS, "sc.opencl", "clReleaseProgram failed: " << openclwrapper::errorString(err));
+ lastSecondProgram = nullptr;
+ }
+ if (openclwrapper::buildProgramFromBinary("",
+ &openclwrapper::gpuEnv, KernelHash.c_str(), 0))
+ {
+ mpProgram = openclwrapper::gpuEnv.mpArryPrograms[0];
+ openclwrapper::gpuEnv.mpArryPrograms[0] = nullptr;
+ }
+ else
+ {
+ mpProgram = clCreateProgramWithSource(kEnv.mpkContext, 1,
+ &src, nullptr, &err);
+ if (err != CL_SUCCESS)
+ throw OpenCLError("clCreateProgramWithSource", err, __FILE__, __LINE__);
+ SAL_INFO("sc.opencl", "Created program " << mpProgram);
+
+ err = clBuildProgram(mpProgram, 1,
+ &openclwrapper::gpuEnv.mpDevID, "", nullptr, nullptr);
+ if (err != CL_SUCCESS)
+ {
+#if OSL_DEBUG_LEVEL > 0
+ if (err == CL_BUILD_PROGRAM_FAILURE)
+ {
+ cl_build_status stat;
+ cl_int e = clGetProgramBuildInfo(
+ mpProgram, openclwrapper::gpuEnv.mpDevID,
+ CL_PROGRAM_BUILD_STATUS, sizeof(cl_build_status),
+ &stat, nullptr);
+ SAL_WARN_IF(
+ e != CL_SUCCESS, "sc.opencl",
+ "after CL_BUILD_PROGRAM_FAILURE,"
+ " clGetProgramBuildInfo(CL_PROGRAM_BUILD_STATUS)"
+ " fails with " << openclwrapper::errorString(e));
+ if (e == CL_SUCCESS)
+ {
+ size_t n;
+ e = clGetProgramBuildInfo(
+ mpProgram, openclwrapper::gpuEnv.mpDevID,
+ CL_PROGRAM_BUILD_LOG, 0, nullptr, &n);
+ SAL_WARN_IF(
+ e != CL_SUCCESS || n == 0, "sc.opencl",
+ "after CL_BUILD_PROGRAM_FAILURE,"
+ " clGetProgramBuildInfo(CL_PROGRAM_BUILD_LOG)"
+ " fails with " << openclwrapper::errorString(e) << ", n=" << n);
+ if (e == CL_SUCCESS && n != 0)
+ {
+ std::vector<char> log(n);
+ e = clGetProgramBuildInfo(
+ mpProgram, openclwrapper::gpuEnv.mpDevID,
+ CL_PROGRAM_BUILD_LOG, n, log.data(), nullptr);
+ SAL_WARN_IF(
+ e != CL_SUCCESS || n == 0, "sc.opencl",
+ "after CL_BUILD_PROGRAM_FAILURE,"
+ " clGetProgramBuildInfo("
+ "CL_PROGRAM_BUILD_LOG) fails with " << openclwrapper::errorString(e));
+ if (e == CL_SUCCESS)
+ SAL_WARN(
+ "sc.opencl",
+ "CL_BUILD_PROGRAM_FAILURE, status " << stat
+ << ", log \"" << log.data() << "\"");
+ }
+ }
+ }
+#endif
+#ifdef DBG_UTIL
+ SAL_WARN("sc.opencl", "Program failed to build, aborting.");
+ abort(); // make sure errors such as typos don't accidentally go unnoticed
+#else
+ throw OpenCLError("clBuildProgram", err, __FILE__, __LINE__);
+#endif
+ }
+ SAL_INFO("sc.opencl", "Built program " << mpProgram);
+
+ // Generate binary out of compiled kernel.
+ openclwrapper::generatBinFromKernelSource(mpProgram,
+ (mKernelSignature + GetMD5()).c_str());
+ }
+ lastSecondKernelHash = lastOneKernelHash;
+ lastSecondProgram = lastOneProgram;
+ lastOneKernelHash = KernelHash;
+ lastOneProgram = mpProgram;
+ }
+ mpKernel = clCreateKernel(mpProgram, kname.c_str(), &err);
+ if (err != CL_SUCCESS)
+ throw OpenCLError("clCreateKernel", err, __FILE__, __LINE__);
+ SAL_INFO("sc.opencl", "Created kernel " << mpKernel << " with name " << kname << " in program " << mpProgram);
+}
+
+void DynamicKernel::Launch( size_t nr )
+{
+ OpenCLZone zone;
+ openclwrapper::KernelEnv kEnv;
+ openclwrapper::setKernelEnv(&kEnv);
+ cl_int err;
+ // The results
+ mpResClmem = clCreateBuffer(kEnv.mpkContext,
+ cl_mem_flags(CL_MEM_READ_WRITE) | CL_MEM_ALLOC_HOST_PTR,
+ nr * sizeof(double), nullptr, &err);
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clCreateBuffer", err, __FILE__, __LINE__);
+ SAL_INFO("sc.opencl", "Created buffer " << mpResClmem << " size " << nr << "*" << sizeof(double) << "=" << (nr*sizeof(double)));
+
+ SAL_INFO("sc.opencl", "Kernel " << mpKernel << " arg " << 0 << ": cl_mem: " << mpResClmem << " (result)");
+ err = clSetKernelArg(mpKernel, 0, sizeof(cl_mem), static_cast<void*>(&mpResClmem));
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clSetKernelArg", err, __FILE__, __LINE__);
+ // The rest of buffers
+ mSyms.Marshal(mpKernel, nr, mpProgram);
+ size_t global_work_size[] = { nr };
+ SAL_INFO("sc.opencl", "Enqueuing kernel " << mpKernel);
+ err = clEnqueueNDRangeKernel(kEnv.mpkCmdQueue, mpKernel, 1, nullptr,
+ global_work_size, nullptr, 0, nullptr, nullptr);
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clEnqueueNDRangeKernel", err, __FILE__, __LINE__);
+ err = clFlush(kEnv.mpkCmdQueue);
+ if (CL_SUCCESS != err)
+ throw OpenCLError("clFlush", err, __FILE__, __LINE__);
+}
+
+// Symbol lookup. If there is no such symbol created, allocate one
+// kernel with argument with unique name and return so.
+// The template argument T must be a subclass of DynamicKernelArgument
+template <typename T>
+const DynamicKernelArgument* SymbolTable::DeclRefArg(const ScCalcConfig& config,
+ const FormulaTreeNodeRef& t,
+ std::shared_ptr<SlidingFunctionBase> pCodeGen, int nResultSize)
+{
+ FormulaToken* ref = t->GetFormulaToken();
+ ArgumentMap::iterator it = mSymbols.find(ref);
+ if (it == mSymbols.end())
+ {
+ // Allocate new symbols
+ std::stringstream ss;
+ ss << "tmp" << mCurId++;
+ DynamicKernelArgumentRef new_arg = std::make_shared<T>(config, ss.str(), t, std::move(pCodeGen), nResultSize);
+ mSymbols[ref] = new_arg;
+ mParams.push_back(new_arg);
+ return new_arg.get();
+ }
+ else
+ {
+ return it->second.get();
+ }
+}
+
+FormulaGroupInterpreterOpenCL::FormulaGroupInterpreterOpenCL() {}
+
+FormulaGroupInterpreterOpenCL::~FormulaGroupInterpreterOpenCL() {}
+
+ScMatrixRef FormulaGroupInterpreterOpenCL::inverseMatrix( const ScMatrix& )
+{
+ return nullptr;
+}
+
+std::shared_ptr<DynamicKernel> DynamicKernel::create( const ScCalcConfig& rConfig, const ScTokenArray& rCode, int nResultSize )
+{
+ // Constructing "AST"
+ FormulaTokenIterator aCode(rCode);
+ std::vector<FormulaToken*> aTokenVector;
+ std::map<FormulaToken*, FormulaTreeNodeRef> aHashMap;
+ FormulaToken* pCur;
+ while ((pCur = const_cast<FormulaToken*>(aCode.Next())) != nullptr)
+ {
+ OpCode eOp = pCur->GetOpCode();
+ if (eOp != ocPush)
+ {
+ FormulaTreeNodeRef pCurNode = std::make_shared<FormulaTreeNode>(pCur);
+ sal_uInt8 nParamCount = pCur->GetParamCount();
+ for (sal_uInt8 i = 0; i < nParamCount; i++)
+ {
+ if( aTokenVector.empty())
+ return nullptr;
+ FormulaToken* pTempFormula = aTokenVector.back();
+ aTokenVector.pop_back();
+ if (pTempFormula->GetOpCode() != ocPush)
+ {
+ if (aHashMap.find(pTempFormula) == aHashMap.end())
+ return nullptr;
+ pCurNode->Children.push_back(aHashMap[pTempFormula]);
+ }
+ else
+ {
+ FormulaTreeNodeRef pChildTreeNode =
+ std::make_shared<FormulaTreeNode>(pTempFormula);
+ pCurNode->Children.push_back(pChildTreeNode);
+ }
+ }
+ std::reverse(pCurNode->Children.begin(), pCurNode->Children.end());
+ aHashMap[pCur] = pCurNode;
+ }
+ aTokenVector.push_back(pCur);
+ }
+
+ FormulaTreeNodeRef Root = std::make_shared<FormulaTreeNode>(nullptr);
+ Root->Children.push_back(aHashMap[aTokenVector.back()]);
+
+ auto pDynamicKernel = std::make_shared<DynamicKernel>(rConfig, Root, nResultSize);
+
+ // OpenCL source code generation and kernel compilation
+ try
+ {
+ pDynamicKernel->CodeGen();
+ pDynamicKernel->CreateKernel();
+ }
+ catch (const UnhandledToken& ut)
+ {
+ SAL_INFO("sc.opencl", "Dynamic formula compiler: UnhandledToken: " << ut.mMessage << " at " << ut.mFile << ":" << ut.mLineNumber);
+ return nullptr;
+ }
+ catch (const InvalidParameterCount& ipc)
+ {
+ SAL_INFO("sc.opencl", "Dynamic formula compiler: InvalidParameterCount " << ipc.mParameterCount
+ << " at " << ipc.mFile << ":" << ipc.mLineNumber);
+ return nullptr;
+ }
+ catch (const OpenCLError& oce)
+ {
+ // I think OpenCLError exceptions are actually exceptional (unexpected), so do use SAL_WARN
+ // here.
+ SAL_WARN("sc.opencl", "Dynamic formula compiler: OpenCLError from " << oce.mFunction << ": " << openclwrapper::errorString(oce.mError) << " at " << oce.mFile << ":" << oce.mLineNumber);
+
+ // OpenCLError used to go to the catch-all below, and not delete pDynamicKernel. Was that
+ // intentional, should we not do it here then either?
+ openclwrapper::kernelFailures++;
+ return nullptr;
+ }
+ catch (const Unhandled& uh)
+ {
+ SAL_INFO("sc.opencl", "Dynamic formula compiler: Unhandled at " << uh.mFile << ":" << uh.mLineNumber);
+
+ // Unhandled used to go to the catch-all below, and not delete pDynamicKernel. Was that
+ // intentional, should we not do it here then either?
+ openclwrapper::kernelFailures++;
+ return nullptr;
+ }
+ catch (...)
+ {
+ // FIXME: Do we really want to catch random exceptions here?
+ SAL_WARN("sc.opencl", "Dynamic formula compiler: unexpected exception");
+ openclwrapper::kernelFailures++;
+ return nullptr;
+ }
+ return pDynamicKernel;
+}
+
+namespace {
+
+class CLInterpreterResult
+{
+ DynamicKernel* mpKernel;
+
+ SCROW mnGroupLength;
+
+ cl_mem mpCLResBuf;
+ double* mpResBuf;
+
+public:
+ CLInterpreterResult() : mpKernel(nullptr), mnGroupLength(0), mpCLResBuf(nullptr), mpResBuf(nullptr) {}
+ CLInterpreterResult( DynamicKernel* pKernel, SCROW nGroupLength ) :
+ mpKernel(pKernel), mnGroupLength(nGroupLength), mpCLResBuf(nullptr), mpResBuf(nullptr) {}
+
+ bool isValid() const { return mpKernel != nullptr; }
+
+ void fetchResultFromKernel()
+ {
+ if (!isValid())
+ return;
+
+ OpenCLZone zone;
+
+ // Map results back
+ mpCLResBuf = mpKernel->GetResultBuffer();
+
+ openclwrapper::KernelEnv kEnv;
+ openclwrapper::setKernelEnv(&kEnv);
+
+ cl_int err;
+ mpResBuf = static_cast<double*>(clEnqueueMapBuffer(kEnv.mpkCmdQueue,
+ mpCLResBuf,
+ CL_TRUE, CL_MAP_READ, 0,
+ mnGroupLength * sizeof(double), 0, nullptr, nullptr,
+ &err));
+
+ if (err != CL_SUCCESS)
+ {
+ SAL_WARN("sc.opencl", "clEnqueueMapBuffer failed:: " << openclwrapper::errorString(err));
+ mpResBuf = nullptr;
+ return;
+ }
+ SAL_INFO("sc.opencl", "Kernel results: cl_mem: " << mpResBuf << " (" << DebugPeekDoubles(mpResBuf, mnGroupLength) << ")");
+ }
+
+ bool pushResultToDocument( ScDocument& rDoc, const ScAddress& rTopPos )
+ {
+ if (!mpResBuf)
+ return false;
+
+ OpenCLZone zone;
+
+ rDoc.SetFormulaResults(rTopPos, mpResBuf, mnGroupLength);
+
+ openclwrapper::KernelEnv kEnv;
+ openclwrapper::setKernelEnv(&kEnv);
+
+ cl_int err;
+ err = clEnqueueUnmapMemObject(kEnv.mpkCmdQueue, mpCLResBuf, mpResBuf, 0, nullptr, nullptr);
+
+ if (err != CL_SUCCESS)
+ {
+ SAL_WARN("sc.opencl", "clEnqueueUnmapMemObject failed: " << openclwrapper::errorString(err));
+ return false;
+ }
+
+ return true;
+ }
+};
+
+class CLInterpreterContext
+{
+ std::shared_ptr<DynamicKernel> mpKernelStore; /// for managed kernel instance.
+ DynamicKernel* mpKernel;
+
+ SCROW mnGroupLength;
+
+public:
+ explicit CLInterpreterContext(SCROW nGroupLength)
+ : mpKernel(nullptr)
+ , mnGroupLength(nGroupLength) {}
+
+ bool isValid() const
+ {
+ return mpKernel != nullptr;
+ }
+
+ void setManagedKernel( std::shared_ptr<DynamicKernel> pKernel )
+ {
+ mpKernelStore = std::move(pKernel);
+ mpKernel = mpKernelStore.get();
+ }
+
+ CLInterpreterResult launchKernel()
+ {
+ if (!isValid())
+ return CLInterpreterResult();
+
+ try
+ {
+ // Run the kernel.
+ mpKernel->Launch(mnGroupLength);
+ }
+ catch (const UnhandledToken& ut)
+ {
+ SAL_INFO("sc.opencl", "Dynamic formula compiler: UnhandledToken: " << ut.mMessage << " at " << ut.mFile << ":" << ut.mLineNumber);
+ openclwrapper::kernelFailures++;
+ return CLInterpreterResult();
+ }
+ catch (const OpenCLError& oce)
+ {
+ SAL_WARN("sc.opencl", "Dynamic formula compiler: OpenCLError from " << oce.mFunction << ": " << openclwrapper::errorString(oce.mError) << " at " << oce.mFile << ":" << oce.mLineNumber);
+ openclwrapper::kernelFailures++;
+ return CLInterpreterResult();
+ }
+ catch (const Unhandled& uh)
+ {
+ SAL_INFO("sc.opencl", "Dynamic formula compiler: Unhandled at " << uh.mFile << ":" << uh.mLineNumber);
+ openclwrapper::kernelFailures++;
+ return CLInterpreterResult();
+ }
+ catch (...)
+ {
+ SAL_WARN("sc.opencl", "Dynamic formula compiler: unexpected exception");
+ openclwrapper::kernelFailures++;
+ return CLInterpreterResult();
+ }
+
+ return CLInterpreterResult(mpKernel, mnGroupLength);
+ }
+};
+
+
+CLInterpreterContext createCLInterpreterContext( const ScCalcConfig& rConfig,
+ const ScFormulaCellGroupRef& xGroup, const ScTokenArray& rCode )
+{
+ CLInterpreterContext aCxt(xGroup->mnLength);
+
+ aCxt.setManagedKernel(DynamicKernel::create(rConfig, rCode, xGroup->mnLength));
+
+ return aCxt;
+}
+
+void genRPNTokens( ScDocument& rDoc, const ScAddress& rTopPos, ScTokenArray& rCode )
+{
+ ScCompiler aComp(rDoc, rTopPos, rCode, rDoc.GetGrammar());
+ // Disable special ordering for jump commands for the OpenCL interpreter.
+ aComp.EnableJumpCommandReorder(false);
+ aComp.CompileTokenArray(); // Regenerate RPN tokens.
+}
+
+bool waitForResults()
+{
+ OpenCLZone zone;
+ openclwrapper::KernelEnv kEnv;
+ openclwrapper::setKernelEnv(&kEnv);
+
+ cl_int err = clFinish(kEnv.mpkCmdQueue);
+ if (err != CL_SUCCESS)
+ SAL_WARN("sc.opencl", "clFinish failed: " << openclwrapper::errorString(err));
+
+ return err == CL_SUCCESS;
+}
+
+}
+
+bool FormulaGroupInterpreterOpenCL::interpret( ScDocument& rDoc,
+ const ScAddress& rTopPos, ScFormulaCellGroupRef& xGroup,
+ ScTokenArray& rCode )
+{
+ MergeCalcConfig(rDoc);
+
+ genRPNTokens(rDoc, rTopPos, rCode);
+
+ if( rCode.GetCodeLen() == 0 )
+ return false;
+
+ CLInterpreterContext aCxt = createCLInterpreterContext(maCalcConfig, xGroup, rCode);
+ if (!aCxt.isValid())
+ return false;
+
+ CLInterpreterResult aRes = aCxt.launchKernel();
+ if (!aRes.isValid())
+ return false;
+
+ if (!waitForResults())
+ return false;
+
+ aRes.fetchResultFromKernel();
+
+ return aRes.pushResultToDocument(rDoc, rTopPos);
+}
+
+} // namespace sc::opencl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/opencl/op_addin.cxx b/sc/source/core/opencl/op_addin.cxx
new file mode 100644
index 000000000..8a42de323
--- /dev/null
+++ b/sc/source/core/opencl/op_addin.cxx
@@ -0,0 +1,236 @@
+/* -*- 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 "op_addin.hxx"
+
+#include <formula/vectortoken.hxx>
+#include <sstream>
+
+using namespace formula;
+
+namespace sc::opencl {
+
+void OpBesselj::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ CHECK_PARAMETER_COUNT( 2, 2 );
+ ss << "\ndouble " << sSymName;
+ ss << "_" << BinFuncName() << "(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double x = 0.0;\n";
+ ss << " double N = 0.0;\n";
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ assert(tmpCur0);
+ if(ocPush == vSubArguments[0]->GetFormulaToken()->GetOpCode())
+ {
+ if(tmpCur0->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurSVR0 =
+ static_cast<const formula::SingleVectorRefToken *>(tmpCur0);
+ ss << " if (gid0 < " << tmpCurSVR0->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " x = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (isnan(x))\n";
+ ss << " x = 0.0;\n";
+ ss << " }\n";
+ }
+ else if(tmpCur0->GetType() == formula::svDouble)
+ {
+ ss << " x = " << tmpCur0->GetDouble() << ";\n";
+ }
+ else
+ {
+ throw Unhandled(__FILE__, __LINE__);
+ }
+ }
+ else
+ {
+ ss << " x = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ }
+
+ FormulaToken *tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ assert(tmpCur1);
+ if(ocPush == vSubArguments[1]->GetFormulaToken()->GetOpCode())
+ {
+ if(tmpCur1->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurSVR1 =
+ static_cast<const formula::SingleVectorRefToken *>(tmpCur1);
+ ss << " if (gid0 < " << tmpCurSVR1->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " N = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (isnan(N))\n";
+ ss << " N = 0.0;\n";
+ ss << " }\n";
+ }
+ else if(tmpCur1->GetType() == formula::svDouble)
+ {
+ ss << " N = " << tmpCur1->GetDouble() << ";\n";
+ }
+ else
+ {
+ throw Unhandled(__FILE__, __LINE__);
+ }
+ }
+ else
+ {
+ ss << " N = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ";\n";
+ }
+ ss << " double f_PI = 3.1415926535897932385;\n";
+ ss << " double f_2_DIV_PI = 2.0 / f_PI;\n";
+ ss << " double f_PI_DIV_2 = f_PI / 2.0;\n";
+ ss << " double f_PI_DIV_4 = f_PI / 4.0;\n";
+ ss << " if( N < 0.0 )\n";
+ ss << " return CreateDoubleError(IllegalArgument);\n";
+ ss << " if (x == 0.0)\n";
+ ss << " return (N == 0.0) ? 1.0 : 0.0;\n";
+ ss << " double fSign = ((int)N % 2 == 1 && x < 0.0) ? -1.0 : 1.0;\n";
+ ss << " double fX = fabs(x);\n";
+ ss << " double fMaxIteration = 9000000.0;\n";
+ ss << " double fEstimateIteration = fX * 1.5 + N;\n";
+ ss << " bool bAsymptoticPossible = pow(fX,0.4) > N;\n";
+ ss << " if (fEstimateIteration > fMaxIteration)\n";
+ ss << " {\n";
+ ss << " if (bAsymptoticPossible)\n";
+ ss << " return fSign * sqrt(f_2_DIV_PI/fX)";
+ ss << "* cos(fX-N*f_PI_DIV_2-f_PI_DIV_4);\n";
+ ss << " else\n";
+ ss << " return CreateDoubleError(NoConvergence);\n";
+ ss << " }\n";
+ ss << " double epsilon = 1.0e-15;\n";
+ ss << " bool bHasfound = false;\n";
+ ss << " double k= 0.0;\n";
+ ss << " double u ;\n";
+ ss << " double m_bar;\n";
+ ss << " double g_bar;\n";
+ ss << " double g_bar_delta_u;\n";
+ ss << " double g = 0.0;\n";
+ ss << " double delta_u = 0.0;\n";
+ ss << " double f_bar = -1.0;\n";
+ ss << " if (N==0)\n";
+ ss << " {\n";
+ ss << " u = 1.0;\n";
+ ss << " g_bar_delta_u = 0.0;\n";
+ ss << " g_bar = - 2.0/fX; \n";
+ ss << " delta_u = g_bar_delta_u / g_bar;\n";
+ ss << " u = u + delta_u ;\n";
+ ss << " g = -1.0 / g_bar; \n";
+ ss << " f_bar = f_bar * g;\n";
+ ss << " k = 2.0;\n";
+ ss << " }\n";
+ ss << " if (N!=0)\n";
+ ss << " {\n";
+ ss << " u=0.0;\n";
+ ss << " for (k =1.0; k<= N-1; k = k + 1.0)\n";
+ ss << " {\n";
+ ss << " m_bar=2.0 * fmod(k-1.0, 2.0) * f_bar;\n";
+ ss << " g_bar_delta_u = - g * delta_u - m_bar * u;\n";
+ ss << " g_bar = m_bar - 2.0*k/fX + g;\n";
+ ss << " delta_u = g_bar_delta_u / g_bar;\n";
+ ss << " u = u + delta_u;\n";
+ ss << " g = -1.0/g_bar;\n";
+ ss << " f_bar=f_bar * g;\n";
+ ss << " }\n";
+ ss << " m_bar=2.0 * fmod(k-1.0, 2.0) * f_bar;\n";
+ ss << " g_bar_delta_u = f_bar - g * delta_u - m_bar * u;\n";
+ ss << " g_bar = m_bar - 2.0*k/fX + g;\n";
+ ss << " delta_u = g_bar_delta_u / g_bar;\n";
+ ss << " u = u + delta_u;\n";
+ ss << " g = -1.0/g_bar;\n";
+ ss << " f_bar = f_bar * g;\n";
+ ss << " k = k + 1.0;\n";
+ ss << " }\n";
+ ss << " do\n";
+ ss << " {\n";
+ ss << " m_bar = 2.0 * fmod(k-1.0, 2.0) * f_bar;\n";
+ ss << " g_bar_delta_u = - g * delta_u - m_bar * u;\n";
+ ss << " g_bar = m_bar - 2.0*k/fX + g;\n";
+ ss << " delta_u = g_bar_delta_u / g_bar;\n";
+ ss << " u = u + delta_u;\n";
+ ss << " g = -pow(g_bar,-1.0);\n";
+ ss << " f_bar = f_bar * g;\n";
+ ss << " bHasfound = (fabs(delta_u)<=fabs(u)*epsilon);\n";
+ ss << " k = k + 1.0;\n";
+ ss << " }\n";
+ ss << " while (!bHasfound && k <= fMaxIteration);\n";
+ ss << " if (bHasfound)\n";
+ ss << " return u * fSign;\n";
+ ss << " else\n";
+ ss << " return CreateDoubleError(NoConvergence);\n";
+ ss << "}";
+}
+void OpGestep::GenSlidingWindowFunction(
+ std::stringstream &ss,const std::string &sSymName,
+ SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " double tmp=0,tmp0 =0,tmp1 = 0;\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss <<"\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken& rSVR =
+ dynamic_cast< const formula::SingleVectorRefToken& >(*pCur);
+ ss << " if (gid0 < " << rSVR.GetArrayLength() << ")\n";
+ ss << " {\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << " {\n";
+ }
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " tmp"<<i<<" = 0;\n";
+ ss << " else\n";
+ ss << " tmp"<<i<<" = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n }\n";
+ }
+ else
+ {
+ ss << "tmp"<<i<<" ="<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ }
+ }
+ ss << " tmp =tmp0 >= tmp1 ? 1 : 0;\n";
+ ss << " return tmp;\n";
+ ss << "}\n";
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/opencl/op_addin.hxx b/sc/source/core/opencl/op_addin.hxx
new file mode 100644
index 000000000..96f89968b
--- /dev/null
+++ b/sc/source/core/opencl/op_addin.hxx
@@ -0,0 +1,33 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include "opbase.hxx"
+
+namespace sc::opencl {
+
+class OpBesselj: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Besselj"; }
+};
+class OpGestep: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Gestep"; }
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/opencl/op_array.cxx b/sc/source/core/opencl/op_array.cxx
new file mode 100644
index 000000000..c745ec8a1
--- /dev/null
+++ b/sc/source/core/opencl/op_array.cxx
@@ -0,0 +1,191 @@
+/* -*- 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 "op_array.hxx"
+
+#include <formula/vectortoken.hxx>
+#include <sstream>
+
+using namespace formula;
+
+namespace sc::opencl {
+
+void OpSumX2MY2::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n {\n";
+ ss <<" int gid0=get_global_id(0);\n";
+ ss << " double tmp =0;\n";
+ GenTmpVariables(ss,vSubArguments);
+ if(vSubArguments[0]->GetFormulaToken()->GetType() ==
+ formula::svDoubleVectorRef)
+ {
+ FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur);
+ size_t nCurWindowSize = pCurDVR->GetArrayLength() <
+ pCurDVR->GetRefRowSize() ? pCurDVR->GetArrayLength():
+ pCurDVR->GetRefRowSize() ;
+ ss << " int i ;\n";
+ ss << " for (i = ";
+ if (!pCurDVR->IsStartFixed() && pCurDVR->IsEndFixed()) {
+ ss << "gid0; i < "<< nCurWindowSize <<"; i++)\n";
+ } else if (pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed()) {
+ ss << "0; i < gid0+"<< nCurWindowSize <<"; i++)\n";
+ } else {
+ ss << "0; i < "<< nCurWindowSize <<"; i++)\n";
+ }
+ ss << " {\n";
+ if(!pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed())
+ {
+ ss << " int doubleIndex =i+gid0;\n";
+ }else
+ {
+ ss << " int doubleIndex =i;\n";
+ }
+
+ CheckSubArgumentIsNan(ss,vSubArguments,0);
+ CheckSubArgumentIsNan(ss,vSubArguments,1);
+ ss << " tmp +=pow(tmp0,2) - pow(tmp1,2);\n";
+ ss <<" }\n";
+ }
+ else
+ {
+ ss << " int singleIndex =gid0;\n";
+ CheckAllSubArgumentIsNan(ss, vSubArguments);
+ ss << " tmp = pow(tmp0,2) - pow(tmp1,2);\n";
+ }
+ ss << "return tmp;\n";
+ ss << "}";
+}
+
+void OpSumX2PY2::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n {\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double tmp =0;\n";
+ GenTmpVariables(ss,vSubArguments);
+ if(vSubArguments[0]->GetFormulaToken()->GetType() ==
+ formula::svDoubleVectorRef)
+ {
+ FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur);
+ size_t nCurWindowSize = pCurDVR->GetArrayLength() <
+ pCurDVR->GetRefRowSize() ? pCurDVR->GetArrayLength():
+ pCurDVR->GetRefRowSize() ;
+ ss << " int i ;\n";
+ ss << " for (i = ";
+ if (!pCurDVR->IsStartFixed() && pCurDVR->IsEndFixed()) {
+ ss << "gid0; i < "<< nCurWindowSize <<"; i++)\n";
+ } else if (pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed()) {
+ ss << "0; i < gid0+"<< nCurWindowSize <<"; i++)\n";
+ } else {
+ ss << "0; i < "<< nCurWindowSize <<"; i++)\n";
+ }
+ ss << " {\n";
+ if(!pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed())
+ {
+ ss << " int doubleIndex =i+gid0;\n";
+ }else
+ {
+ ss << " int doubleIndex =i;\n";
+ }
+
+ CheckSubArgumentIsNan(ss,vSubArguments,0);
+ CheckSubArgumentIsNan(ss,vSubArguments,1);
+ ss << " tmp +=pow(tmp0,2) + pow(tmp1,2);\n";
+ ss <<" }\n";
+ }
+ else
+ {
+ ss << " int singleIndex =gid0;\n";
+ CheckAllSubArgumentIsNan(ss, vSubArguments);
+ ss << " tmp = pow(tmp0,2) + pow(tmp1,2);\n";
+ }
+ ss << " return tmp;\n";
+ ss << "}";
+}
+void OpSumXMY2::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n {\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double tmp =0;\n";
+ GenTmpVariables(ss,vSubArguments);
+ if(vSubArguments[0]->GetFormulaToken()->GetType() ==
+ formula::svDoubleVectorRef)
+ {
+ FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur);
+ size_t nCurWindowSize = pCurDVR->GetArrayLength() <
+ pCurDVR->GetRefRowSize() ? pCurDVR->GetArrayLength():
+ pCurDVR->GetRefRowSize() ;
+ ss << " int i ;\n";
+ ss << " for (i = ";
+ if (!pCurDVR->IsStartFixed() && pCurDVR->IsEndFixed()) {
+ ss << "gid0; i < "<< nCurWindowSize <<"; i++)\n";
+ } else if (pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed()) {
+ ss << "0; i < gid0+"<< nCurWindowSize <<"; i++)\n";
+ } else {
+ ss << "0; i < "<< nCurWindowSize <<"; i++)\n";
+ }
+ ss << " {\n";
+ if(!pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed())
+ {
+ ss << " int doubleIndex =i+gid0;\n";
+ }else
+ {
+ ss << " int doubleIndex =i;\n";
+ }
+
+ CheckSubArgumentIsNan(ss,vSubArguments,0);
+ CheckSubArgumentIsNan(ss,vSubArguments,1);
+ ss << " tmp +=pow((tmp0-tmp1),2);\n";
+ ss <<" }\n";
+ }
+ else
+ {
+ ss << " int singleIndex =gid0;\n";
+ CheckAllSubArgumentIsNan(ss, vSubArguments);
+ ss << " tmp = pow((tmp0-tmp1),2);\n";
+ }
+ ss << " return tmp;\n";
+ ss << "}";
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/opencl/op_array.hxx b/sc/source/core/opencl/op_array.hxx
new file mode 100644
index 000000000..cd0ba8c13
--- /dev/null
+++ b/sc/source/core/opencl/op_array.hxx
@@ -0,0 +1,40 @@
+/* -*- 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/.
+ */
+#pragma once
+
+#include "opbase.hxx"
+
+namespace sc::opencl {
+
+class OpSumX2MY2: public CheckVariables
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "SumX2MY2"; }
+};
+
+class OpSumX2PY2: public CheckVariables
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "SumX2PY2"; }
+};
+
+class OpSumXMY2: public CheckVariables
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "SumXMY2"; }
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/opencl/op_database.cxx b/sc/source/core/opencl/op_database.cxx
new file mode 100644
index 000000000..b3fc72d40
--- /dev/null
+++ b/sc/source/core/opencl/op_database.cxx
@@ -0,0 +1,1642 @@
+/* -*- 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 "op_database.hxx"
+
+#include <formula/vectortoken.hxx>
+#include <sstream>
+
+namespace sc::opencl {
+
+void OpDmax::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n {\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double max = -1000000000000;\n";
+ ss << " double value=0.0;\n";
+ GenTmpVariables(ss,vSubArguments);
+ int dataCol = 0;
+ int dataRow = 0;
+ if(vSubArguments[0]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+
+ formula::FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur);
+ dataCol = pCurDVR->GetArrays().size();
+ dataRow = pCurDVR->GetArrayLength();
+
+ if(vSubArguments[dataCol]->GetFormulaToken()->GetType() !=
+ formula::svSingleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+
+ const formula::SingleVectorRefToken*pTmpDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(vSubArguments[dataCol]->
+ GetFormulaToken());
+ ss << " tmp"<<dataCol<<"=";
+ ss << vSubArguments[dataCol]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(gid0>="<<pTmpDVR1->GetArrayLength()<<" ||isnan(";
+ ss << "tmp"<<dataCol<<"))\n";
+ ss << " tmp"<<dataCol<<"=0;\n";
+
+ int conditionCol = 0;
+ int conditionRow = 0;
+ if(vSubArguments[dataCol + 1]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ {
+ throw Unhandled(__FILE__, __LINE__);
+ }
+ tmpCur = vSubArguments[dataCol + 1]->
+ GetFormulaToken();
+ pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur);
+ conditionCol = pCurDVR->GetArrays().size();
+ conditionRow = pCurDVR->GetArrayLength();
+
+ if(dataCol!=conditionCol)
+ throw Unhandled(__FILE__, __LINE__);
+ if(dataCol > 0 && dataRow > 0)
+ {
+ formula::FormulaToken *tmpCur1 = vSubArguments[0]->GetFormulaToken();
+ formula::FormulaToken *tmpCur2 = vSubArguments[dataCol + 1]->
+ GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR1= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur1);
+ const formula::DoubleVectorRefToken*pCurDVR2= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur2);
+
+ if(pCurDVR1->IsStartFixed() && pCurDVR1->IsEndFixed() &&
+ pCurDVR2->IsStartFixed() && pCurDVR2->IsEndFixed())
+ {
+ ss << " int i,j,p;\n";
+ ss << " bool flag;\n";
+ ss << " for(p = 1;p < " << dataRow << ";++p)\n";
+ ss << " {\n";
+ ss << " i = p;\n";
+ for(int i = 0; i < dataCol; ++i){
+ if(vSubArguments[i]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+ ss << " tmp"<<i<<"=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(isnan(tmp"<<i<<"))\n";
+ ss <<" tmp"<<i<<" = 0;\n";
+ }
+ ss << " flag = false;\n";
+ ss << " for(j = 1; j < " << conditionRow << ";++j)\n";
+ ss << " {\n";
+ ss << " i = j;\n";
+ ss << " if (flag)\n";
+ ss << " break;\n";
+ ss << " else{\n";
+ for(int i = dataCol + 1; i < dataCol + 1 + conditionCol; ++i){
+ if(vSubArguments[i]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+ ss << " tmp"<<i<<"=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(!isnan(tmp"<<i<<")){\n";
+ ss << " if(tmp"<<(i-dataCol-1)<<"!=tmp";
+ ss << i<<"){\n";
+ ss << " continue;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ ss << " flag=true;\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << " if (flag){\n";
+ for(int i = 0; i < dataCol; ++i){
+ ss << " if(tmp"<<dataCol<<"=="<<(i+1)<<"){\n";
+ ss << " value=tmp"<<i<<";\n";
+ ss << " }\n";
+ }
+ ss << " if(max<value)\n";
+ ss << " max=value;";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else
+ ss << "max = -1;\n";
+ }
+ else
+ ss << "max = -1;\n";
+ ss << " return max;\n";
+ ss << "}";
+}
+
+void OpDmin::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n {\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double min = 1000000000000;\n";
+ ss << " double value=0.0;\n";
+ GenTmpVariables(ss,vSubArguments);
+ int dataCol = 0;
+ int dataRow = 0;
+ if(vSubArguments[0]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+
+ formula::FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur);
+ dataCol = pCurDVR->GetArrays().size();
+ dataRow = pCurDVR->GetArrayLength();
+
+ if(vSubArguments[dataCol]->GetFormulaToken()->GetType() !=
+ formula::svSingleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+
+ const formula::SingleVectorRefToken*pTmpDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(vSubArguments[dataCol]->
+ GetFormulaToken());
+ ss << " tmp"<<dataCol<<"=";
+ ss << vSubArguments[dataCol]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(gid0>="<<pTmpDVR1->GetArrayLength()<<" ||isnan(";
+ ss << "tmp"<<dataCol<<"))\n";
+ ss << " tmp"<<dataCol<<"=0;\n";
+
+ int conditionCol = 0;
+ int conditionRow = 0;
+ if(vSubArguments[dataCol + 1]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ {
+ throw Unhandled(__FILE__, __LINE__);
+ }
+ tmpCur = vSubArguments[dataCol + 1]->
+ GetFormulaToken();
+ pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur);
+ conditionCol = pCurDVR->GetArrays().size();
+ conditionRow = pCurDVR->GetArrayLength();
+
+ if(dataCol!=conditionCol)
+ throw Unhandled(__FILE__, __LINE__);
+ if(dataCol > 0 && dataRow > 0)
+ {
+ formula::FormulaToken *tmpCur1 = vSubArguments[0]->GetFormulaToken();
+ formula::FormulaToken *tmpCur2 = vSubArguments[dataCol + 1]->
+ GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR1= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur1);
+ const formula::DoubleVectorRefToken*pCurDVR2= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur2);
+
+ if(pCurDVR1->IsStartFixed() && pCurDVR1->IsEndFixed() &&
+ pCurDVR2->IsStartFixed() && pCurDVR2->IsEndFixed())
+ {
+ ss << " int i,j,p;\n";
+ ss << " bool flag;\n";
+
+ ss << " for(p = 1;p < " << dataRow << ";++p)\n";
+ ss << " {\n";
+ ss << " i = p;\n";
+ for(int i = 0; i < dataCol; ++i){
+ if(vSubArguments[i]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+ ss << " tmp"<<i<<"=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(isnan(tmp"<<i<<"))\n";
+ ss <<" tmp"<<i<<" = 0;\n";
+ }
+ ss << " flag = false;\n";
+ ss << " for(j = 1; j < " << conditionRow << ";++j)\n";
+ ss << " {\n";
+ ss << " i = j;\n";
+ ss << " if (flag)\n";
+ ss << " break;\n";
+ ss << " else{\n";
+ for(int i = dataCol + 1; i < dataCol + 1 + conditionCol; ++i){
+ if(vSubArguments[i]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+ ss << " tmp"<<i<<"=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(!isnan(tmp"<<i<<")){\n";
+ ss << " if(tmp"<<(i-dataCol-1)<<"!=tmp";
+ ss << i<<"){\n";
+ ss << " continue;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ ss << " flag=true;\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << " if (flag){\n";
+ for(int i = 0; i < dataCol; ++i){
+ ss << " if(tmp"<<dataCol<<"=="<<(i+1)<<"){\n";
+ ss << " value=tmp"<<i<<";\n";
+ ss << " }\n";
+ }
+ ss << " if(min>value)\n";
+ ss << " min=value;";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else
+ ss << "min = -1;\n";
+ }
+ else
+ ss << "min = -1;\n";
+ ss << " return min;\n";
+ ss << "}";
+}
+
+void OpDproduct::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n {\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double product = 1;\n";
+ ss << " double value =0;\n";
+ GenTmpVariables(ss,vSubArguments);
+ int dataCol = 0;
+ int dataRow = 0;
+ if(vSubArguments[0]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+
+ formula::FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur);
+ dataCol = pCurDVR->GetArrays().size();
+ dataRow = pCurDVR->GetArrayLength();
+
+ if(vSubArguments[dataCol]->GetFormulaToken()->GetType() !=
+ formula::svSingleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+
+ const formula::SingleVectorRefToken*pTmpDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(vSubArguments[dataCol]->
+ GetFormulaToken());
+ ss << " tmp"<<dataCol<<"=";
+ ss << vSubArguments[dataCol]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(gid0>="<<pTmpDVR1->GetArrayLength()<<" ||isnan(";
+ ss << "tmp"<<dataCol<<"))\n";
+ ss << " tmp"<<dataCol<<"=0;\n";
+
+ int conditionCol = 0;
+ int conditionRow = 0;
+ if(vSubArguments[dataCol + 1]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ {
+ throw Unhandled(__FILE__, __LINE__);
+ }
+ tmpCur = vSubArguments[dataCol + 1]->
+ GetFormulaToken();
+ pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur);
+ conditionCol = pCurDVR->GetArrays().size();
+ conditionRow = pCurDVR->GetArrayLength();
+
+ if(dataCol!=conditionCol)
+ throw Unhandled(__FILE__, __LINE__);
+ if(dataCol > 0 && dataRow > 0)
+ {
+ formula::FormulaToken *tmpCur1 = vSubArguments[0]->GetFormulaToken();
+ formula::FormulaToken *tmpCur2 = vSubArguments[dataCol + 1]->
+ GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR1= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur1);
+ const formula::DoubleVectorRefToken*pCurDVR2= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur2);
+
+ if(pCurDVR1->IsStartFixed() && pCurDVR1->IsEndFixed() &&
+ pCurDVR2->IsStartFixed() && pCurDVR2->IsEndFixed())
+ {
+ ss << " int i,j,p;\n";
+ ss << " bool flag;\n";
+ ss << " for(p = 1;p < " << dataRow << ";++p)\n";
+ ss << " {\n";
+ ss << " i = p;\n";
+ for(int i = 0; i < dataCol; ++i){
+ if(vSubArguments[i]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+ ss << " tmp"<<i<<"=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(isnan(tmp"<<i<<"))\n";
+ ss <<" tmp"<<i<<" = 0;\n";
+ }
+ ss << " flag = false;\n";
+ ss << " for(j = 1; j < " << conditionRow << ";++j)\n";
+ ss << " {\n";
+ ss << " i = j;\n";
+ ss << " if (flag)\n";
+ ss << " break;\n";
+ ss << " else{\n";
+ for(int i = dataCol + 1; i < dataCol + 1 + conditionCol; ++i){
+ if(vSubArguments[i]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+ ss << " tmp"<<i<<"=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(!isnan(tmp"<<i<<")){\n";
+ ss << " if(tmp"<<(i-dataCol-1)<<"!=tmp";
+ ss << i<<"){\n";
+ ss << " continue;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ ss << " flag=true;\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << " if (flag){\n";
+ for(int i = 0; i < dataCol; ++i){
+ ss << " if(tmp"<<dataCol<<"=="<<(i+1)<<"){\n";
+ ss << " value=tmp"<<i<<";\n";
+ ss << " }\n";
+ }
+ ss << " product*=value;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else
+ ss << "product = -1;\n";
+ }
+ else
+ ss << "product = -1;\n";
+ ss << " return product;\n";
+ ss << "}";
+}
+
+void OpDaverage::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n {\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double sum = 0;\n";
+ ss << " int count = 0;\n";
+ ss << " double value =0;\n";
+ GenTmpVariables(ss,vSubArguments);
+ int dataCol = 0;
+ int dataRow = 0;
+ if(vSubArguments[0]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+
+ formula::FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur);
+ dataCol = pCurDVR->GetArrays().size();
+ dataRow = pCurDVR->GetArrayLength();
+
+ if(vSubArguments[dataCol]->GetFormulaToken()->GetType() !=
+ formula::svSingleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+
+ const formula::SingleVectorRefToken*pTmpDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(vSubArguments[dataCol]->
+ GetFormulaToken());
+ ss << " tmp"<<dataCol<<"=";
+ ss << vSubArguments[dataCol]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(gid0>="<<pTmpDVR1->GetArrayLength()<<" ||isnan(";
+ ss << "tmp"<<dataCol<<"))\n";
+ ss << " tmp"<<dataCol<<"=0;\n";
+
+ int conditionCol = 0;
+ int conditionRow = 0;
+ if(vSubArguments[dataCol + 1]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ {
+ throw Unhandled(__FILE__, __LINE__);
+ }
+ tmpCur = vSubArguments[dataCol + 1]->
+ GetFormulaToken();
+ pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur);
+ conditionCol = pCurDVR->GetArrays().size();
+ conditionRow = pCurDVR->GetArrayLength();
+
+ if(dataCol!=conditionCol)
+ throw Unhandled(__FILE__, __LINE__);
+ if(dataCol > 0 && dataRow > 0)
+ {
+ formula::FormulaToken *tmpCur1 = vSubArguments[0]->GetFormulaToken();
+ formula::FormulaToken *tmpCur2 = vSubArguments[dataCol + 1]->
+ GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR1= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur1);
+ const formula::DoubleVectorRefToken*pCurDVR2= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur2);
+
+ if(pCurDVR1->IsStartFixed() && pCurDVR1->IsEndFixed() &&
+ pCurDVR2->IsStartFixed() && pCurDVR2->IsEndFixed())
+ {
+ ss << " int i,j,p;\n";
+ ss << " bool flag;\n";
+
+ ss << " for(p = 1;p < " << dataRow << ";++p)\n";
+ ss << " {\n";
+ ss << " i = p;\n";
+ for(int i = 0; i < dataCol; ++i){
+ if(vSubArguments[i]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+ ss << " tmp"<<i<<"=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(isnan(tmp"<<i<<"))\n";
+ ss <<" tmp"<<i<<" = 0;\n";
+ }
+ ss << " flag = false;\n";
+ ss << " for(j = 1; j < " << conditionRow << ";++j)\n";
+ ss << " {\n";
+ ss << " i = j;\n";
+ ss << " if (flag)\n";
+ ss << " break;\n";
+ ss << " else{\n";
+ for(int i = dataCol + 1; i < dataCol + 1 + conditionCol; ++i){
+ if(vSubArguments[i]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+ ss << " tmp"<<i<<"=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(!isnan(tmp"<<i<<")){\n";
+ ss << " if(tmp"<<(i-dataCol-1)<<"!=tmp";
+ ss << i<<"){\n";
+ ss << " continue;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ ss << " flag=true;\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << " if (flag){\n";
+ ss << " count++;\n";
+ for(int i = 0; i < dataCol; ++i){
+ ss << " if(tmp"<<dataCol<<"=="<<(i+1)<<"){\n";
+ ss << " value=tmp"<<i<<";\n";
+ ss << " }\n";
+ }
+ ss << " sum+=value;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else
+ ss << "sum = -1;\n";
+ }
+ else
+ ss << "sum = -1;\n";
+ ss << " if(count==0)\n";
+ ss << " return 0;\n";
+ ss << " return sum/count;\n";
+ ss << "}";
+}
+
+void OpDstdev::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n {\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double var = 0;\n";
+ ss << " double mean = 0;\n";
+ ss << " double value =0;\n";
+ ss << " int count = 0;\n";
+ GenTmpVariables(ss,vSubArguments);
+ int dataCol = 0;
+ int dataRow = 0;
+ if(vSubArguments[0]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+
+ formula::FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur);
+ dataCol = pCurDVR->GetArrays().size();
+ dataRow = pCurDVR->GetArrayLength();
+
+ if(vSubArguments[dataCol]->GetFormulaToken()->GetType() !=
+ formula::svSingleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+
+ const formula::SingleVectorRefToken*pTmpDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(vSubArguments[dataCol]->
+ GetFormulaToken());
+ ss << " tmp"<<dataCol<<"=";
+ ss << vSubArguments[dataCol]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(gid0>="<<pTmpDVR1->GetArrayLength()<<" ||isnan(";
+ ss << "tmp"<<dataCol<<"))\n";
+ ss << " tmp"<<dataCol<<"=0;\n";
+
+ int conditionCol = 0;
+ int conditionRow = 0;
+ if(vSubArguments[dataCol + 1]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ {
+ throw Unhandled(__FILE__, __LINE__);
+ }
+ tmpCur = vSubArguments[dataCol + 1]->
+ GetFormulaToken();
+ pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur);
+ conditionCol = pCurDVR->GetArrays().size();
+ conditionRow = pCurDVR->GetArrayLength();
+
+ if(dataCol!=conditionCol)
+ throw Unhandled(__FILE__, __LINE__);
+ if(dataCol > 0 && dataRow > 0)
+ {
+ formula::FormulaToken *tmpCur1 = vSubArguments[0]->GetFormulaToken();
+ formula::FormulaToken *tmpCur2 = vSubArguments[dataCol + 1]->
+ GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR1= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur1);
+ const formula::DoubleVectorRefToken*pCurDVR2= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur2);
+
+ if(pCurDVR1->IsStartFixed() && pCurDVR1->IsEndFixed() &&
+ pCurDVR2->IsStartFixed() && pCurDVR2->IsEndFixed())
+ {
+ ss << " int i,j,p;\n";
+ ss << " bool flag;\n";
+
+ ss << " for(p = 1;p < " << dataRow << ";++p)\n";
+ ss << " {\n";
+ ss << " i = p;\n";
+ for(int i = 0; i < dataCol; ++i){
+ if(vSubArguments[i]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+ ss << " tmp"<<i<<"=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(isnan(tmp"<<i<<"))\n";
+ ss <<" tmp"<<i<<" = 0;\n";
+ }
+ ss << " flag = false;\n";
+ ss << " for(j = 1; j < " << conditionRow << ";++j)\n";
+ ss << " {\n";
+ ss << " i = j;\n";
+ ss << " if (flag)\n";
+ ss << " break;\n";
+ ss << " else{\n";
+ for(int i = dataCol + 1; i < dataCol + 1 + conditionCol; ++i){
+ if(vSubArguments[i]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+ ss << " tmp"<<i<<"=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(!isnan(tmp"<<i<<")){\n";
+ ss << " if(tmp"<<(i-dataCol-1)<<"!=tmp";
+ ss << i<<"){\n";
+ ss << " continue;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ ss << " flag=true;\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << " if (flag){\n";
+ ss << " count++;\n";
+ for(int i = 0; i < dataCol; ++i){
+ ss << " if(tmp"<<dataCol<<"=="<<(i+1)<<"){\n";
+ ss << " value=tmp"<<i<<";\n";
+ ss << " }\n";
+ }
+ ss << " mean+=value;\n";
+ ss << " }\n";
+ ss << " }\n";
+
+ ss << " if(count<=1)\n";
+ ss << " return 0;\n";
+
+ ss << " mean/=count;\n";
+
+ ss << " for(p = 1;p < " << dataRow << ";++p)\n";
+ ss << " {\n";
+ ss << " i = p;\n";
+ for(int i = 0; i < dataCol; ++i){
+ if(vSubArguments[i]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+ ss << " tmp"<<i<<"=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(isnan(tmp"<<i<<"))\n";
+ ss <<" tmp"<<i<<" = 0;\n";
+ }
+ ss << " flag = false;\n";
+ ss << " for(j = 1; j < " << conditionRow << ";++j)\n";
+ ss << " {\n";
+ ss << " i = j;\n";
+ ss << " if (flag)\n";
+ ss << " break;\n";
+ ss << " else{\n";
+ for(int i = dataCol + 1; i < dataCol + 1 + conditionCol; ++i){
+ if(vSubArguments[i]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+ ss << " tmp"<<i<<"=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(!isnan(tmp"<<i<<")){\n";
+ ss << " if(tmp"<<(i-dataCol-1)<<"!=tmp";
+ ss << i<<"){\n";
+ ss << " continue;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ ss << " flag=true;\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << " if (flag){\n";
+ for(int i = 0; i < dataCol; ++i){
+ ss << " if(tmp"<<dataCol<<"=="<<(i+1)<<"){\n";
+ ss << " value=tmp"<<i<<";\n";
+ ss << " }\n";
+ }
+ ss << " var+=pow(mean-value,2);\n";
+ ss << " }\n";
+ ss << " }\n";
+
+ ss << " var = sqrt( var/(count-1) );\n";
+ }
+ else
+ ss << "var = -1;\n";
+ }
+ else
+ ss << "var = -1;\n";
+ ss << " return var;\n";
+ ss << "}";
+}
+
+void OpDstdevp::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n {\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double var = 0;\n";
+ ss << " double mean = 0;\n";
+ ss << " double value =0;\n";
+ ss << " int count = 0;\n";
+ GenTmpVariables(ss,vSubArguments);
+ int dataCol = 0;
+ int dataRow = 0;
+ if(vSubArguments[0]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+
+ formula::FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur);
+ dataCol = pCurDVR->GetArrays().size();
+ dataRow = pCurDVR->GetArrayLength();
+
+ if(vSubArguments[dataCol]->GetFormulaToken()->GetType() !=
+ formula::svSingleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+
+ const formula::SingleVectorRefToken*pTmpDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(vSubArguments[dataCol]->
+ GetFormulaToken());
+ ss << " tmp"<<dataCol<<"=";
+ ss << vSubArguments[dataCol]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(gid0>="<<pTmpDVR1->GetArrayLength()<<" ||isnan(";
+ ss << "tmp"<<dataCol<<"))\n";
+ ss << " tmp"<<dataCol<<"=0;\n";
+
+ int conditionCol = 0;
+ int conditionRow = 0;
+ if(vSubArguments[dataCol + 1]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ {
+ throw Unhandled(__FILE__, __LINE__);
+ }
+ tmpCur = vSubArguments[dataCol + 1]->
+ GetFormulaToken();
+ pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur);
+ conditionCol = pCurDVR->GetArrays().size();
+ conditionRow = pCurDVR->GetArrayLength();
+
+ if(dataCol!=conditionCol)
+ throw Unhandled(__FILE__, __LINE__);
+ if(dataCol > 0 && dataRow > 0)
+ {
+ formula::FormulaToken *tmpCur1 = vSubArguments[0]->GetFormulaToken();
+ formula::FormulaToken *tmpCur2 = vSubArguments[dataCol + 1]->
+ GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR1= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur1);
+ const formula::DoubleVectorRefToken*pCurDVR2= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur2);
+
+ if(pCurDVR1->IsStartFixed() && pCurDVR1->IsEndFixed() &&
+ pCurDVR2->IsStartFixed() && pCurDVR2->IsEndFixed())
+ {
+ ss << " int i,j,p;\n";
+ ss << " bool flag;\n";
+
+ ss << " for(p = 1;p < " << dataRow << ";++p)\n";
+ ss << " {\n";
+ ss << " i = p;\n";
+ for(int i = 0; i < dataCol; ++i){
+ if(vSubArguments[i]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+ ss << " tmp"<<i<<"=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(isnan(tmp"<<i<<"))\n";
+ ss <<" tmp"<<i<<" = 0;\n";
+ }
+ ss << " flag = false;\n";
+ ss << " for(j = 1; j < " << conditionRow << ";++j)\n";
+ ss << " {\n";
+ ss << " i = j;\n";
+ ss << " if (flag)\n";
+ ss << " break;\n";
+ ss << " else{\n";
+ for(int i = dataCol + 1; i < dataCol + 1 + conditionCol; ++i){
+ if(vSubArguments[i]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+ ss << " tmp"<<i<<"=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(!isnan(tmp"<<i<<")){\n";
+ ss << " if(tmp"<<(i-dataCol-1)<<"!=tmp";
+ ss << i<<"){\n";
+ ss << " continue;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ ss << " flag=true;\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << " if (flag){\n";
+ ss << " count++;\n";
+ for(int i = 0; i < dataCol; ++i){
+ ss << " if(tmp"<<dataCol<<"=="<<(i+1)<<"){\n";
+ ss << " value=tmp"<<i<<";\n";
+ ss << " }\n";
+ }
+ ss << " mean+=value;\n";
+ ss << " }\n";
+ ss << " }\n";
+
+ ss << " if(count<=1)\n";
+ ss << " return 0;\n";
+
+ ss << " mean/=count;\n";
+
+ ss << " for(p = 1;p < " << dataRow << ";++p)\n";
+ ss << " {\n";
+ ss << " i = p;\n";
+ for(int i = 0; i < dataCol; ++i){
+ if(vSubArguments[i]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+ ss << " tmp"<<i<<"=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(isnan(tmp"<<i<<"))\n";
+ ss <<" tmp"<<i<<" = 0;\n";
+ }
+ ss << " flag = false;\n";
+ ss << " for(j = 1; j < " << conditionRow << ";++j)\n";
+ ss << " {\n";
+ ss << " i = j;\n";
+ ss << " if (flag)\n";
+ ss << " break;\n";
+ ss << " else{\n";
+ for(int i = dataCol + 1; i < dataCol + 1 + conditionCol; ++i){
+ if(vSubArguments[i]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+ ss << " tmp"<<i<<"=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(!isnan(tmp"<<i<<")){\n";
+ ss << " if(tmp"<<(i-dataCol-1)<<"!=tmp";
+ ss << i<<"){\n";
+ ss << " continue;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ ss << " flag=true;\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << " if (flag){\n";
+ for(int i = 0; i < dataCol; ++i){
+ ss << " if(tmp"<<dataCol<<"=="<<(i+1)<<"){\n";
+ ss << " value=tmp"<<i<<";\n";
+ ss << " }\n";
+ }
+ ss << " var+=pow(mean-value,2);\n";
+ ss << " }\n";
+ ss << " }\n";
+
+ ss << " var = sqrt( var/count );\n";
+ }
+ else
+ ss << "var = -1;\n";
+ }
+ else
+ ss << "var = -1;\n";
+ ss << " return var;\n";
+ ss << "}";
+}
+
+void OpDsum::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n {\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double sum = 0;\n";
+ ss << " double value =0;\n";
+ GenTmpVariables(ss,vSubArguments);
+ int dataCol = 0;
+ int dataRow = 0;
+ if(vSubArguments[0]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+
+ formula::FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur);
+ dataCol = pCurDVR->GetArrays().size();
+ dataRow = pCurDVR->GetArrayLength();
+
+ if(vSubArguments[dataCol]->GetFormulaToken()->GetType() !=
+ formula::svSingleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+
+ const formula::SingleVectorRefToken*pTmpDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(vSubArguments[dataCol]->
+ GetFormulaToken());
+ ss << " tmp"<<dataCol<<"=";
+ ss << vSubArguments[dataCol]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(gid0>="<<pTmpDVR1->GetArrayLength()<<" ||isnan(";
+ ss << "tmp"<<dataCol<<"))\n";
+ ss << " tmp"<<dataCol<<"=0;\n";
+
+ int conditionCol = 0;
+ int conditionRow = 0;
+ if(vSubArguments[dataCol + 1]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ {
+ throw Unhandled(__FILE__, __LINE__);
+ }
+ tmpCur = vSubArguments[dataCol + 1]->
+ GetFormulaToken();
+ pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur);
+ conditionCol = pCurDVR->GetArrays().size();
+ conditionRow = pCurDVR->GetArrayLength();
+
+ if(dataCol!=conditionCol)
+ throw Unhandled(__FILE__, __LINE__);
+ if(dataCol > 0 && dataRow > 0)
+ {
+ formula::FormulaToken *tmpCur1 = vSubArguments[0]->GetFormulaToken();
+ formula::FormulaToken *tmpCur2 = vSubArguments[dataCol + 1]->
+ GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR1= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur1);
+ const formula::DoubleVectorRefToken*pCurDVR2= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur2);
+
+ if(pCurDVR1->IsStartFixed() && pCurDVR1->IsEndFixed() &&
+ pCurDVR2->IsStartFixed() && pCurDVR2->IsEndFixed())
+ {
+ ss << " int i,j,p;\n";
+ ss << " bool flag;\n";
+
+ ss << " for(p = 1;p < " << dataRow << ";++p)\n";
+ ss << " {\n";
+ ss << " i = p;\n";
+ for(int i = 0; i < dataCol; ++i){
+ if(vSubArguments[i]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+ ss << " tmp"<<i<<"=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(isnan(tmp"<<i<<"))\n";
+ ss <<" tmp"<<i<<" = 0;\n";
+ }
+ ss << " flag = false;\n";
+ ss << " for(j = 1; j < " << conditionRow << ";++j)\n";
+ ss << " {\n";
+ ss << " i = j;\n";
+ ss << " if (flag)\n";
+ ss << " break;\n";
+ ss << " else{\n";
+ for(int i = dataCol + 1; i < dataCol + 1 + conditionCol; ++i){
+ if(vSubArguments[i]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+ ss << " tmp"<<i<<"=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(!isnan(tmp"<<i<<")){\n";
+ ss << " if(tmp"<<(i-dataCol-1)<<"!=tmp";
+ ss << i<<"){\n";
+ ss << " continue;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ ss << " flag=true;\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << " if (flag){\n";
+ for(int i = 0; i < dataCol; ++i){
+ ss << " if(tmp"<<dataCol<<"=="<<(i+1)<<"){\n";
+ ss << " value=tmp"<<i<<";\n";
+ ss << " }\n";
+ }
+ ss << " sum+=value;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else
+ ss << "sum = -1;\n";
+ }
+ else
+ ss << "sum = -1;\n";
+ ss << " return sum;\n";
+ ss << "}";
+}
+
+void OpDvar::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n {\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double var = 0;\n";
+ ss << " double mean = 0;\n";
+ ss << " double value =0;\n";
+ ss << " int count = 0;\n";
+ GenTmpVariables(ss,vSubArguments);
+ int dataCol = 0;
+ int dataRow = 0;
+ if(vSubArguments[0]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+
+ formula::FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur);
+ dataCol = pCurDVR->GetArrays().size();
+ dataRow = pCurDVR->GetArrayLength();
+
+ if(vSubArguments[dataCol]->GetFormulaToken()->GetType() !=
+ formula::svSingleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+
+ const formula::SingleVectorRefToken*pTmpDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(vSubArguments[dataCol]->
+ GetFormulaToken());
+ ss << " tmp"<<dataCol<<"=";
+ ss << vSubArguments[dataCol]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(gid0>="<<pTmpDVR1->GetArrayLength()<<" ||isnan(";
+ ss << "tmp"<<dataCol<<"))\n";
+ ss << " tmp"<<dataCol<<"=0;\n";
+
+ int conditionCol = 0;
+ int conditionRow = 0;
+ if(vSubArguments[dataCol + 1]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ {
+ throw Unhandled(__FILE__, __LINE__);
+ }
+ tmpCur = vSubArguments[dataCol + 1]->
+ GetFormulaToken();
+ pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur);
+ conditionCol = pCurDVR->GetArrays().size();
+ conditionRow = pCurDVR->GetArrayLength();
+
+ if(dataCol!=conditionCol)
+ throw Unhandled(__FILE__, __LINE__);
+ if(dataCol > 0 && dataRow > 0)
+ {
+ formula::FormulaToken *tmpCur1 = vSubArguments[0]->GetFormulaToken();
+ formula::FormulaToken *tmpCur2 = vSubArguments[dataCol + 1]->
+ GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR1= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur1);
+ const formula::DoubleVectorRefToken*pCurDVR2= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur2);
+
+ if(pCurDVR1->IsStartFixed() && pCurDVR1->IsEndFixed() &&
+ pCurDVR2->IsStartFixed() && pCurDVR2->IsEndFixed())
+ {
+ ss << " int i,j,p;\n";
+ ss << " bool flag;\n";
+
+ ss << " for(p = 1;p < " << dataRow << ";++p)\n";
+ ss << " {\n";
+ ss << " i = p;\n";
+ for(int i = 0; i < dataCol; ++i){
+ if(vSubArguments[i]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+ ss << " tmp"<<i<<"=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(isnan(tmp"<<i<<"))\n";
+ ss <<" tmp"<<i<<" = 0;\n";
+ }
+ ss << " flag = false;\n";
+ ss << " for(j = 1; j < " << conditionRow << ";++j)\n";
+ ss << " {\n";
+ ss << " i = j;\n";
+ ss << " if (flag)\n";
+ ss << " break;\n";
+ ss << " else{\n";
+ for(int i = dataCol + 1; i < dataCol + 1 + conditionCol; ++i){
+ if(vSubArguments[i]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+ ss << " tmp"<<i<<"=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(!isnan(tmp"<<i<<")){\n";
+ ss << " if(tmp"<<(i-dataCol-1)<<"!=tmp";
+ ss << i<<"){\n";
+ ss << " continue;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ ss << " flag=true;\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << " if (flag){\n";
+ ss << " count++;\n";
+ for(int i = 0; i < dataCol; ++i){
+ ss << " if(tmp"<<dataCol<<"=="<<(i+1)<<"){\n";
+ ss << " value=tmp"<<i<<";\n";
+ ss << " }\n";
+ }
+ ss << " mean+=value;\n";
+ ss << " }\n";
+ ss << " }\n";
+
+ ss << " if(count<=1)\n";
+ ss << " return 0;\n";
+
+ ss << " mean/=count;\n";
+
+ ss << " for(p = 1;p < " << dataRow << ";++p)\n";
+ ss << " {\n";
+ ss << " i = p;\n";
+ for(int i = 0; i < dataCol; ++i){
+ if(vSubArguments[i]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+ ss << " tmp"<<i<<"=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(isnan(tmp"<<i<<"))\n";
+ ss <<" tmp"<<i<<" = 0;\n";
+ }
+ ss << " flag = false;\n";
+ ss << " for(j = 1; j < " << conditionRow << ";++j)\n";
+ ss << " {\n";
+ ss << " i = j;\n";
+ ss << " if (flag)\n";
+ ss << " break;\n";
+ ss << " else{\n";
+ for(int i = dataCol + 1; i < dataCol + 1 + conditionCol; ++i){
+ if(vSubArguments[i]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+ ss << " tmp"<<i<<"=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(!isnan(tmp"<<i<<")){\n";
+ ss << " if(tmp"<<(i-dataCol-1)<<"!=tmp";
+ ss << i<<"){\n";
+ ss << " continue;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ ss << " flag=true;\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << " if (flag){\n";
+ for(int i = 0; i < dataCol; ++i){
+ ss << " if(tmp"<<dataCol<<"=="<<(i+1)<<"){\n";
+ ss << " value=tmp"<<i<<";\n";
+ ss << " }\n";
+ }
+ ss << " var+=pow(mean-value,2);\n";
+ ss << " }\n";
+ ss << " }\n";
+
+ ss << " var = var/(count-1);\n";
+ }
+ else
+ ss << "var = -1;\n";
+ }
+ else
+ ss << "var = -1;\n";
+ ss << " return var;\n";
+ ss << "}";
+}
+
+void OpDvarp::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n {\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double var = 0;\n";
+ ss << " double mean = 0;\n";
+ ss << " double value =0;\n";
+ ss << " int count = 0;\n";
+ GenTmpVariables(ss,vSubArguments);
+ int dataCol = 0;
+ int dataRow = 0;
+ if(vSubArguments[0]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+
+ formula::FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur);
+ dataCol = pCurDVR->GetArrays().size();
+ dataRow = pCurDVR->GetArrayLength();
+
+ if(vSubArguments[dataCol]->GetFormulaToken()->GetType() !=
+ formula::svSingleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+
+ const formula::SingleVectorRefToken*pTmpDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(vSubArguments[dataCol]->
+ GetFormulaToken());
+ ss << " tmp"<<dataCol<<"=";
+ ss << vSubArguments[dataCol]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(gid0>="<<pTmpDVR1->GetArrayLength()<<" ||isnan(";
+ ss << "tmp"<<dataCol<<"))\n";
+ ss << " tmp"<<dataCol<<"=0;\n";
+
+ int conditionCol = 0;
+ int conditionRow = 0;
+ if(vSubArguments[dataCol + 1]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ {
+ throw Unhandled(__FILE__, __LINE__);
+ }
+ tmpCur = vSubArguments[dataCol + 1]->
+ GetFormulaToken();
+ pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur);
+ conditionCol = pCurDVR->GetArrays().size();
+ conditionRow = pCurDVR->GetArrayLength();
+
+ if(dataCol!=conditionCol)
+ throw Unhandled(__FILE__, __LINE__);
+ if(dataCol > 0 && dataRow > 0)
+ {
+ formula::FormulaToken *tmpCur1 = vSubArguments[0]->GetFormulaToken();
+ formula::FormulaToken *tmpCur2 = vSubArguments[dataCol + 1]->
+ GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR1= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur1);
+ const formula::DoubleVectorRefToken*pCurDVR2= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur2);
+
+ if(pCurDVR1->IsStartFixed() && pCurDVR1->IsEndFixed() &&
+ pCurDVR2->IsStartFixed() && pCurDVR2->IsEndFixed())
+ {
+ ss << " int i,j,p;\n";
+ ss << " bool flag;\n";
+
+ ss << " for(p = 1;p < " << dataRow << ";++p)\n";
+ ss << " {\n";
+ ss << " i = p;\n";
+ for(int i = 0; i < dataCol; ++i){
+ if(vSubArguments[i]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+ ss << " tmp"<<i<<"=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(isnan(tmp"<<i<<"))\n";
+ ss <<" tmp"<<i<<" = 0;\n";
+ }
+ ss << " flag = false;\n";
+ ss << " for(j = 1; j < " << conditionRow << ";++j)\n";
+ ss << " {\n";
+ ss << " i = j;\n";
+ ss << " if (flag)\n";
+ ss << " break;\n";
+ ss << " else{\n";
+ for(int i = dataCol + 1; i < dataCol + 1 + conditionCol; ++i){
+ if(vSubArguments[i]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+ ss << " tmp"<<i<<"=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(!isnan(tmp"<<i<<")){\n";
+ ss << " if(tmp"<<(i-dataCol-1)<<"!=tmp";
+ ss << i<<"){\n";
+ ss << " continue;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ ss << " flag=true;\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << " if (flag){\n";
+ ss << " count++;\n";
+ for(int i = 0; i < dataCol; ++i){
+ ss << " if(tmp"<<dataCol<<"=="<<(i+1)<<"){\n";
+ ss << " value=tmp"<<i<<";\n";
+ ss << " }\n";
+ }
+ ss << " mean+=value;\n";
+ ss << " }\n";
+ ss << " }\n";
+
+ ss << " if(count<=0)\n";
+ ss << " return 0;\n";
+
+ ss << " mean/=count;\n";
+
+ ss << " for(p = 1;p < " << dataRow << ";++p)\n";
+ ss << " {\n";
+ ss << " i = p;\n";
+ for(int i = 0; i < dataCol; ++i){
+ if(vSubArguments[i]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+ ss << " tmp"<<i<<"=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(isnan(tmp"<<i<<"))\n";
+ ss <<" tmp"<<i<<" = 0;\n";
+ }
+ ss << " flag = false;\n";
+ ss << " for(j = 1; j < " << conditionRow << ";++j)\n";
+ ss << " {\n";
+ ss << " i = j;\n";
+ ss << " if (flag)\n";
+ ss << " break;\n";
+ ss << " else{\n";
+ for(int i = dataCol + 1; i < dataCol + 1 + conditionCol; ++i){
+ if(vSubArguments[i]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+ ss << " tmp"<<i<<"=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(!isnan(tmp"<<i<<")){\n";
+ ss << " if(tmp"<<(i-dataCol-1)<<"!=tmp";
+ ss << i<<"){\n";
+ ss << " continue;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ ss << " flag=true;\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << " if (flag){\n";
+ for(int i = 0; i < dataCol; ++i){
+ ss << " if(tmp"<<dataCol<<"=="<<(i+1)<<"){\n";
+ ss << " value=tmp"<<i<<";\n";
+ ss << " }\n";
+ }
+ ss << " var+=pow(mean-value,2);\n";
+ ss << " }\n";
+ ss << " }\n";
+
+ ss << " var = var/count;\n";
+ }
+ else
+ ss << "var = -1;\n";
+ }
+ else
+ ss << "var = -1;\n";
+ ss << " return var;\n";
+ ss << "}";
+}
+
+void OpDcount::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n {\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double value=0;\n";
+ ss << " int count = 0;\n";
+ GenTmpVariables(ss,vSubArguments);
+ int dataCol = 0;
+ int dataRow = 0;
+ if(vSubArguments[0]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+
+ formula::FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur);
+ dataCol = pCurDVR->GetArrays().size();
+ dataRow = pCurDVR->GetArrayLength();
+
+ if(vSubArguments[dataCol]->GetFormulaToken()->GetType() !=
+ formula::svSingleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+
+ const formula::SingleVectorRefToken*pTmpDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(vSubArguments[dataCol]
+ ->GetFormulaToken());
+ ss << " tmp"<<dataCol<<"=";
+ ss << vSubArguments[dataCol]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(gid0>="<<pTmpDVR1->GetArrayLength()<<" ||isnan(";
+ ss << "tmp"<<dataCol<<"))\n";
+ ss << " tmp"<<dataCol<<"=DBL_MIN;\n";
+
+ int conditionCol = 0;
+ int conditionRow = 0;
+ if(vSubArguments[dataCol + 1]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ {
+ throw Unhandled(__FILE__, __LINE__);
+ }
+ tmpCur = vSubArguments[dataCol + 1]
+ ->GetFormulaToken();
+ pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur);
+ conditionCol = pCurDVR->GetArrays().size();
+ conditionRow = pCurDVR->GetArrayLength();
+
+ if(dataCol!=conditionCol)
+ throw Unhandled(__FILE__, __LINE__);
+ if(dataCol > 0 && dataRow > 0)
+ {
+ formula::FormulaToken *tmpCur1 = vSubArguments[0]->GetFormulaToken();
+ formula::FormulaToken *tmpCur2 = vSubArguments[dataCol + 1]
+ ->GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR1= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur1);
+ const formula::DoubleVectorRefToken*pCurDVR2= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur2);
+
+ if(pCurDVR1->IsStartFixed() && pCurDVR1->IsEndFixed() &&
+ pCurDVR2->IsStartFixed() && pCurDVR2->IsEndFixed())
+ {
+ ss << " int i,j,p;\n";
+ ss << " bool flag;\n";
+
+ ss << " for(p = 1;p < " << dataRow << ";++p)\n";
+ ss << " {\n";
+ ss << " i = p;\n";
+ for(int i = 0; i < dataCol; ++i){
+ if(vSubArguments[i]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+ ss << " tmp"<<i<<"=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(isnan(tmp"<<i<<"))\n";
+ ss <<" tmp"<<i<<" = 0;\n";
+ }
+ ss << " flag = false;\n";
+ ss << " for(j = 1; j < " << conditionRow << ";++j)\n";
+ ss << " {\n";
+ ss << " i = j;\n";
+ ss << " if (flag)\n";
+ ss << " break;\n";
+ ss << " else{\n";
+ for(int i = dataCol + 1; i < dataCol + 1 + conditionCol; ++i){
+ if(vSubArguments[i]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+ ss << " tmp"<<i<<"=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(!isnan(tmp"<<i<<")){\n";
+ ss << " if(tmp"<<(i-dataCol-1)<<"!=tmp";
+ ss << i<<"){\n";
+ ss << " continue;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ ss << " flag=true;\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << " if (flag){\n";
+ for(int i = 0; i < dataCol; ++i){
+ ss << " if(tmp"<<dataCol<<"=="<<(i+1)<<"){\n";
+ ss << " value=tmp"<<i<<";\n";
+ ss << " }\n";
+ }
+ ss << " if(value > DBL_MIN)\n";
+ ss << " count++;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else
+ ss << "count = -1;\n";
+ }
+ else
+ ss << "count = -1;\n";
+ ss << " return count;\n";
+ ss << "}";
+}
+
+void OpDcount2::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n {\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double value=0;\n";
+ ss << " int count = 0;\n";
+ GenTmpVariables(ss,vSubArguments);
+ int dataCol = 0;
+ int dataRow = 0;
+ if(vSubArguments[0]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+
+ formula::FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur);
+ dataCol = pCurDVR->GetArrays().size();
+ dataRow = pCurDVR->GetArrayLength();
+
+ if(vSubArguments[dataCol]->GetFormulaToken()->GetType() !=
+ formula::svSingleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+
+ const formula::SingleVectorRefToken*pTmpDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(vSubArguments[dataCol]
+ ->GetFormulaToken());
+ ss << " tmp"<<dataCol<<"=";
+ ss << vSubArguments[dataCol]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(gid0>="<<pTmpDVR1->GetArrayLength()<<" ||isnan(";
+ ss << "tmp"<<dataCol<<"))\n";
+ ss << " tmp"<<dataCol<<"=DBL_MIN;\n";
+
+ int conditionCol = 0;
+ int conditionRow = 0;
+ if(vSubArguments[dataCol + 1]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ {
+ throw Unhandled(__FILE__, __LINE__);
+ }
+ tmpCur = vSubArguments[dataCol + 1]->
+ GetFormulaToken();
+ pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur);
+ conditionCol = pCurDVR->GetArrays().size();
+ conditionRow = pCurDVR->GetArrayLength();
+
+ if(dataCol!=conditionCol)
+ throw Unhandled(__FILE__, __LINE__);
+ if(dataCol > 0 && dataRow > 0)
+ {
+ formula::FormulaToken *tmpCur1 = vSubArguments[0]->GetFormulaToken();
+ formula::FormulaToken *tmpCur2 = vSubArguments[dataCol + 1]->
+ GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR1= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur1);
+ const formula::DoubleVectorRefToken*pCurDVR2= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur2);
+
+ if(pCurDVR1->IsStartFixed() && pCurDVR1->IsEndFixed() &&
+ pCurDVR2->IsStartFixed() && pCurDVR2->IsEndFixed())
+ {
+ ss << " int i,j,p;\n";
+ ss << " bool flag;\n";
+
+ ss << " for(p = 1;p < " << dataRow << ";++p)\n";
+ ss << " {\n";
+ ss << " i = p;\n";
+ for(int i = 0; i < dataCol; ++i){
+ if(vSubArguments[i]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+ ss << " tmp"<<i<<"=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(isnan(tmp"<<i<<"))\n";
+ ss <<" tmp"<<i<<" = 0;\n";
+ }
+ ss << " flag = false;\n";
+ ss << " for(j = 1; j < " << conditionRow << ";++j)\n";
+ ss << " {\n";
+ ss << " i = j;\n";
+ ss << " if (flag)\n";
+ ss << " break;\n";
+ ss << " else{\n";
+ for(int i = dataCol + 1; i < dataCol + 1 + conditionCol; ++i){
+ if(vSubArguments[i]->GetFormulaToken()->GetType() !=
+ formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__);
+ ss << " tmp"<<i<<"=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " if(!isnan(tmp"<<i<<")){\n";
+ ss << " if(tmp"<<(i-dataCol-1)<<"!=tmp";
+ ss << i<<"){\n";
+ ss << " continue;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ ss << " flag=true;\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << " if (flag){\n";
+ for(int i = 0; i < dataCol; ++i){
+ ss << " if(tmp"<<dataCol<<"=="<<(i+1)<<"){\n";
+ ss << " value=tmp"<<i<<";\n";
+ ss << " }\n";
+ }
+ ss << " if(value > DBL_MIN)\n";
+ ss << " count++;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else
+ ss << "count = -1;\n";
+ }
+ else
+ ss << "count = -1;\n";
+ ss << " return count;\n";
+ ss << "}";
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/opencl/op_database.hxx b/sc/source/core/opencl/op_database.hxx
new file mode 100644
index 000000000..effacc123
--- /dev/null
+++ b/sc/source/core/opencl/op_database.hxx
@@ -0,0 +1,106 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include "opbase.hxx"
+
+namespace sc::opencl {
+
+class OpDmax: public CheckVariables
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Dmax"; }
+};
+
+class OpDmin: public CheckVariables
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Dmin"; }
+};
+
+class OpDproduct: public CheckVariables
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Dproduct"; }
+};
+
+class OpDaverage: public CheckVariables
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Daverage"; }
+};
+
+class OpDstdev: public CheckVariables
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Dstdev"; }
+};
+
+class OpDstdevp: public CheckVariables
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Dstdevp"; }
+};
+
+class OpDsum: public CheckVariables
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Dsum"; }
+};
+
+class OpDvar: public CheckVariables
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Dvar"; }
+};
+
+class OpDvarp: public CheckVariables
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Dvarp"; }
+};
+
+class OpDcount: public CheckVariables
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Dcount"; }
+};
+
+class OpDcount2: public CheckVariables
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Dcount2"; }
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/opencl/op_financial.cxx b/sc/source/core/opencl/op_financial.cxx
new file mode 100644
index 000000000..30a47cbfb
--- /dev/null
+++ b/sc/source/core/opencl/op_financial.cxx
@@ -0,0 +1,4791 @@
+/* -*- 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 "op_financial.hxx"
+
+#include <formula/vectortoken.hxx>
+#include <sstream>
+
+using namespace formula;
+
+namespace sc::opencl {
+// Definitions of inline functions
+#include "opinlinefun_finacial.cxx"
+
+void RRI::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " double tmp = " << GetBottom() <<";\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double fv;\n";
+ ss << " double pv;\n";
+ ss << " double nper;\n";
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR0= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+
+ FormulaToken *tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur1);
+
+ FormulaToken *tmpCur2 = vSubArguments[2]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR2= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur2);
+
+ ss<< " int buffer_nper_len = ";
+ ss<< tmpCurDVR0->GetArrayLength();
+ ss << ";\n";
+
+ ss<< " int buffer_pv_len = ";
+ ss<< tmpCurDVR1->GetArrayLength();
+ ss << ";\n";
+
+ ss<< " int buffer_fv_len = ";
+ ss<< tmpCurDVR2->GetArrayLength();
+ ss << ";\n";
+
+ ss<<" if(gid0>=buffer_nper_len || isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss<<"))\n";
+ ss<<" nper = 0;\n\telse \n";
+ ss<<" nper = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss<<";\n";
+
+ ss<<" if(gid0>=buffer_pv_len || isnan(";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss<<"))\n";
+ ss<<" pv = 0;\n\telse \n";
+ ss<<" pv = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss<<";\n";
+
+ ss<<" if(gid0>=buffer_pv_len || isnan(";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss<<"))\n";
+ ss<<" fv = 0;\n\telse \n";
+ ss<<" fv = ";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss<<";\n";
+ ss << " tmp = pow(fv*pow(pv,-1),1.0*pow(nper,-1))-1;\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+
+void OpNominal::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &
+vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n\t";
+ ss << "double tmp = 0;\n\t";
+ ss << "double temp = 0;\n\t";
+ ss << "int gid0 = get_global_id(0);\n\t";
+ ss << "double tmp0=0,tmp1=0;\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << "{\n";
+ }
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss <<" temp="<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ ss <<" if (isnan(temp))\n";
+ ss <<" tmp"<<i<<"= 0;\n";
+ ss <<" else\n";
+ ss <<" tmp"<<i<<"=temp;\n";
+ ss <<" }\n";
+ }
+ else
+ {
+ ss <<" tmp"<<i<<"="<<vSubArguments[i]->GenSlidingWindowDeclRef(
+);
+ ss <<";\n";
+ }
+ }
+ ss<<"if(tmp1==0)\n\t";
+ ss<<"\treturn 0;\n\t";
+ ss<<"tmp=pow( tmp1,-1);\n\t";
+ ss<<"tmp=( pow( tmp0+ 1.0, tmp ) - 1.0 ) *";
+ ss<<"tmp1;\n\t";
+ ss << "return tmp;\n";
+ ss << "}";
+}
+
+void OpDollarde::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n\t";
+ ss << "double tmp = " << GetBottom() <<";\n\t";
+ ss << "int gid0 = get_global_id(0);\n\t";
+ ss << "double fInt = " << GetBottom() <<";\n\t";
+ ss << "double dollar;\n\t";
+ ss << "double fFrac;\n\t";
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR0= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+ FormulaToken *tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur1);
+ ss<< "int buffer_dollar_len = ";
+ ss<< tmpCurDVR0->GetArrayLength();
+ ss << ";\n\t";
+ ss<< "int buffer_frac_len = ";
+ ss<< tmpCurDVR1->GetArrayLength();
+ ss << ";\n\t";
+ ss<<"if((gid0)>=buffer_dollar_len || isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"dollar = 0;\n\telse \n\t\t";
+ ss<<"dollar = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+ ss<<"if((gid0)>=buffer_frac_len || isnan(";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"fFrac = 0;\n\telse \n\t\t";
+ ss<<"fFrac = (int)(";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss<<");\n\t";
+ ss << "tmp = modf( dollar , &fInt );\n\t";
+ ss << "tmp /= fFrac;\n\t";
+ ss << "tmp *= pow( 10.0 , ceil( log10(fFrac ) ) );\n\t";
+ ss << "tmp += fInt;\t";
+ ss << "\n\treturn tmp;\n";
+ ss << "}";
+}
+
+void OpDollarfr::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n\t";
+ ss << "double tmp = " << GetBottom() <<";\n\t";
+ ss << "int gid0 = get_global_id(0);\n\t";
+ ss << "double fInt = " << GetBottom() <<";\n\t";
+ ss << "double dollar;\n\t";
+ ss << "double fFrac;\n\t";
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR0= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+ FormulaToken *tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur1);
+ ss<< "int buffer_dollar_len = ";
+ ss<< tmpCurDVR0->GetArrayLength();
+ ss << ";\n\t";
+ ss<< "int buffer_frac_len = ";
+ ss<< tmpCurDVR1->GetArrayLength();
+ ss << ";\n\t";
+ ss<<"if((gid0)>=buffer_dollar_len || isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"dollar = 0;\n\telse \n\t\t";
+ ss<<"dollar = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+ ss<<"if((gid0)>=buffer_frac_len || isnan(";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"fFrac = 0;\n\telse \n\t\t";
+ ss<<"fFrac = (int)(";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss<<");\n\t";
+ ss << "tmp = modf( dollar , &fInt );\n\t";
+ ss << "tmp *= fFrac;\n\t";
+ ss << "tmp *= pow( 10.0 , -ceil( log10(fFrac ) ) );\n\t";
+ ss << "tmp += fInt;\t";
+ ss << "\n\treturn tmp;\n";
+ ss << "}";
+}
+
+void OpDISC::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(GetYearFrac_newDecl);decls.insert(DaysToDate_newDecl);
+ decls.insert(DaysInMonthDecl);decls.insert(IsLeapYearDecl);
+ funs.insert(GetYearFrac_new);funs.insert(DaysToDate_new);
+ funs.insert(DaysInMonth);funs.insert(IsLeapYear);
+}
+
+void OpDISC::GenSlidingWindowFunction(std::stringstream& ss,
+ const std::string &sSymName, SubArguments& vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_" << BinFuncName() << "(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n\t";
+ ss << " double tmp = " << GetBottom() << ";\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double arg0 = " << GetBottom() << ";\n";
+ ss << " double arg1 = " << GetBottom() << ";\n";
+ ss << " double arg2 = " << GetBottom() << ";\n";
+ ss << " double arg3 = " << GetBottom() << ";\n";
+ ss << " double arg4 = " << GetBottom() << ";\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken* pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << " {\n";
+ }
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " arg" << i << " = 0;\n";
+ ss << " else\n";
+ ss << " arg" << i << " = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " arg" << i << " = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ }
+ }
+ ss << " int nNullDate = 693594;\n";
+ ss << " tmp = 1.0 - arg2 / arg3;\n";
+ ss << " tmp /=";
+ ss << " GetYearFrac_new(nNullDate, (int)arg0, (int)arg1, (int)arg4);\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+
+void OpINTRATE::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(GetYearDiff_newDecl);decls.insert(GetDiffDate_newDecl);
+ decls.insert(DaysToDate_newDecl);decls.insert(GetNullDateDecl);
+ decls.insert(DateToDaysDecl);decls.insert(DaysInMonthDecl);
+ decls.insert(IsLeapYearDecl);
+ funs.insert(GetYearDiff_new);funs.insert(GetDiffDate_new);
+ funs.insert(DaysToDate_new);funs.insert(GetNullDate);
+ funs.insert(DateToDays);funs.insert(DaysInMonth);
+ funs.insert(IsLeapYear);
+}
+
+void OpINTRATE::GenSlidingWindowFunction(std::stringstream& ss,
+ const std::string &sSymName, SubArguments& vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_" << BinFuncName() << "(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " double tmp = " << GetBottom() << ";\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double arg0 = " << GetBottom() << ";\n";
+ ss << " double arg1 = " << GetBottom() << ";\n";
+ ss << " double arg2 = " << GetBottom() << ";\n";
+ ss << " double arg3 = " << GetBottom() << ";\n";
+ ss << " double arg4 = " << GetBottom() << ";\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken* pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << " {\n";
+ }
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " arg" << i << " = 0;\n";
+ ss << " else\n";
+ ss << " arg" << i << " = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " arg" << i << " = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ }
+ }
+ ss << " int nNullDate = GetNullDate();\n";
+ ss << " tmp = ((arg3 / arg2) - 1) / GetYearDiff_new(nNullDate, (int)arg0,";
+ ss << " (int)arg1,(int)arg4);\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+
+void OpFV::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(GetFVDecl);
+ funs.insert(GetFV);
+}
+
+void OpFV::GenSlidingWindowFunction(std::stringstream& ss,
+ const std::string &sSymName, SubArguments& vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " double tmp = " << GetBottom() << ";\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double arg0 = " << GetBottom() << ";\n";
+ ss << " double arg1 = " << GetBottom() << ";\n";
+ ss << " double arg2 = " << GetBottom() << ";\n";
+ ss << " double arg3 = " << GetBottom() << ";\n";
+ ss << " double arg4 = " << GetBottom() << ";\n";
+ unsigned j = vSubArguments.size();
+ while (j--)
+ {
+ FormulaToken* pCur = vSubArguments[j]->GetFormulaToken();
+ assert(pCur);
+ if(pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if(gid0 >= " << pSVR->GetArrayLength() << " || isnan(";
+ ss << vSubArguments[j]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " arg" << j << " = " <<GetBottom() << ";\n";
+ ss << " else\n";
+ ss << " arg" << j << " = ";
+ ss << vSubArguments[j]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ }
+ ss << " tmp = GetFV(arg0, arg1, arg2, arg3, arg4);\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+
+void OpIPMT::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(GetFVDecl);
+ funs.insert(GetFV);
+}
+
+void OpIPMT::GenSlidingWindowFunction(std::stringstream& ss,
+ const std::string &sSymName, SubArguments& vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " double tmp = " << GetBottom() << ";\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double arg0 = " << GetBottom() << ";\n";
+ ss << " double arg1 = " << GetBottom() << ";\n";
+ ss << " double arg2 = " << GetBottom() << ";\n";
+ ss << " double arg3 = " << GetBottom() << ";\n";
+ ss << " double arg4 = " << GetBottom() << ";\n";
+ ss << " double arg5 = " << GetBottom() << ";\n";
+ unsigned j = vSubArguments.size();
+ while (j--)
+ {
+ FormulaToken* pCur = vSubArguments[j]->GetFormulaToken();
+ assert(pCur);
+ if(pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if(gid0 >= " << pSVR->GetArrayLength() << " || isnan(";
+ ss << vSubArguments[j]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " arg" << j << " = " <<GetBottom() << ";\n";
+ ss << " else\n";
+ ss << " arg" << j << " = ";
+ ss << vSubArguments[j]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ }
+ ss << " double pmt ;\n";
+ ss << " if(arg0 == 0.0)\n";
+ ss << " return 0;\n";
+ ss << " double temp1 = 0;\n";
+ ss << " double abl = pow(1.0 + arg0, arg2);\n";
+ ss << " temp1 -= arg4;\n";
+ ss << " temp1 -= arg3 * abl;\n";
+ ss << " pmt = temp1 / (1.0 + arg0 * arg5) /";
+ ss << " ( (abl - 1.0) / arg0);\n";
+ ss << " double temp = pow( 1 + arg0, arg1 - 2);\n";
+ ss << " if(arg1 == 1.0)\n";
+ ss << " {\n";
+ ss << " if(arg5 > 0.0)\n";
+ ss << " tmp = 0.0;\n";
+ ss << " else\n";
+ ss << " tmp = -arg3;\n";
+ ss << " }\n";
+ ss << " else\n";
+ ss << " {\n";
+ ss << " if(arg5 > 0.0)\n";
+ ss << " tmp = GetFV(arg0, arg1 - 2.0, pmt, arg3, 1.0)";
+ ss << " - pmt;\n";
+ ss << " else\n";
+ ss << " tmp = GetFV(arg0, arg1 - 1.0, pmt, arg3, 0.0);\n";
+ ss << " }\n";
+ ss << " tmp = tmp * arg0;\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+void OpISPMT::GenSlidingWindowFunction(std::stringstream& ss,
+ const std::string &sSymName, SubArguments& vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_" << BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " double tmp = " << GetBottom() << ";\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double arg0 = " << GetBottom() << ";\n";
+ ss << " double arg1 = " << GetBottom() << ";\n";
+ ss << " double arg2 = " << GetBottom() << ";\n";
+ ss << " double arg3 = " << GetBottom() << ";\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken* pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << " {\n";
+ }
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " arg" << i << " = 0;\n";
+ ss << " else\n";
+ ss << " arg" << i << " = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " arg" << i << " = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ }
+ }
+ ss << " tmp = arg3 * arg0 * ( arg1 - arg2) * pow(arg2, -1);\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+
+void OpPDuration::GenSlidingWindowFunction(std::stringstream& ss,
+ const std::string &sSymName, SubArguments& vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_" << BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " double tmp = " << GetBottom() << ";\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double arg0 = " << GetBottom() << ";\n";
+ ss << " double arg1 = " << GetBottom() << ";\n";
+ ss << " double arg2 = " << GetBottom() << ";\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken* pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << " {\n";
+ }
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " arg" << i << " = 0;\n";
+ ss << " else\n";
+ ss << " arg" << i << " = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " arg" << i << " = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ }
+ }
+ ss << " tmp = log(arg2 * pow( arg1,-1)) / log(arg0 + 1.0);\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+
+void OpDuration_ADD::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(GetDurationDecl);decls.insert(lcl_GetcoupnumDecl);
+ decls.insert(GetYearFracDecl);decls.insert(DaysToDateDecl);
+ decls.insert(GetNullDateDecl);decls.insert(DateToDaysDecl);
+ decls.insert(DaysInMonthDecl);decls.insert(IsLeapYearDecl);
+ funs.insert(GetDuration);funs.insert(lcl_Getcoupnum);
+ funs.insert(GetYearFrac);funs.insert(DaysToDate);
+ funs.insert(GetNullDate);funs.insert(DateToDays);
+ funs.insert(DaysInMonth);funs.insert(IsLeapYear);
+}
+
+void OpDuration_ADD::GenSlidingWindowFunction(std::stringstream& ss,
+ const std::string &sSymName, SubArguments& vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " double tmp = " << GetBottom() << ";\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double arg0 = " << GetBottom() << ";\n";
+ ss << " double arg1 = " << GetBottom() << ";\n";
+ ss << " double arg2 = " << GetBottom() << ";\n";
+ ss << " double arg3 = " << GetBottom() << ";\n";
+ ss << " double arg4 = " << GetBottom() << ";\n";
+ ss << " double arg5 = " << GetBottom() << ";\n";
+ unsigned j = vSubArguments.size();
+ while (j--)
+ {
+ FormulaToken* pCur = vSubArguments[j]->GetFormulaToken();
+ assert(pCur);
+ if(pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if(gid0 >= " << pSVR->GetArrayLength() << " || isnan(";
+ ss << vSubArguments[j]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " arg" << j << " = " <<GetBottom() << ";\n";
+ ss << " else\n";
+ ss << " arg" << j << " = ";
+ ss << vSubArguments[j]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ }
+ ss << " int nNullDate = GetNullDate();\n";
+ ss << " tmp = GetDuration( nNullDate, (int)arg0, (int)arg1, arg2,";
+ ss << " arg3, (int)arg4, (int)arg5);\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+void OpMDuration::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(GetDuration_newDecl);decls.insert(lcl_Getcoupnum_newDecl);
+ decls.insert(addMonthsDecl);decls.insert(checklessthanDecl);
+ decls.insert(setDayDecl);decls.insert(ScaDateDecl);
+ decls.insert(GetYearFracDecl);decls.insert(DaysToDateDecl);
+ decls.insert(DaysInMonthDecl);decls.insert(IsLeapYearDecl);
+ funs.insert(GetDuration_new);funs.insert(lcl_Getcoupnum_new);
+ funs.insert(addMonths);funs.insert(checklessthan);
+ funs.insert(setDay);funs.insert(ScaDate);
+ funs.insert(GetYearFrac);funs.insert(DaysToDate);
+ funs.insert(DaysInMonth);funs.insert(IsLeapYear);
+}
+
+void OpMDuration::GenSlidingWindowFunction(std::stringstream& ss,
+ const std::string &sSymName, SubArguments& vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " double tmp = " << GetBottom() << ";\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double arg0 = " << GetBottom() << ";\n";
+ ss << " double arg1 = " << GetBottom() << ";\n";
+ ss << " double arg2 = " << GetBottom() << ";\n";
+ ss << " double arg3 = " << GetBottom() << ";\n";
+ ss << " double arg4 = " << GetBottom() << ";\n";
+ ss << " double arg5 = " << GetBottom() << ";\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken* pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << " {\n";
+ }
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " arg" << i << " = 0;\n";
+ ss << " else\n";
+ ss << " arg" << i << " = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " arg" << i << " = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ }
+ }
+ ss << " int nNullDate = 693594;\n";
+ ss << " tmp = GetDuration_new( nNullDate, (int)arg0, (int)arg1, arg2,";
+ ss << " arg3, (int)arg4, (int)arg5);\n";
+ ss << " tmp = tmp * pow(1.0 + arg3 * pow((int)arg4, -1.0), -1);\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+void Fvschedule::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &vSubArguments)
+{
+ CHECK_PARAMETER_COUNT( 2, 2 );
+ FormulaToken* pCur = vSubArguments[1]->GetFormulaToken();
+ assert(pCur);
+ if(vSubArguments[0]->GetFormulaToken()->GetType() != formula::svDoubleVectorRef)
+ throw Unhandled( __FILE__, __LINE__ );
+ const formula::DoubleVectorRefToken* pCurDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pCurDVR->GetRefRowSize();
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n\t";
+ ss << "double tmp = 1.0;\n\t";
+ ss << "int gid0 = get_global_id(0);\n\t";
+ ss << "double arg0 = " << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n\t";
+ ss << "if (isnan(arg0))\n\t\t";
+ ss << "arg0 = 0;\n\t";
+ ss << "double arg1;\n\t";
+ ss << "int arrayLength = " << pCurDVR->GetArrayLength() << ";\n\t";
+ ss << "for (int i = 0; i + gid0 < arrayLength &&";
+ ss << " i < " << nCurWindowSize << "; i++){\n\t\t";
+ ss << "arg1 = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ";\n\t\t\t";
+ ss << "if (isnan(arg1))\n\t\t\t\t";
+ ss << "arg1 = 0;\n\t\t\t";
+ ss << "tmp *= arg1 + 1.0;\n\t\t";
+ ss << "}\n\t";
+ ss << "return (double)tmp * arg0";
+ ss << ";\n}";
+}
+void Cumipmt::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(GetPMT_newDecl); decls.insert(GetFV_newDecl);
+ funs.insert(GetPMT_new);funs.insert(GetFV_new);
+}
+void Cumipmt::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &
+vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double fRate,fVal;\n";
+ ss << " int nStartPer,nEndPer,nNumPeriods,nPayType;\n";
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ FormulaToken *tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ FormulaToken *tmpCur2 = vSubArguments[2]->GetFormulaToken();
+ FormulaToken *tmpCur3 = vSubArguments[3]->GetFormulaToken();
+ FormulaToken *tmpCur4 = vSubArguments[4]->GetFormulaToken();
+ FormulaToken *tmpCur5 = vSubArguments[5]->GetFormulaToken();
+ if(tmpCur0->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR0= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+ ss <<" if(gid0 >= "<<tmpCurDVR0->GetArrayLength()<<" || isnan(";
+ ss <<vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" fRate = 0;\n else\n";
+ }
+ ss <<" fRate = "<<vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur1->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur1);
+ ss <<" if(gid0 >= "<<tmpCurDVR1->GetArrayLength()<<" || isnan(";
+ ss <<vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" nNumPeriods = 0;\n else\n";
+ }
+ ss <<" nNumPeriods = (int)";
+ ss <<vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur2->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR2= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur2);
+ ss <<" if(gid0 >= "<<tmpCurDVR2->GetArrayLength()<<" || isnan(";
+ ss <<vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" fVal = 0;\n else\n";
+ }
+ ss <<" fVal = "<<vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur3->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR3= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur3);
+ ss <<" if(gid0 >= "<<tmpCurDVR3->GetArrayLength()<<" || isnan(";
+ ss <<vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" nStartPer = 0;\n else\n";
+ }
+ ss <<" nStartPer = (int)";
+ ss <<vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur4->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR4= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur4);
+ ss <<" if(gid0 >= "<<tmpCurDVR4->GetArrayLength()<<" || isnan(";
+ ss <<vSubArguments[4]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" nEndPer = 0;\n else\n";
+ }
+ ss <<" nEndPer = (int)";
+ ss <<vSubArguments[4]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+
+ if(tmpCur5->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR5= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur5);
+ ss <<" if(gid0 >= "<<tmpCurDVR5->GetArrayLength()<<" || isnan(";
+ ss <<vSubArguments[5]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" nPayType = 0;\n else\n";
+ }
+ ss <<" nPayType = (int)"<<vSubArguments[5]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ ss <<" double fPmt;\n";
+ ss <<" fPmt = GetPMT_new( fRate, nNumPeriods, fVal, 0.0, nPayType );\n";
+ ss <<" double tmp = 0.0;\n";
+ ss <<" if( nStartPer == 1 )\n";
+ ss <<" {\n";
+ ss <<" if( nPayType <= 0 )\n";
+ ss <<" tmp = -fVal;\n";
+ ss <<" nStartPer++;\n";
+ ss <<" }\n";
+ ss <<" for( ; nStartPer<= nEndPer ; nStartPer++ )\n";
+ ss <<" {\n";
+ ss <<" if( nPayType > 0 )\n";
+ ss <<" tmp += GetFV_new( fRate, nStartPer - 2 , ";
+ ss <<"fPmt, fVal, 1 ) - fPmt;\n";
+ ss <<" else\n";
+ ss <<" tmp += GetFV_new( fRate, nStartPer - 1 , ";
+ ss <<"fPmt, fVal, 0 );\n";
+ ss <<" }\n";
+ ss <<" tmp *= fRate;\n";
+ ss <<" return tmp;\n";
+ ss <<"}";
+}
+
+void IRR::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_" << BinFuncName() << "(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " #define Epsilon 1.0E-7\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ FormulaToken* pSur = vSubArguments[1]->GetFormulaToken();
+ assert(pSur);
+ ss << " double fEstimated = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " double fEps = 1.0;\n";
+ ss << " double x = 0.0, xNew = 0.0, fNumerator = 0.0, fDenominator = 0.0;\n";
+ ss << " double nCount = 0.0;\n";
+ if (pSur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pSur);
+ ss << " if (gid0 >= " << pSVR->GetArrayLength() << ")\n";
+ ss << " fEstimated = 0.1;\n";
+ ss << " if (isnan(fEstimated))\n";
+ ss << " x = 0.1;\n";
+ ss << " else\n";
+ }
+ else if (pSur->GetType() == formula::svDouble)
+ {
+ ss << " if (isnan(fEstimated))\n";
+ ss << " x = 0.1;\n";
+ ss << " else\n";
+ }
+ ss << " x = fEstimated;\n";
+ ss << " unsigned short nItCount = 0;\n";
+ ss << " while (fEps > Epsilon && nItCount < 20){\n";
+ ss << " nCount = 0.0; fNumerator = 0.0; fDenominator = 0.0;\n";
+ ss << " double arg0, arg1;\n";
+ ss << " int i = 0;\n";
+ FormulaToken* pCur = vSubArguments[0]->GetFormulaToken();
+ assert(pCur);
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken* >(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for ( ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed()) {
+ ss << "i = gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << " /2*2; i++){\n";
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " i++;;\n";
+ ss << " arg1 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (!isnan(arg0)){\n";
+ ss << " fNumerator += arg0 / pow(1.0 + x, nCount);\n";
+ ss << " fDenominator+=-1*nCount*arg0/pow(1.0+x,nCount+1.0);\n";
+ ss << " nCount += 1;\n";
+ ss << " }\n";
+ ss << " if (!isnan(arg1)){\n";
+ ss << " fNumerator += arg1 / pow(1.0 + x, nCount);\n";
+ ss << " fDenominator+=-1*nCount*arg1/pow(1.0+x,nCount+1.0);\n";
+ ss << " nCount += 1;\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << "if(i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << ") ;{\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed()) {
+ ss << "; i < " << pDVR->GetArrayLength();
+ ss << " && i < (gid0+" << nCurWindowSize << " )/2*2; i++){\n";
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (!isnan(arg0)){\n";
+ ss << " fNumerator += arg0 / pow(1.0 + x, nCount);\n";
+ ss << " fDenominator+=-1*nCount*arg0/pow(1.0+x,nCount+1.0);\n";
+ ss << " nCount += 1;\n";
+ ss << " }\n";
+ ss << " i++;\n";
+ ss << " arg1 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (!isnan(arg1)){\n";
+ ss << " fNumerator += arg1 / pow(1.0 + x, nCount);\n";
+ ss << " fDenominator+=-1*nCount*arg1/pow(1.0+x,nCount+1.0);\n";
+ ss << " nCount+=1;\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << " if(i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+" << nCurWindowSize << "){\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed()){
+ ss << " ; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << " /2*2; i++){\n";
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " i++;;\n";
+ ss << " arg1 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (!isnan(arg0)){\n";
+ ss << " fNumerator += arg0 / pow(1.0 + x, nCount);\n";
+ ss << " fDenominator+=-1*nCount*arg0/pow(1.0+x,nCount+1.0);\n";
+ ss << " nCount += 1;\n";
+ ss << " }\n";
+ ss << " if (!isnan(arg1)){\n";
+ ss << " fNumerator += arg1 / pow(1.0 + x, nCount);\n";
+ ss << " fDenominator+=-1*nCount*arg1/pow(1.0+x,nCount+1.0);\n";
+ ss << " nCount+=1;\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << " if(i + gid0 < " << pDVR->GetArrayLength() << " &&";
+ ss << " i < " << nCurWindowSize << "){\n";
+
+ } else {
+ ss << "; i < " << nCurWindowSize << " /2*2; i++){\n";
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " i++;;\n";
+ ss << " arg1 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (!isnan(arg0)){\n";
+ ss << " fNumerator += arg0 / pow(1.0 + x, nCount);\n";
+ ss << " fDenominator+=-1*nCount*arg0/pow(1.0+x,nCount+1.0);\n";
+ ss << " nCount += 1;\n";
+ ss << " }\n";
+ ss << " if (!isnan(arg1)){\n";
+ ss << " fNumerator += arg1 / pow(1.0 + x, nCount);\n";
+ ss << " fDenominator+=-1*nCount*arg1/pow(1.0+x,nCount+1.0);\n";
+ ss << " nCount+=1;\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << "if(i<" << nCurWindowSize << "){\n";
+
+ }
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (isnan(arg0))\n";
+ ss << " continue;\n";
+ ss << " fNumerator += arg0 / pow(1.0+x, nCount);\n";
+ ss << " fDenominator += -nCount * arg0 / pow(1.0+x,nCount+1.0);\n";
+ ss << " nCount+=1;\n";
+ ss << " }\n";
+ ss << " xNew = x - fNumerator / fDenominator;\n";
+ ss << " fEps = fabs(xNew - x);\n";
+ ss << " x = xNew;\n";
+ ss << " nItCount++;\n }\n";
+ ss << " if (fEstimated == 0.0 && fabs(x) < Epsilon)\n";
+ ss << " x = 0.0;\n";
+ ss << " if (fEps < Epsilon)\n";
+ ss << " return x;\n";
+ ss << " else\n";
+ // FIXME: This is of course horribly wrong. 523 is the error code NoConvergence, and this should
+ // be CreateDoubleError(523). Ditto for the other occurrences of 523 in the OpenCL code
+ // generated in this file.
+ ss << " return (double)523;\n";
+ ss << "}";
+}
+
+void XNPV::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &vSubArguments)
+{
+ FormulaToken *pCur = vSubArguments[1]->GetFormulaToken();
+ assert(pCur);
+ const formula::DoubleVectorRefToken* pCurDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pCurDVR->GetRefRowSize();
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"( ";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+
+ ss << ") {\n\t";
+ ss << "double result = 0.0;\n\t";
+ ss << "int gid0 = get_global_id(0);\n\t";
+ ss << "int i=0;\n\t";
+ ss << "double date;\n\t";
+ ss << "double value;\n\t";
+ ss << "double rate;\n\t";
+ ss << "double dateNull;\n\t";
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR0= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+
+ FormulaToken *tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ const formula::DoubleVectorRefToken*tmpCurDVR1= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur1);
+
+ FormulaToken *tmpCur2 = vSubArguments[2]->GetFormulaToken();
+ const formula::DoubleVectorRefToken*tmpCurDVR2= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur2);
+ ss<< "int buffer_rate_len = ";
+ ss<< tmpCurDVR0->GetArrayLength();
+ ss << ";\n\t";
+ ss<< "int buffer_value_len = ";
+ ss<< tmpCurDVR1->GetArrayLength();
+ ss << ";\n\t";
+ ss<< "int buffer_date_len = ";
+ ss<< tmpCurDVR2->GetArrayLength();
+ ss << ";\n\t";
+ ss<<"if((gid0)>=buffer_date_len || isnan(";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"return NAN;\n\telse \n";
+ ss<<"dateNull = ";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+ ss<<"if((gid0)>=buffer_rate_len || isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"return NAN;\n\telse \n";
+ ss<<"rate = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+ ss<<"if(1 == buffer_date_len )\n";
+ ss<<"return ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+ ss << "for (int i = ";
+ if (!pCurDVR->IsStartFixed() && pCurDVR->IsEndFixed())
+ {
+ ss << "gid0; i < "<< nCurWindowSize <<"; i++)\n\t\t";
+ }
+ else if (pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed())
+ {
+ ss << "0; i < gid0+"<< nCurWindowSize <<"; i++)\n\t\t";
+ }
+ else
+ {
+ ss << "0; i < "<< nCurWindowSize <<"; i++)\n\t\t";
+ }
+ ss << "{\n\t";
+ if (!pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed())
+ {
+ ss << "if((i+gid0)>=buffer_value_len || (i+gid0)>=buffer_date_len)\n\t\t";
+ ss << "return result;\n\telse \n\t\t";
+ }
+ else
+ {
+ ss << "if(i>=buffer_value_len || i>=buffer_date_len)\n\t\t";
+ ss << "return result;\n\telse \n\t\t";
+ }
+
+ ss << "value = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef(true);
+ ss << ";\n";
+ ss << " date = ";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef(true);
+ ss << ";\n";
+ ss << "result += value/(pow((rate+1),(date-dateNull)/365));\n";
+ ss << "}\n";
+ ss << "return result;\n";
+ ss << "}";
+}
+
+ void PriceMat::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(GetYearFracDecl);decls.insert(GetNullDateDecl);
+ decls.insert(DateToDaysDecl);decls.insert(DaysToDateDecl);
+ decls.insert(DaysInMonthDecl);decls.insert(IsLeapYearDecl);
+
+ funs.insert(GetYearFrac);funs.insert(GetNullDate);
+ funs.insert(DateToDays);funs.insert(DaysToDate);
+ funs.insert(DaysInMonth);funs.insert(IsLeapYear);
+}
+void PriceMat::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n\t";
+ ss << "int gid0 = get_global_id(0);\n\t";
+ ss << "double result=0;\n\t";
+ ss<< "int nNullDate = GetNullDate( );\n\t";
+ ss <<"int settle;\n\t";
+ ss <<"int mat;\n\t";
+ ss <<"int issue;\n\t";
+ ss <<"double rate;\n\t";
+ ss <<"double yield;\n\t";
+ ss <<"int nBase;\n\t";
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR0= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+ FormulaToken *tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur1);
+ FormulaToken *tmpCur2 = vSubArguments[2]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR2= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur2);
+ FormulaToken *tmpCur3 = vSubArguments[3]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR3= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur3);
+ FormulaToken *tmpCur4 = vSubArguments[4]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR4= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur4);
+ FormulaToken *tmpCur5 = vSubArguments[5]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR5= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur5);
+
+ ss<< "int buffer_settle_len = ";
+ ss<< tmpCurDVR0->GetArrayLength();
+ ss << ";\n\t";
+ ss<< "int buffer_mat_len = ";
+ ss<< tmpCurDVR1->GetArrayLength();
+ ss << ";\n\t";
+ ss<< "int buffer_issue_len = ";
+ ss<< tmpCurDVR2->GetArrayLength();
+ ss << ";\n\t";
+ ss<< "int buffer_rate_len = ";
+ ss<< tmpCurDVR3->GetArrayLength();
+ ss << ";\n\t";
+ ss<< "int buffer_yield_len = ";
+ ss<< tmpCurDVR4->GetArrayLength();
+ ss << ";\n\t";
+ ss<< "int buffer_base_len = ";
+ ss<< tmpCurDVR5->GetArrayLength();
+ ss << ";\n\t";
+ ss<<"if(gid0>=buffer_settle_len || isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"settle = 0;\n\telse \n\t\t";
+ ss<<"settle = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+ ss<<"if(gid0>=buffer_mat_len || isnan(";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"mat = 0;\n\telse \n\t\t";
+ ss<<"mat = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+ ss<<"if(gid0>=buffer_issue_len || isnan(";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"issue = 0;\n\telse \n\t\t";
+ ss<<"issue = ";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+ ss<<"if(gid0>=buffer_rate_len || isnan(";
+ ss << vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"rate = 0;\n\telse \n\t\t";
+ ss<<"rate = ";
+ ss << vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+ ss<<"if(gid0>=buffer_yield_len || isnan(";
+ ss << vSubArguments[4]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"yield = 0;\n\telse \n\t\t";
+ ss<<"yield = ";
+ ss << vSubArguments[4]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+ ss<<"if(gid0>=buffer_base_len || isnan(";
+ ss << vSubArguments[5]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"nBase = 0;\n\telse \n\t\t";
+ ss<<"nBase = ";
+ ss << vSubArguments[5]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+ ss<< "double fIssMat = GetYearFrac( nNullDate, issue, mat, nBase);\n";
+ ss<<"double fIssSet = GetYearFrac( nNullDate, issue, settle,nBase);\n";
+ ss<<"double fSetMat = GetYearFrac( nNullDate, settle, mat, nBase);\n";
+ ss<<"result = 1.0 + fIssMat * rate;\n\t";
+ ss<<"result /= 1.0 + fSetMat * yield;\n\t";
+ ss<<"result -= fIssSet * rate;\n\t";
+ ss<<"result*= 100.0;\n\t";
+ ss<<"return result;\n\t";
+ ss<<"}\n";
+}
+
+void OpSYD::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double result=0;\n";
+ ss << " double cost;\n";
+ ss << " double salvage;\n";
+ ss << " double life;\n";
+ ss << " double period;\n";
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR0= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+
+ FormulaToken *tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur1);
+
+ FormulaToken *tmpCur2 = vSubArguments[2]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR2= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur2);
+
+ FormulaToken *tmpCur3 = vSubArguments[3]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR3= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur3);
+
+ ss << " int buffer_cost_len = ";
+ ss << tmpCurDVR0->GetArrayLength();
+ ss << ";\n";
+
+ ss << " int buffer_salvage_len = ";
+ ss << tmpCurDVR1->GetArrayLength();
+ ss << ";\n";
+
+ ss << " int buffer_life_len = ";
+ ss << tmpCurDVR2->GetArrayLength();
+ ss << ";\n";
+ ss << " int buffer_period_len = ";
+ ss << tmpCurDVR3->GetArrayLength();
+ ss << ";\n";
+
+ ss <<" if(gid0>=buffer_cost_len || isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" cost = 0;\n\telse \n";
+ ss <<" cost = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ ss <<" if(gid0>=buffer_salvage_len || isnan(";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" salvage = 0;\n\telse \n";
+ ss <<" salvage = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ ss <<" if(gid0>=buffer_life_len || isnan(";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" life = 0;\n\telse \n";
+ ss <<" life = ";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ ss <<" if(gid0>=buffer_period_len || isnan(";
+ ss << vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" period = 0;\n\telse \n";
+ ss <<" period = ";
+ ss << vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ ss <<" double tmpvalue = ((life*(life+1))*pow(2.0,-1));\n";
+ ss <<" result = ((cost-salvage)*(life-period+1)";
+ ss << "*pow(tmpvalue,-1));\n";
+ ss <<" return result;\n";
+ ss <<"}\n";
+}
+
+void MIRR::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &vSubArguments)
+{
+ FormulaToken* pCur = vSubArguments[0]->GetFormulaToken();
+ assert(pCur);
+ const formula::DoubleVectorRefToken* pCurDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pCurDVR->GetRefRowSize();
+ FormulaToken* pCur1 = vSubArguments[1]->GetFormulaToken();
+ assert(pCur1);
+ const formula::SingleVectorRefToken* pSVR1 =
+ static_cast< const formula::SingleVectorRefToken* >(pCur1);
+ assert(pSVR1);
+ FormulaToken* pCur2 = vSubArguments[2]->GetFormulaToken();
+ assert(pCur2);
+ const formula::SingleVectorRefToken* pSVR2 =
+ static_cast< const formula::SingleVectorRefToken* >(pCur2);
+ assert(pSVR2);
+
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n\t";
+ ss << "double tmp = " << GetBottom() <<";\n\t";
+ ss << "int gid0 = get_global_id(0);\n\t";
+ ss << "double arg0, arg1, arg2;\n\t";
+ ss << "arg1 = " << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss << ";\n\t";
+ ss << "arg2 = " << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss << ";\n\t";
+ ss << "int argLen1 = " << pSVR1->GetArrayLength() << ";\n\t";
+ ss << "int argLen2 = " << pSVR2->GetArrayLength() << ";\n\t";
+ ss << "if (gid0 >= argLen1)\n\t\t";
+ ss << "arg1 = 0.0;\n\t";
+ ss << "if (gid0 >= argLen2)\n\t\t";
+ ss << "arg2 = 0.0;\n\t";
+ ss << "if (isnan(arg1))\n\t\t";
+ ss << "arg1 = 0.0;\n\t";
+ ss << "if (isnan(arg2))\n\t\t";
+ ss << "arg2 = 0.0;\n\t";
+ ss << "double invest = arg1 + 1.0;\n\t";
+ ss << "double reinvest = arg2 + 1.0;\n\t";
+ ss << "double NPV_invest = 0.0;\n\t";
+ ss << "double Pow_invest = 1.0;\n\t";
+ ss << "double NPV_reinvest = 0.0;\n\t";
+ ss << "double Pow_reinvest = 1.0;\n\t";
+ ss << "int nCount = 0;\n\t";
+ ss << "int arrayLength = " << pCurDVR->GetArrayLength() << ";\n\t";
+ ss << "for (int i = 0; i + gid0 < arrayLength &&";
+ ss << " i < " << nCurWindowSize << "; i++){\n\t\t";
+ ss << "arg0 = " << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n\t\t";
+ ss << "if (isnan(arg0))\n\t\t\t";
+ ss << "continue;\n\t\t";
+ ss << "if (arg0 > 0.0)\n\t\t\t";
+ ss << "NPV_reinvest += arg0 * Pow_reinvest;\n\t\t";
+ ss << "else if (arg0 < 0.0)\n\t\t\t";
+ ss << "NPV_invest += arg0 * Pow_invest;\n\t\t";
+ ss << "Pow_reinvest /= reinvest;\n\t\t";
+ ss << "Pow_invest /= invest;\n\t\t";
+ ss << "nCount++;\n\t";
+ ss << "}\n\t";
+ ss << "tmp = ";
+ ss << "-NPV_reinvest /NPV_invest * pow(reinvest,(double)nCount-1);\n\t";
+ ss << "tmp = pow(tmp, 1.0 / (nCount - 1)) - 1.0;\n\t";
+ ss << "return (double)tmp;\n";
+ ss << "}";
+}
+
+void OpEffective::GenSlidingWindowFunction(std::stringstream& ss,
+ const std::string &sSymName, SubArguments& vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_" << BinFuncName() << "(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " double tmp = " << GetBottom() <<";\n";
+ ss << " int gid0 = get_global_id(0);\n\t";
+ ss << " double arg0 = " << GetBottom() << ";\n";
+ ss << " double arg1 = " << GetBottom() << ";\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken* pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << " {\n";
+ }
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " arg" << i << " = 0;\n";
+ ss << " else\n";
+ ss << " arg" << i << " = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " arg" << i << " = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ }
+ }
+ ss << " tmp = pow(1.0 + arg0 * pow(arg1, -1), arg1)-1.0;\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+
+ void OpTbilleq::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(GetDiffDate360_Decl);decls.insert(GetDiffDate360Decl);
+ decls.insert(DateToDaysDecl);decls.insert(DaysToDate_LocalBarrierDecl);
+ decls.insert(DaysInMonthDecl);decls.insert(GetNullDateDecl);
+ decls.insert(IsLeapYearDecl);
+ funs.insert(GetDiffDate360_);funs.insert(GetDiffDate360);
+ funs.insert(DateToDays);funs.insert(DaysToDate_LocalBarrier);
+ funs.insert(DaysInMonth);funs.insert(GetNullDate);
+ funs.insert(IsLeapYear);
+}
+void OpTbilleq::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n\t";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << "double tmp = 0;\n\t";
+ ss << "double tmp000;\n\t";
+ ss << "double tmp001;\n\t";
+ ss << "double tmp002;\n\t";
+
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR0= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+
+ FormulaToken *tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur1);
+
+ FormulaToken *tmpCur2 = vSubArguments[2]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR2= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur2);
+
+ ss<< "int buffer_tmp000_len = ";
+ ss<< tmpCurDVR0->GetArrayLength();
+ ss << ";\n\t";
+
+ ss<< "int buffer_tmp001_len = ";
+ ss<< tmpCurDVR1->GetArrayLength();
+ ss << ";\n\t";
+
+ ss<< "int buffer_tmp002_len = ";
+ ss<< tmpCurDVR2->GetArrayLength();
+ ss << ";\n\t";
+
+ ss<<"if(gid0>=buffer_tmp000_len || isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"tmp000 = 0;\n\telse \n\t\t";
+ ss<<"tmp000 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+
+ ss<<"if(gid0>=buffer_tmp001_len || isnan(";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"tmp001 = 0;\n\telse \n\t\t";
+ ss<<"tmp001 = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+
+ ss<<"if(gid0>=buffer_tmp002_len || isnan(";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"tmp002 = 0;\n\telse \n\t\t";
+ ss<<"tmp002 = ";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+
+ ss<<"tmp001+=1.0;\n";
+ ss<<"int nDiff =GetDiffDate360(GetNullDate(),tmp000,tmp001,true);\n";
+ ss<<"tmp =( 365 * tmp002 ) / ( 360 - ( tmp002 * ( nDiff ) ) );\n";
+ ss << "return tmp;\n";
+ ss << "}";
+}
+void OpCumprinc::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(GetPMT_newDecl); decls.insert(GetFV_newDecl);
+ funs.insert(GetPMT_new);funs.insert(GetFV_new);
+}
+void OpCumprinc::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " double tmp = " << GetBottom() <<";\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double fRate,fVal;\n";
+ ss << " int nStartPer,nEndPer,nNumPeriods,nPayType;\n";
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ FormulaToken *tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ FormulaToken *tmpCur2 = vSubArguments[2]->GetFormulaToken();
+ FormulaToken *tmpCur3 = vSubArguments[3]->GetFormulaToken();
+ FormulaToken *tmpCur4 = vSubArguments[4]->GetFormulaToken();
+ FormulaToken *tmpCur5 = vSubArguments[5]->GetFormulaToken();
+ if(tmpCur0->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR0= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+ ss <<" if(gid0 >= "<<tmpCurDVR0->GetArrayLength()<<" || isnan(";
+ ss <<vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" fRate = 0;\n else\n";
+ }
+ ss <<" fRate = "<<vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur1->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur1);
+ ss <<" if(gid0 >= "<<tmpCurDVR1->GetArrayLength()<<" || isnan(";
+ ss <<vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" nNumPeriods = 0;\n else\n";
+ }
+ ss <<" nNumPeriods = (int)";
+ ss <<vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur2->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR2= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur2);
+ ss <<" if(gid0 >= "<<tmpCurDVR2->GetArrayLength()<<" || isnan(";
+ ss <<vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" fVal = 0;\n else\n";
+ }
+ ss <<" fVal = "<<vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur3->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR3= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur3);
+ ss <<" if(gid0 >= "<<tmpCurDVR3->GetArrayLength()<<" || isnan(";
+ ss <<vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" nStartPer = 0;\n else\n";
+ }
+ ss <<" nStartPer = (int)";
+ ss <<vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur4->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR4= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur4);
+ ss <<" if(gid0 >= "<<tmpCurDVR4->GetArrayLength()<<" || isnan(";
+ ss <<vSubArguments[4]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" nEndPer = 0;\n else\n";
+ }
+ ss <<" nEndPer = (int)";
+ ss <<vSubArguments[4]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+
+ if(tmpCur5->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR5= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur5);
+ ss <<" if(gid0 >= "<<tmpCurDVR5->GetArrayLength()<<" || isnan(";
+ ss <<vSubArguments[5]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" nPayType = 0;\n else\n";
+ }
+ ss <<" nPayType = (int)";
+ ss <<vSubArguments[5]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ ss <<" double fPmt;\n";
+ ss <<" fPmt = GetPMT_new( fRate, nNumPeriods,fVal,0.0,nPayType );\n";
+ ss <<" if(nStartPer == 1)\n";
+ ss <<" {\n";
+ ss <<" if( nPayType <= 0 )\n";
+ ss <<" tmp = fPmt + fVal * fRate;\n";
+ ss <<" else\n";
+ ss <<" tmp = fPmt;\n";
+ ss <<" nStartPer=nStartPer+1;\n";
+ ss <<" }\n";
+ ss <<" for( int i = nStartPer ; i <= nEndPer ; i++ )\n";
+ ss <<" {\n";
+ ss <<" if( nPayType > 0 )\n";
+ ss <<" tmp += fPmt - ( GetFV_new( fRate,i - 2,";
+ ss <<"fPmt,fVal,1)- fPmt ) * fRate;\n";
+ ss <<" else\n";
+ ss <<" tmp += fPmt - GetFV_new( fRate, i - 1,";
+ ss <<"fPmt,fVal,0 ) * fRate;\n";
+ ss <<" }\n";
+ ss <<" return tmp;\n";
+ ss <<"}";
+}
+void OpAccrint::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(IsLeapYearDecl); decls.insert(DaysInMonthDecl);
+ decls.insert(DaysToDateDecl); decls.insert(DateToDaysDecl);
+ decls.insert(GetNullDateDecl); decls.insert(GetDiffDateDecl);
+ funs.insert(IsLeapYear);funs.insert(DaysInMonth);
+ funs.insert(DaysToDate);funs.insert(DateToDays);
+ funs.insert(GetNullDate);funs.insert(GetDiffDate);
+}
+void OpAccrint::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName,
+ SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double tmp = 0;\n";
+ ss << " int nStartDate,nEndDate,mode,freq;\n";
+ ss << " int nDays1stYear=0;\n";
+ ss << " double fVal,fRate;\n";
+ FormulaToken* tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR0= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+ FormulaToken* tmpCur2 = vSubArguments[2]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR2= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur2);
+ FormulaToken* tmpCur3 = vSubArguments[3]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR3= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur3);
+ FormulaToken* tmpCur4 = vSubArguments[4]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR4= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur4);
+ FormulaToken* tmpCur5 = vSubArguments[5]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR5= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur5);
+ FormulaToken* tmpCur6 = vSubArguments[6]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR6= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur6);
+ ss<< " int buffer_nIssue_len = ";
+ ss<< tmpCurDVR0->GetArrayLength();
+ ss<< ";\n";
+ ss<< " int buffer_nSettle_len = ";
+ ss<< tmpCurDVR2->GetArrayLength();
+ ss<< ";\n";
+ ss<< " int buffer_fRate_len = ";
+ ss<< tmpCurDVR3->GetArrayLength();
+ ss<< ";\n";
+ ss<< " int buffer_fVal_len = ";
+ ss<< tmpCurDVR4->GetArrayLength();
+ ss<< ";\n";
+ ss<< " int buffer_nFreq_len = ";
+ ss<< tmpCurDVR5->GetArrayLength();
+ ss<< ";\n";
+ ss<< " int buffer_nMode_len = ";
+ ss<< tmpCurDVR6->GetArrayLength();
+ ss << ";\n";
+ ss<<" if(gid0 >= buffer_nIssue_len || isnan(";
+ ss <<vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" nStartDate = 0;\n else\n";
+ ss <<" nStartDate=(int)";
+ ss <<vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ ss <<" if(gid0 >= buffer_nSettle_len || isnan(";
+ ss <<vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" nEndDate = 0;\n else\n";
+ ss <<" nEndDate=(int)";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss <<" if(gid0 >= buffer_fRate_len || isnan(";
+ ss <<vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" fRate = 0;\n else\n";
+ ss <<" fRate=";
+ ss << vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ ss <<" if(gid0 >= buffer_fVal_len || isnan(";
+ ss <<vSubArguments[4]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" fVal = 0;\n else\n";
+ ss <<" fVal=";
+ ss << vSubArguments[4]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ ss <<" if(gid0 >= buffer_nFreq_len || isnan(";
+ ss <<vSubArguments[5]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" freq = 0;\n else\n";
+ ss <<" freq= (int)";
+ ss << vSubArguments[5]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ ss <<" if(gid0 >= buffer_nMode_len || isnan(";
+ ss <<vSubArguments[6]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" mode = 0;\n else\n";
+ ss <<" mode = (int)";
+ ss << vSubArguments[6]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ ss <<" int nNullDate=GetNullDate();\n";
+ ss <<" int nTotalDays = GetDiffDate(nNullDate,nStartDate,";
+ ss <<"nEndDate, mode,&nDays1stYear);\n";
+ ss <<" tmp = fVal*fRate*convert_double(nTotalDays)";
+ ss <<"/convert_double(nDays1stYear);\n";
+ ss <<" return tmp;\n";
+ ss <<"}";
+}
+
+void OpAccrintm::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(IsLeapYearDecl); decls.insert(DaysInMonthDecl);
+ decls.insert(DaysToDateDecl); decls.insert(DateToDaysDecl);
+ decls.insert(GetNullDateDecl); decls.insert(GetDiffDateDecl);
+ funs.insert(IsLeapYear);funs.insert(DaysInMonth);
+ funs.insert(DaysToDate);funs.insert(DateToDays);
+ funs.insert(GetNullDate);funs.insert(GetDiffDate);
+}
+void OpAccrintm::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n\t";
+ ss << "int gid0 = get_global_id(0);\n\t";
+ ss << "double tmp = " << GetBottom() <<";\n\t";
+ ss << "int nStartDate,nEndDate,mode;\n\t";
+ ss << "double fRate,fVal;\n\t";
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR0= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+ FormulaToken *tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur1);
+
+ FormulaToken *tmpCur2 = vSubArguments[2]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR2= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur2);
+
+ FormulaToken *tmpCur3 = vSubArguments[3]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR3= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur3);
+
+ FormulaToken *tmpCur4 = vSubArguments[4]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR4= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur4);
+ ss<< "int buffer_nIssue_len = ";
+ ss<< tmpCurDVR0->GetArrayLength();
+ ss << ";\n\t";
+
+ ss<< "int buffer_nSettle_len = ";
+ ss<< tmpCurDVR1->GetArrayLength();
+ ss << ";\n\t";
+
+ ss<< "int buffer_fRate_len = ";
+ ss<< tmpCurDVR2->GetArrayLength();
+ ss << ";\n\t";
+
+ ss<< "int buffer_fVal_len = ";
+ ss<< tmpCurDVR3->GetArrayLength();
+ ss << ";\n\t";
+
+ ss<< "int buffer_nMode_len = ";
+ ss<< tmpCurDVR4->GetArrayLength();
+ ss << ";\n\t";
+ ss <<"if(gid0 >= buffer_nIssue_len || isnan(";
+ ss <<vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<"))\n\t\t";
+ ss <<"nStartDate = 0;\n\telse\n\t\t";
+ ss << "nStartDate=(int)";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<";\n\t";
+ ss <<"if(gid0 >= buffer_nSettle_len || isnan(";
+ ss <<vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss <<"))\n\t\t";
+ ss <<"nEndDate = 0;\n\telse\n\t\t";
+ ss << "nEndDate=(int)";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss << ";\n\t";
+
+ ss <<"if(gid0 >= buffer_fRate_len || isnan(";
+ ss <<vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss <<"))\n\t\t";
+ ss <<"fRate = 0;\n\telse\n\t\t";
+ ss << "fRate=";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss <<";\n\t";
+ ss <<"if(gid0 >= buffer_fVal_len || isnan(";
+ ss <<vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss <<"))\n\t\t";
+ ss <<"fVal = 0;\n\telse\n\t\t";
+ ss << "fVal=";
+ ss << vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss << ";\n\t";
+ ss <<"if(gid0 >= buffer_nMode_len || isnan(";
+ ss <<vSubArguments[4]->GenSlidingWindowDeclRef();
+ ss <<"))\n\t\t";
+ ss <<"mode = 0;\n\telse\n\t\t";
+ ss << "mode = (int)";
+ ss << vSubArguments[4]->GenSlidingWindowDeclRef();
+ ss << ";\n\t";
+ ss <<"int nDays1stYear=0;\n\t";
+ ss <<"int nNullDate=GetNullDate();\n\t";
+ ss <<"int nTotalDays = GetDiffDate(nNullDate,nStartDate,";
+ ss <<"nEndDate, mode,&nDays1stYear);\n\t";
+ ss <<"tmp = fVal*fRate*convert_double(nTotalDays)";
+ ss <<"/convert_double(nDays1stYear);\n\t";
+ ss << "return tmp;\n";
+ ss << "}";
+}
+
+ void OpYield::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(getYield_Decl);decls.insert(getPrice_Decl);
+ decls.insert(coupnumDecl);decls.insert(coupdaysncDecl);
+ decls.insert(coupdaybsDecl);decls.insert(coupdaysDecl);
+ decls.insert(lcl_GetcoupnumDecl);decls.insert(lcl_GetcoupdaysDecl);
+ decls.insert(lcl_GetcoupdaybsDecl);decls.insert(getDiffDecl);
+ decls.insert(getDaysInYearRangeDecl);decls.insert(GetDaysInYearDecl);
+ decls.insert(GetDaysInYearsDecl);decls.insert(getDaysInMonthRangeDecl);
+ decls.insert(addMonthsDecl);decls.insert(ScaDateDecl);
+ decls.insert(GetNullDateDecl);decls.insert(DateToDaysDecl);
+ decls.insert(DaysToDateDecl);decls.insert(DaysInMonthDecl);
+ decls.insert(IsLeapYearDecl);
+
+ funs.insert(getYield_);funs.insert(getPrice_);
+ funs.insert(coupnum);funs.insert(coupdaysnc);
+ funs.insert(coupdaybs);funs.insert(coupdays);
+ funs.insert(lcl_Getcoupnum);funs.insert(lcl_Getcoupdays);
+ funs.insert(lcl_Getcoupdaybs);funs.insert(getDiff);
+ funs.insert(getDaysInYearRange);funs.insert(GetDaysInYear);
+ funs.insert(GetDaysInYears);funs.insert(getDaysInMonthRange);
+ funs.insert(addMonths);funs.insert(ScaDate);
+ funs.insert(GetNullDate);funs.insert(DateToDays);
+ funs.insert(DaysToDate);funs.insert(DaysInMonth);
+ funs.insert(IsLeapYear);
+}
+
+void OpYield::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n\t";
+ ss << "double tmp = 0;\n\t";
+ ss << "int gid0 = get_global_id(0);\n\t";
+ ss << "double tmp000;\n\t";
+ ss << "double tmp001;\n\t";
+ ss << "double tmp002;\n\t";
+ ss << "double tmp003;\n\t";
+ ss << "double tmp004;\n\t";
+ ss << "double tmp005;\n\t";
+ ss << "double tmp006;\n\t";
+
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR0= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+
+ FormulaToken *tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur1);
+
+ FormulaToken *tmpCur2 = vSubArguments[2]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR2= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur2);
+
+ FormulaToken *tmpCur3 = vSubArguments[3]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR3= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur3);
+
+ FormulaToken *tmpCur4 = vSubArguments[4]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR4= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur4);
+
+ FormulaToken *tmpCur5 = vSubArguments[5]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR5= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur5);
+
+ FormulaToken *tmpCur6 = vSubArguments[6]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR6= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur6);
+
+ ss<< "int buffer_tmp000_len = ";
+ ss<< tmpCurDVR0->GetArrayLength();
+ ss << ";\n\t";
+
+ ss<< "int buffer_tmp001_len = ";
+ ss<< tmpCurDVR1->GetArrayLength();
+ ss << ";\n\t";
+
+ ss<< "int buffer_tmp002_len = ";
+ ss<< tmpCurDVR2->GetArrayLength();
+ ss << ";\n\t";
+
+ ss<< "int buffer_tmp003_len = ";
+ ss<< tmpCurDVR3->GetArrayLength();
+ ss << ";\n\t";
+
+ ss<< "int buffer_tmp004_len = ";
+ ss<< tmpCurDVR4->GetArrayLength();
+ ss << ";\n\t";
+
+ ss<< "int buffer_tmp005_len = ";
+ ss<< tmpCurDVR5->GetArrayLength();
+ ss << ";\n\t";
+
+ ss<< "int buffer_tmp006_len = ";
+ ss<< tmpCurDVR6->GetArrayLength();
+ ss << ";\n\t";
+
+ ss<<"if(gid0>=buffer_tmp000_len || isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"tmp000 = 0;\n\telse \n\t\t";
+ ss<<"tmp000 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+
+ ss<<"if(gid0>=buffer_tmp001_len || isnan(";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"tmp001 = 0;\n\telse \n\t\t";
+ ss<<"tmp001 = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+
+ ss<<"if(gid0>=buffer_tmp002_len || isnan(";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"tmp002 = 0;\n\telse \n\t\t";
+ ss<<"tmp002 = ";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+
+ ss<<"if(gid0>=buffer_tmp003_len || isnan(";
+ ss << vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"tmp003 = 0;\n\telse \n\t\t";
+ ss<<"tmp003 = ";
+ ss << vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+
+ ss<<"if(gid0>=buffer_tmp004_len || isnan(";
+ ss << vSubArguments[4]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"tmp004 = 0;\n\telse \n\t\t";
+ ss<<"tmp004 = ";
+ ss << vSubArguments[4]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+
+ ss<<"if(gid0>=buffer_tmp005_len || isnan(";
+ ss << vSubArguments[5]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"tmp005 = 0;\n\telse \n\t\t";
+ ss<<"tmp005 = ";
+ ss << vSubArguments[5]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+
+ ss<<"if(gid0>=buffer_tmp006_len || isnan(";
+ ss << vSubArguments[6]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"tmp006 = 0;\n\telse \n\t\t";
+ ss<<"tmp006 = ";
+ ss << vSubArguments[6]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+
+ ss << "tmp = getYield_(";
+ ss << "GetNullDate(),tmp000,tmp001,tmp002,tmp003,tmp004,tmp005,tmp006);\n\t ";
+ ss << "return tmp;\n";
+ ss << "}";
+}
+
+void OpSLN::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " double tmp = 0;\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double cost;\n";
+ ss << " double salvage;\n";
+ ss << " double life;\n";
+
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR0=
+ static_cast<const formula::SingleVectorRefToken *>(tmpCur0);
+ FormulaToken *tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR1=
+ static_cast<const formula::SingleVectorRefToken *>(tmpCur1);
+ FormulaToken *tmpCur2 = vSubArguments[2]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR2=
+ static_cast<const formula::SingleVectorRefToken *>(tmpCur2);
+ ss<< " int buffer_cost_len = ";
+ ss<< tmpCurDVR0->GetArrayLength();
+ ss << ";\n";
+ ss<< " int buffer_salvage_len = ";
+ ss<< tmpCurDVR1->GetArrayLength();
+ ss << ";\n";
+ ss<< " int buffer_life_len = ";
+ ss<< tmpCurDVR2->GetArrayLength();
+ ss << ";\n";
+ ss<<" if(gid0>=buffer_cost_len || isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss<<"))\n";
+ ss<<" cost = 0;\n\telse \n";
+ ss<<" cost = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss<<";\n";
+ ss<<" if(gid0>=buffer_salvage_len || isnan(";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss<<"))\n";
+ ss<<" salvage = 0;\n\telse \n";
+ ss<<" salvage = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss<<";\n";
+ ss<<" if(gid0>=buffer_life_len || isnan(";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss<<"))\n";
+ ss<<" life = 0;\n\telse \n";
+ ss<<" life = ";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss<<";\n";
+ ss << " tmp = (cost-salvage)*pow(life,-1);\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+
+ void OpYieldmat::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(GetYearFrac_newDecl);decls.insert(GetNullDateDecl);
+ decls.insert(DateToDaysDecl);decls.insert(DaysToDate_newDecl);
+ decls.insert(DaysInMonthDecl);decls.insert(IsLeapYearDecl);
+ decls.insert(GetYieldmatDecl);
+
+ funs.insert(GetYearFrac_new);funs.insert(GetNullDate);
+ funs.insert(DateToDays);funs.insert(DaysToDate_new);
+ funs.insert(DaysInMonth);funs.insert(IsLeapYear);
+ funs.insert(GetYieldmat);
+}
+
+void OpYieldmat::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n\t";
+ ss << "double tmp = 0;\n\t";
+ ss << "int gid0 = get_global_id(0);\n\t";
+ ss << "double tmp000;\n\t";
+ ss << "double tmp001;\n\t";
+ ss << "double tmp002;\n\t";
+ ss << "double tmp003;\n\t";
+ ss << "double tmp004;\n\t";
+ ss << "double tmp005;\n\t";
+
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR0= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+
+ FormulaToken *tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur1);
+
+ FormulaToken *tmpCur2 = vSubArguments[2]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR2= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur2);
+
+ FormulaToken *tmpCur3 = vSubArguments[3]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR3= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur3);
+
+ FormulaToken *tmpCur4 = vSubArguments[4]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR4= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur4);
+
+ FormulaToken *tmpCur5 = vSubArguments[5]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR5= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur5);
+
+ ss<< "int buffer_tmp000_len = ";
+ ss<< tmpCurDVR0->GetArrayLength();
+ ss << ";\n\t";
+
+ ss<< "int buffer_tmp001_len = ";
+ ss<< tmpCurDVR1->GetArrayLength();
+ ss << ";\n\t";
+
+ ss<< "int buffer_tmp002_len = ";
+ ss<< tmpCurDVR2->GetArrayLength();
+ ss << ";\n\t";
+
+ ss<< "int buffer_tmp003_len = ";
+ ss<< tmpCurDVR3->GetArrayLength();
+ ss << ";\n\t";
+
+ ss<< "int buffer_tmp004_len = ";
+ ss<< tmpCurDVR4->GetArrayLength();
+ ss << ";\n\t";
+
+ ss<< "int buffer_tmp005_len = ";
+ ss<< tmpCurDVR5->GetArrayLength();
+ ss << ";\n\t";
+
+ ss<<"if(gid0>=buffer_tmp000_len || isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"tmp000 = 0;\n\telse \n\t\t";
+ ss<<"tmp000 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+
+ ss<<"if(gid0>=buffer_tmp001_len || isnan(";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"tmp001 = 0;\n\telse \n\t\t";
+ ss<<"tmp001 = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+
+ ss<<"if(gid0>=buffer_tmp002_len || isnan(";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"tmp002 = 0;\n\telse \n\t\t";
+ ss<<"tmp002 = ";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+
+ ss<<"if(gid0>=buffer_tmp003_len || isnan(";
+ ss << vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"tmp003 = 0;\n\telse \n\t\t";
+ ss<<"tmp003 = ";
+ ss << vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+
+ ss<<"if(gid0>=buffer_tmp004_len || isnan(";
+ ss << vSubArguments[4]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"tmp004 = 0;\n\telse \n\t\t";
+ ss<<"tmp004 = ";
+ ss << vSubArguments[4]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+
+ ss<<"if(gid0>=buffer_tmp005_len || isnan(";
+ ss << vSubArguments[5]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"tmp005 = 0;\n\telse \n\t\t";
+ ss<<"tmp005 = ";
+ ss << vSubArguments[5]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+
+ ss << "tmp = GetYieldmat(";
+ ss<<"GetNullDate(),tmp000,tmp001,tmp002,tmp003,tmp004,tmp005);\n\t";
+ ss << "return tmp;\n";
+ ss << "}";
+}
+
+void OpPMT::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ", ";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss<<") {\n";
+ ss<<" double tmp = 0;\n";
+ ss<<" double temp=0.0;\n";
+ ss<<" int gid0 = get_global_id(0);\n";
+ ss<<" double tmp0=0,tmp1=0,tmp2=0;\n";
+ ss<<" double tmp3=0,tmp4=0;\n";
+ ss <<"\n ";
+ //while (i-- > 1)
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << "if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << "{\n";
+ }
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss <<" temp="<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ ss <<" if (isnan(temp))\n";
+ ss <<" tmp"<<i<<"= 0;\n";
+ ss <<" else\n";
+ ss <<" tmp"<<i<<"=temp;\n";
+ ss <<" }\n";
+ }
+ else
+ {
+ ss <<" tmp"<<i<<"="<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ }
+ }
+ ss<<" if(tmp0==0.0)\n";
+ ss<<" return -(tmp2+tmp3)/tmp1;\n";
+ ss<<" tmp-=tmp3;\n";
+ ss<<" tmp=tmp-tmp2*pow(1.0+tmp0,tmp1);\n";
+ ss<<" tmp=tmp*pow(( (1.0+tmp0*tmp4)* ";
+ ss<<"( (pow(1.0+tmp0,tmp1)-1.0)/tmp0)),-1);\n";
+ ss<<" return tmp;\n";
+ ss<<"}";
+}
+
+void OpNPV::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ", ";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " double tmp = 0.0;\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " int nCount = 1;\n";
+ ss << " double arg0=";
+ ss <<vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ //while (i-- > 1)
+ for (size_t i = 1; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed()) {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++){\n";
+ } else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed()) {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+"<< nCurWindowSize << "; i++){\n";
+ } else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed()){
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < "<< nCurWindowSize << "; i++){\n";
+ }
+ else {
+ ss << "0; i < "<< nCurWindowSize << "; i++){\n";
+ }
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << "{\n";
+ }
+ else
+ {
+ ss << "nCount += 1;\n";
+ }
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " double temp=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " double temp1=1.0;";
+ ss << " if (isnan(temp)){\n";
+ ss << " tmp += 0;}\n";
+ ss << " else{\n";
+ ss << " for(int i=1;i<nCount;i+=2)\n";
+ ss << " temp1*=pow(1.0f+ arg0 ,2);\n";
+ ss << " if(nCount%2)\n";
+ ss << " temp1*=1.0f+ arg0;\n";
+ ss << " tmp +=temp/ temp1;\n";
+ ss << " nCount += 1;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " double temp=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " double temp1=1.0;";
+ ss << " for(int i=1;i<nCount;i+=2)";
+ ss << " temp1*=pow(1.0f+ arg0 ,2);\n";
+ ss << " if(nCount%2)";
+ ss << " temp1*=1.0f+ arg0;\n";
+ ss << " tmp +=temp/ temp1;\n";
+ ss << " nCount += 1;\n";
+ }
+ }
+ ss << " return tmp;\n";
+ ss << "}";
+}
+
+ void OpPrice::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+ {
+ decls.insert(getPrice_new_Decl);
+ decls.insert(IsLeapYearDecl);decls.insert(DaysInMonthDecl);
+ decls.insert(DaysToDateDecl);
+ decls.insert(DateToDaysDecl);
+ decls.insert(ScaDateDecl);
+ decls.insert(setDayDecl);decls.insert(checklessthanDecl);
+ decls.insert(addMonthsDecl);decls.insert(lcl_Getcoupnum_newDecl);
+ decls.insert(coupnum_newDecl);
+ decls.insert(DateToDays_newDecl);
+ decls.insert(getDaysInMonthRangeDecl);
+ decls.insert(GetDaysInYearsDecl); decls.insert(GetDaysInYearDecl);
+ decls.insert(getDaysInYearRangeDecl); decls.insert(getDiffDecl);
+ decls.insert(coupdaybs_newDecl);
+ decls.insert(lcl_Getcoupdays_newDecl);
+ decls.insert(lcl_Getcoupdaybs_newDecl);
+ decls.insert(coupdays_newDecl);
+ decls.insert(coupdaysnc_newDecl);
+ funs.insert(IsLeapYear);funs.insert(DaysInMonth_new);
+ funs.insert(DaysToDate);funs.insert(DateToDays_new);
+ funs.insert(DateToDays);
+ funs.insert(ScaDate);
+ funs.insert(addMonths);funs.insert(getDaysInMonthRange);
+ funs.insert(GetDaysInYears);funs.insert(GetDaysInYear);
+ funs.insert(getDaysInYearRange);funs.insert(getDiff);
+ funs.insert(setDay);funs.insert(checklessthan);
+ funs.insert(lcl_Getcoupdaybs_new);
+ funs.insert(coupdaybs_new);
+ funs.insert(lcl_Getcoupdays_new);
+ funs.insert(coupdaysnc_new);
+ funs.insert(coupdays_new);
+ funs.insert(setDay);funs.insert(checklessthan);
+ funs.insert(lcl_Getcoupnum_new);
+ funs.insert(coupnum_new);funs.insert(getPrice_new);
+ }
+void OpPrice::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ", ";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss<<") {\n";
+ ss<<" double tmp = 0;\n";
+ ss<<" int gid0 = get_global_id(0);\n";
+ ss<<" double tmp0=0;\n";
+ ss<<" double tmp1=0;\n";
+ ss<<" double tmp2=0;\n";
+ ss<<" double tmp3=0;\n";
+ ss<<" double tmp4=0,tmp5=0;\n";
+ ss<<" double tmp6=0;\n";
+ ss<<"\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << "{\n";
+ }
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " tmp"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " tmp"<<i<<"=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n }\n";
+ }
+ else
+ {
+ ss << " tmp"<<i<<"=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ }
+ }
+ ss << " if(tmp4*tmp5 == 0) return NAN;\n";
+ ss << " tmp = getPrice_(tmp0,tmp1,tmp2,tmp3,tmp4,tmp5,tmp6);\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+
+void OpOddlprice::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(GetOddlpriceDecl);decls.insert(GetDiffDateDecl);
+ decls.insert(GetYearDiffDecl);decls.insert(IsLeapYearDecl);
+ decls.insert(GetNullDateDecl);decls.insert(DateToDaysDecl);
+ decls.insert(DaysToDateDecl);decls.insert(DaysInMonthDecl);
+ decls.insert(GetYearFracDecl);
+ funs.insert(GetOddlprice);funs.insert(GetDiffDate);
+ funs.insert(GetYearDiff);funs.insert(IsLeapYear);
+ funs.insert(GetNullDate);funs.insert(DaysInMonth);
+ funs.insert(DaysToDate);funs.insert(DateToDays);
+ funs.insert(GetYearFrac);
+}
+void OpOddlprice::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ", ";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss <<") {\n";
+ ss <<" double tmp = 0;\n";
+ ss <<" int gid0 = get_global_id(0);\n";
+ ss <<" double tmp0=0;\n";
+ ss <<" double tmp1=0;\n";
+ ss <<" double tmp2=0;\n";
+ ss <<" double tmp3=0;\n";
+ ss <<" double tmp4=0;\n";
+ ss <<" double tmp5=0;\n";
+ ss <<" double tmp6=0;\n";
+ ss <<" double tmp7=0;\n";
+ ss <<" \n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed()) {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++){\n";
+ } else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed()) {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+"<< nCurWindowSize << "; i++){\n";
+ } else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed()){
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < "<< nCurWindowSize << "; i++){\n";
+ }
+ else {
+ ss << "0; i < "<< nCurWindowSize << "; i++){\n";
+ }
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << "{\n";
+ }
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " tmp"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " tmp"<<i<<"=";
+ ss <<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss <<" }\n";
+ }
+ else
+ {
+ ss << " tmp"<<i<<"=";
+ ss <<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ }
+ }
+ ss <<" int nNullDate = GetNullDate();\n";
+ ss <<" tmp = GetOddlprice(nNullDate,tmp0,tmp1,";
+ ss <<"tmp2,tmp3,tmp4,tmp5,tmp6,tmp7);\n";
+ ss <<" return tmp;\n";
+ ss <<"}";
+}
+void OpOddlyield::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(GetDiffDateDecl);decls.insert(DaysToDateDecl);
+ decls.insert(GetYearDiffDecl);decls.insert(IsLeapYearDecl);
+ decls.insert(GetNullDateDecl);decls.insert(DateToDaysDecl);
+ decls.insert(DaysInMonthDecl);
+ decls.insert(GetYearFracDecl);decls.insert(GetOddlyieldDecl);
+ funs.insert(GetDiffDate);funs.insert(DaysToDate);
+ funs.insert(GetYearDiff);funs.insert(IsLeapYear);
+ funs.insert(GetNullDate);funs.insert(DaysInMonth);
+ funs.insert(DateToDays);
+ funs.insert(GetYearFrac);funs.insert(GetOddlyield);
+}
+void OpOddlyield::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ", ";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss <<") {\n";
+ ss <<" double tmp = 0;\n";
+ ss <<" int gid0 = get_global_id(0);\n";
+ ss <<" double tmp0=0;\n";
+ ss <<" double tmp1=0;\n";
+ ss <<" double tmp2=0;\n";
+ ss <<" double tmp3=0;\n";
+ ss <<" double tmp4=0;\n";
+ ss <<" double tmp5=0;\n";
+ ss <<" double tmp6=0;\n";
+ ss <<" double tmp7=0;\n";
+ ss <<" \n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed()) {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++){\n";
+ } else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed()) {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+"<< nCurWindowSize << "; i++){\n";
+ } else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed()){
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < "<< nCurWindowSize << "; i++){\n";
+ }
+ else {
+ ss << "0; i < "<< nCurWindowSize << "; i++){\n";
+ }
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << "{\n";
+ }
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " tmp"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " tmp"<<i<<"=";
+ ss <<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss <<" }\n";
+ }
+ else
+ {
+ ss << " tmp"<<i<<"=";
+ ss <<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ }
+ }
+ ss <<" int nNullDate = GetNullDate();\n";
+ ss <<" tmp = GetOddlyield(nNullDate,tmp0,tmp1";
+ ss <<",tmp2,tmp3,tmp4,tmp5,tmp6,tmp7);\n";
+ ss <<" return tmp;\n";
+ ss <<"}";
+}
+void OpPriceDisc::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(GetYearDiffDecl);decls.insert(getDiffDecl);
+ decls.insert(getDaysInYearRangeDecl);decls.insert(GetDaysInYearDecl);
+ decls.insert(GetDaysInYearsDecl);decls.insert(getDaysInMonthRangeDecl);
+ decls.insert(addMonthsDecl);decls.insert(ScaDateDecl);
+ decls.insert(GetNullDateDecl);decls.insert(DateToDaysDecl);
+ decls.insert(DaysToDateDecl);decls.insert(DaysInMonthDecl);
+ decls.insert(IsLeapYearDecl);decls.insert(GetDiffDateDecl);
+ funs.insert(GetYearDiff);funs.insert(getDiff);
+ funs.insert(getDaysInYearRange);funs.insert(GetDaysInYear);
+ funs.insert(GetDaysInYears);funs.insert(getDaysInMonthRange);
+ funs.insert(addMonths);funs.insert(ScaDate);
+ funs.insert(GetNullDate);funs.insert(DateToDays);
+ funs.insert(DaysToDate);funs.insert(DaysInMonth);
+ funs.insert(IsLeapYear);funs.insert(GetDiffDate);
+}
+void OpPriceDisc::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ", ";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " double tmp = 0;\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss<<" double tmp0=0;\n";
+ ss<<" double tmp1=0;\n";
+ ss<<" double tmp2=0;\n";
+ ss<<" double tmp3=0;\n";
+ ss<<" double tmp4=0;\n";
+ ss <<" \n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << "{\n";
+ }
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " tmp"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " tmp"<<i<<"=";
+ ss <<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss <<" }\n";
+ }
+ else
+ {
+ ss << " tmp"<<i<<"=";
+ ss <<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ }
+ }
+ ss <<" int nNullDate = GetNullDate();\n";
+ ss <<" tmp=tmp3* ( 1.0 -tmp2*GetYearDiff( nNullDate, ";
+ ss <<"tmp0,tmp1,tmp4));\n";
+ ss <<" return tmp;\n";
+ ss <<"}";
+}
+void OpNper::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+ {
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ", ";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " double tmp = 0;\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss <<" double tmp0=0;\n";
+ ss <<" double tmp1=0;\n";
+ ss <<" double tmp2=0;\n";
+ ss <<" double tmp3=0;\n";
+ ss <<" double tmp4=0;\n";
+
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << "{\n";
+ }
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " tmp"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " tmp"<<i<<"=";
+ ss <<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss <<" }\n";
+ }
+ else
+ {
+ ss << " tmp"<<i<<"=";
+ ss <<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ }
+ }
+ ss <<" if (tmp0 == 0.0)\n";
+ ss <<" tmp=(-1*(tmp2 + tmp3)/tmp1);\n";
+ ss <<" else if (tmp4 > 0.0)\n";
+ ss <<" tmp=log(-1*(tmp0*tmp3-tmp1*(1.0+tmp0))*";
+ ss <<"pow((tmp0*tmp2+tmp1*(1.0+tmp0)),-1))/log(1.0+tmp0);\n";
+ ss <<" else\n";
+ ss <<" tmp=log(-1*(tmp0*tmp3-tmp1)*pow(tmp0*tmp2+tmp1,-1))";
+ ss <<"/log(1.0+tmp0);\n";
+ ss <<" return tmp;\n";
+ ss <<"}";
+ }
+
+void OpPPMT::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(GetFVDecl);
+ funs.insert(GetFV);
+}
+
+void OpPPMT::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ", ";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss<<") {\n";
+ ss<<" double tmp = 0;\n";
+ ss<<" int gid0 = get_global_id(0);\n";
+ ss<<" double arg=0;\n";
+ ss<<" double tmp0=0;\n";
+ ss<<" double tmp1=0;\n";
+ ss<<" double tmp2=0;\n";
+ ss<<" double tmp3=0;\n";
+ ss<<" double tmp4=0,tmp5=0;\n";
+ ss <<"\n ";
+ //while (i-- > 1)
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << "if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << "{\n";
+ }
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " arg=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (isnan(arg))\n";
+ ss << " tmp"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " tmp"<<i<<"=arg;\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss<<" tmp"<<i<<"="<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss<<";\n";
+ }
+ }
+ ss<<" double pmt=0 ;\n";
+ ss<<" if(tmp0==0.0)\n";
+ ss<<" return -(tmp3+tmp4)/tmp2;\n";
+ ss<<" pmt=pmt-tmp4-tmp3*pow(1.0+tmp0,tmp2);\n";
+ ss<<" pmt=pmt*pow(( (1.0+tmp0*tmp5)* ";
+ ss<<"( (pow(1.0+tmp0,tmp2)-1.0)/tmp0)),-1);\n";
+ ss<<" double temp = pow( 1+tmp0,tmp1-2);\n";
+ ss<<" double re;\n";
+ ss<<" if(tmp1==1.0){\n";
+ ss<<" if(tmp5>0.0)\n";
+ ss<<" re=0.0;\n";
+ ss<<" else\n";
+ ss<<" re=-tmp3;\n";
+ ss<<" }\n";
+ ss<<" else\n";
+ ss<<" {\n";
+ ss<<" if(tmp5>0.0)\n ";
+ ss<<" re=GetFV(tmp0, tmp1-2.0, pmt, tmp3, 1.0) - pmt;\n";
+ ss<<" else\n";
+ ss<<" re=GetFV(tmp0, tmp1-1.0, pmt, tmp3, 0.0);\n";
+ ss<<" }\n ";
+ ss<<" re = re * tmp0;\n";
+ ss<<" tmp = pmt - re;\n";
+ ss<<" return tmp;\n";
+ ss<<"}";
+}
+
+void OpCoupdaybs::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(IsLeapYearDecl); decls.insert(DaysInMonthDecl);
+ decls.insert(DaysToDateDecl); decls.insert(DateToDays_newDecl);
+ decls.insert(GetNullDate_newDecl); decls.insert(ScaDateDecl);
+ decls.insert(addMonthsDecl); decls.insert(getDaysInMonthRangeDecl);
+ decls.insert(GetDaysInYearsDecl);
+ decls.insert(getDaysInYearRangeDecl); decls.insert(getDiffDecl);
+ decls.insert(setDayDecl);decls.insert(checklessthanDecl);
+ decls.insert(lcl_Getcoupdaybs_newDecl);
+ decls.insert(coupdaybs_newDecl);
+ funs.insert(IsLeapYear);funs.insert(DaysInMonth);
+ funs.insert(DaysToDate);funs.insert(DateToDays_new);
+ funs.insert(GetNullDate_new);funs.insert(ScaDate);
+ funs.insert(addMonths);funs.insert(getDaysInMonthRange);
+ funs.insert(GetDaysInYears);
+ funs.insert(getDaysInYearRange);funs.insert(getDiff);
+ funs.insert(setDay);funs.insert(checklessthan);
+ funs.insert(lcl_Getcoupdaybs_new);
+ funs.insert(coupdaybs_new);
+}
+void OpCoupdaybs::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &
+vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " double tmp = 0;\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " int nSettle,nMat,nFreq,nBase;\n";
+ FormulaToken* tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ FormulaToken* tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ FormulaToken* tmpCur2 = vSubArguments[2]->GetFormulaToken();
+ FormulaToken* tmpCur3 = vSubArguments[3]->GetFormulaToken();
+ if(tmpCur0->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR0= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+ ss <<" if(isnan("<<vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR0->GetArrayLength()<<"))\n";
+ ss <<" nSettle = 0;\n else\n";
+ }
+ ss <<" nSettle=(int)";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur1->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur1);
+ ss <<" if(isnan("<<vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR1->GetArrayLength()<<"))\n";
+ ss <<" nMat = 0;\n else\n";
+ }
+ ss <<" nMat=(int)";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur2->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR2= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur2);
+ ss <<" if(isnan("<<vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR2->GetArrayLength()<<"))\n";
+ ss <<" nFreq = 0;\n else\n";
+ }
+ ss << " nFreq=(int)";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur3->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR3= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur3);
+ ss <<" if(isnan(" <<vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR3->GetArrayLength()<<"))\n";
+ ss <<" nBase = 0;\n else\n";
+ }
+ ss << " nBase=(int)";
+ ss << vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss <<" tmp = coupdaybs_new(nSettle,nMat,nFreq,nBase);\n";
+ ss <<" return tmp;\n";
+ ss <<"}";
+}
+
+void OpCoupdays::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(IsLeapYearDecl); decls.insert(DaysInMonthDecl);
+ decls.insert(DaysToDateDecl); decls.insert(DateToDays_newDecl);
+ decls.insert(GetNullDate_newDecl); decls.insert(ScaDateDecl);
+ decls.insert(addMonthsDecl); decls.insert(getDaysInMonthRangeDecl);
+ decls.insert(GetDaysInYearsDecl); decls.insert(GetDaysInYearDecl);
+ decls.insert(getDaysInYearRangeDecl); decls.insert(getDiffDecl);
+ decls.insert(setDayDecl);decls.insert(checklessthanDecl);
+ decls.insert(lcl_Getcoupdays_newDecl);
+ decls.insert(coupdays_newDecl);
+ funs.insert(IsLeapYear);funs.insert(DaysInMonth);
+ funs.insert(DaysToDate);funs.insert(DateToDays_new);
+ funs.insert(GetNullDate_new);funs.insert(ScaDate);
+ funs.insert(addMonths);funs.insert(getDaysInMonthRange);
+ funs.insert(GetDaysInYears);funs.insert(GetDaysInYear);
+ funs.insert(getDaysInYearRange);funs.insert(getDiff);
+ funs.insert(lcl_Getcoupdays_new);
+ funs.insert(setDay);funs.insert(checklessthan);
+ funs.insert(coupdays_new);
+}
+void OpCoupdays::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &
+vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " double tmp = 0;\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " int nSettle,nMat,nFreq,nBase;\n";
+ FormulaToken* tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ FormulaToken* tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ FormulaToken* tmpCur2 = vSubArguments[2]->GetFormulaToken();
+ FormulaToken* tmpCur3 = vSubArguments[3]->GetFormulaToken();
+ if(tmpCur0->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR0= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+ ss <<" if(isnan("<<vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR0->GetArrayLength()<<"))\n";
+ ss <<" nSettle = 0;\n else\n";
+ }
+ ss <<" nSettle=(int)";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur1->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur1);
+ ss <<" if(isnan("<<vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR1->GetArrayLength()<<"))\n";
+ ss <<" nMat = 0;\n else\n";
+ }
+ ss <<" nMat=(int)";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur2->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR2= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur2);
+ ss <<" if(isnan("<<vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR2->GetArrayLength()<<"))\n";
+ ss <<" nFreq = 0;\n else\n";
+ }
+ ss << " nFreq=(int)";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur3->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR3= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur3);
+ ss <<" if(isnan(" <<vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR3->GetArrayLength()<<"))\n";
+ ss <<" nBase = 0;\n else\n";
+ }
+ ss << " nBase=(int)";
+ ss << vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss <<" tmp = coupdays_new(nSettle,nMat,nFreq,nBase);\n";
+ ss <<" return tmp;\n";
+ ss << "}";
+}
+void OpCouppcd::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(IsLeapYearDecl); decls.insert(DaysInMonthDecl);
+ decls.insert(DaysToDateDecl); decls.insert(DateToDaysDecl);
+ decls.insert(GetNullDateDecl);
+ decls.insert(ScaDateDecl);
+ decls.insert(addMonthsDecl);
+ decls.insert(setDayDecl);decls.insert(checklessthanDecl);
+ decls.insert(lcl_GetCouppcdDecl);
+ funs.insert(IsLeapYear);funs.insert(DaysInMonth);
+ funs.insert(DaysToDate);funs.insert(DateToDays);
+ funs.insert(GetNullDate);
+ funs.insert(ScaDate);
+ funs.insert(addMonths);
+ funs.insert(setDay);funs.insert(checklessthan);
+ funs.insert(lcl_GetCouppcd);
+}
+void OpCouppcd::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName,
+ SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " double tmp = 0;\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " int nSettle,nMat,nFreq,nBase;\n";
+ FormulaToken* tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ FormulaToken* tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ FormulaToken* tmpCur2 = vSubArguments[2]->GetFormulaToken();
+ FormulaToken* tmpCur3 = vSubArguments[3]->GetFormulaToken();
+ if(tmpCur0->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR0= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+ ss <<" if(isnan("<<vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR0->GetArrayLength()<<"))\n";
+ ss <<" nSettle = 0;\n else\n";
+ }
+ ss <<" nSettle=(int)";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur1->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur1);
+ ss <<" if(isnan("<<vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR1->GetArrayLength()<<"))\n";
+ ss <<" nMat = 0;\n else\n";
+ }
+ ss <<" nMat=(int)";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur2->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR2= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur2);
+ ss <<" if(isnan("<<vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR2->GetArrayLength()<<"))\n";
+ ss <<" nFreq = 0;\n else\n";
+ }
+ ss << " nFreq=(int)";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur3->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR3= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur3);
+ ss <<" if(isnan(" <<vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR3->GetArrayLength()<<"))\n";
+ ss <<" nBase = 0;\n else\n";
+ }
+ ss << " nBase=(int)";
+ ss << vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss <<" int nNullDate=693594;\n";
+ ss <<" tmp = lcl_GetCouppcd(nNullDate,nSettle,nMat,nFreq,nBase);\n";
+ ss <<" return tmp;\n";
+ ss <<"}";
+}
+void OpCoupncd::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(IsLeapYearDecl); decls.insert(DaysInMonthDecl);
+ decls.insert(DaysToDateDecl); decls.insert(DateToDaysDecl);
+ decls.insert(GetNullDateDecl);
+ decls.insert(ScaDateDecl);
+ decls.insert(addMonthsDecl);
+ decls.insert(setDayDecl);decls.insert(checklessthanDecl);
+ decls.insert(lcl_GetCoupncdDecl);
+ funs.insert(IsLeapYear);funs.insert(DaysInMonth);
+ funs.insert(DaysToDate);funs.insert(DateToDays);
+ funs.insert(GetNullDate);
+ funs.insert(ScaDate);
+ funs.insert(addMonths);
+ funs.insert(setDay);funs.insert(checklessthan);
+ funs.insert(lcl_GetCoupncd);
+}
+void OpCoupncd::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &
+vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " double tmp = 0;\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " int nSettle,nMat,nFreq,nBase;\n";
+ FormulaToken* tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ FormulaToken* tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ FormulaToken* tmpCur2 = vSubArguments[2]->GetFormulaToken();
+ FormulaToken* tmpCur3 = vSubArguments[3]->GetFormulaToken();
+ if(tmpCur0->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR0= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+ ss <<" if(isnan("<<vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR0->GetArrayLength()<<"))\n";
+ ss <<" nSettle = 0;\n else\n";
+ }
+ ss <<" nSettle=(int)";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur1->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur1);
+ ss <<" if(isnan("<<vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR1->GetArrayLength()<<"))\n";
+ ss <<" nMat = 0;\n else\n";
+ }
+ ss <<" nMat=(int)";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur2->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR2= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur2);
+ ss <<" if(isnan("<<vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR2->GetArrayLength()<<"))\n";
+ ss <<" nFreq = 0;\n else\n";
+ }
+ ss << " nFreq=(int)";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur3->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR3= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur3);
+ ss <<" if(isnan(" <<vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR3->GetArrayLength()<<"))\n";
+ ss <<" nBase = 0;\n else\n";
+ }
+ ss << " nBase=(int)";
+ ss << vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss <<" int nNullDate=693594;\n";
+ ss <<" tmp = lcl_GetCoupncd(nNullDate,nSettle,nMat,nFreq,nBase);\n";
+ ss <<" return tmp;\n";
+ ss <<"}";
+}
+
+void OpCoupdaysnc::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(IsLeapYearDecl); decls.insert(DaysInMonth_newDecl);
+ decls.insert(DaysToDateDecl); decls.insert(DateToDaysDecl);
+ decls.insert(DateToDays_newDecl);
+ decls.insert(ScaDateDecl);
+ decls.insert(addMonthsDecl); decls.insert(getDaysInMonthRangeDecl);
+ decls.insert(GetDaysInYearsDecl); decls.insert(GetDaysInYearDecl);
+ decls.insert(getDaysInYearRangeDecl); decls.insert(getDiffDecl);
+ decls.insert(setDayDecl);decls.insert(checklessthanDecl);
+ decls.insert(coupdaybs_newDecl);
+ decls.insert(lcl_Getcoupdays_newDecl);
+ decls.insert(lcl_Getcoupdaybs_newDecl);
+ decls.insert(coupdays_newDecl);
+ decls.insert(coupdaysnc_newDecl);
+ funs.insert(IsLeapYear);funs.insert(DaysInMonth_new);
+ funs.insert(DaysToDate);funs.insert(DateToDays_new);
+ funs.insert(DateToDays);
+ funs.insert(ScaDate);
+ funs.insert(addMonths);funs.insert(getDaysInMonthRange);
+ funs.insert(GetDaysInYears);funs.insert(GetDaysInYear);
+ funs.insert(getDaysInYearRange);funs.insert(getDiff);
+ funs.insert(setDay);funs.insert(checklessthan);
+ funs.insert(lcl_Getcoupdaybs_new);
+ funs.insert(coupdaybs_new);
+ funs.insert(lcl_Getcoupdays_new);
+ funs.insert(coupdaysnc_new);
+ funs.insert(coupdays_new);
+}
+void OpCoupdaysnc::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName,
+ SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " double tmp = 0;\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " int nSettle,nMat,nFreq,nBase;\n";
+ FormulaToken* tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ FormulaToken* tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ FormulaToken* tmpCur2 = vSubArguments[2]->GetFormulaToken();
+ FormulaToken* tmpCur3 = vSubArguments[3]->GetFormulaToken();
+ if(tmpCur0->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR0= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+ ss <<" if(isnan("<<vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR0->GetArrayLength()<<"))\n";
+ ss <<" nSettle = 0;\n else\n";
+ }
+ ss <<" nSettle=(int)";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur1->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur1);
+ ss <<" if(isnan("<<vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR1->GetArrayLength()<<"))\n";
+ ss <<" nMat = 0;\n else\n";
+ }
+ ss <<" nMat=(int)";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur2->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR2= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur2);
+ ss <<" if(isnan("<<vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR2->GetArrayLength()<<"))\n";
+ ss <<" nFreq = 0;\n else\n";
+ }
+ ss << " nFreq=(int)";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur3->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR3= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur3);
+ ss <<" if(isnan(" <<vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR3->GetArrayLength()<<"))\n";
+ ss <<" nBase = 0;\n else\n";
+ }
+ ss << " nBase=(int)";
+ ss << vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss <<" tmp = coupdaysnc_new(nSettle,nMat,nFreq,nBase);\n";
+ ss <<" return tmp;\n";
+ ss << "}";
+}
+
+void OpCoupnum::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(IsLeapYearDecl);decls.insert(DaysInMonthDecl);
+ decls.insert(DaysToDateDecl);
+ decls.insert(DateToDaysDecl);
+ decls.insert(ScaDateDecl);
+ decls.insert(setDayDecl);decls.insert(checklessthanDecl);
+ decls.insert(addMonthsDecl);decls.insert(lcl_Getcoupnum_newDecl);
+ decls.insert(coupnum_newDecl);
+ funs.insert(IsLeapYear);funs.insert(DaysInMonth_new);
+ funs.insert(DaysToDate);
+ funs.insert(DateToDays);
+ funs.insert(ScaDate);
+ funs.insert(setDay);funs.insert(checklessthan);
+ funs.insert(addMonths);funs.insert(lcl_Getcoupnum_new);
+ funs.insert(coupnum_new);
+}
+void OpCoupnum::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " double tmp = 0;\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " int nSettle,nMat,nFreq,nBase;\n";
+ FormulaToken* tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ FormulaToken* tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ FormulaToken* tmpCur2 = vSubArguments[2]->GetFormulaToken();
+ FormulaToken* tmpCur3 = vSubArguments[3]->GetFormulaToken();
+ if(tmpCur0->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR0= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+ ss <<" if(isnan("<<vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR0->GetArrayLength()<<"))\n";
+ ss <<" nSettle = 0;\n else\n";
+ }
+ ss <<" nSettle=(int)";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur1->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur1);
+ ss <<" if(isnan("<<vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR1->GetArrayLength()<<"))\n";
+ ss <<" nMat = 0;\n else\n";
+ }
+ ss <<" nMat=(int)";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur2->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR2= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur2);
+ ss <<" if(isnan("<<vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR2->GetArrayLength()<<"))\n";
+ ss <<" nFreq = 0;\n else\n";
+ }
+ ss << " nFreq=(int)";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur3->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR3= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur3);
+ ss <<" if(isnan(" <<vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR3->GetArrayLength()<<"))\n";
+ ss <<" nBase = 0;\n else\n";
+ }
+ ss << " nBase=(int)";
+ ss << vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss <<" tmp = coupnum_new(nSettle,nMat,nFreq,nBase);\n";
+ ss <<" return tmp;\n";
+ ss << "}";
+}
+void OpAmordegrc::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(nCorrValDecl); decls.insert(RoundDecl);
+ decls.insert(IsLeapYearDecl);decls.insert(DaysInMonthDecl);
+ decls.insert(DaysToDateDecl); decls.insert(DateToDaysDecl);
+ decls.insert(GetNullDateDecl); decls.insert(GetYearFracDecl);
+ funs.insert(Round);
+ funs.insert(IsLeapYear);funs.insert(DaysInMonth);
+ funs.insert(DaysToDate);funs.insert(DateToDays);
+ funs.insert(GetNullDate);funs.insert(GetYearFrac);
+}
+void OpAmordegrc::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n ";
+ ss << "int gid0 = get_global_id(0);\n";
+ ss << " double tmp = " << GetBottom() <<";\n";
+ ss << " double fCost,fRestVal,fPer,fRate;\n";
+ ss << " int nDate,nFirstPer,nBase;\n";
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ FormulaToken *tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ FormulaToken *tmpCur2 = vSubArguments[2]->GetFormulaToken();
+ FormulaToken *tmpCur3 = vSubArguments[3]->GetFormulaToken();
+ FormulaToken *tmpCur4 = vSubArguments[4]->GetFormulaToken();
+ FormulaToken *tmpCur5 = vSubArguments[5]->GetFormulaToken();
+ FormulaToken *tmpCur6 = vSubArguments.size() < 7 ? nullptr : vSubArguments[6]->GetFormulaToken();
+ if(tmpCur0->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR0= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+ ss <<" if(isnan("<<vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR0->GetArrayLength()<<"))\n";
+ ss <<" fCost = 0;\n else\n";
+ }
+ ss << " fCost=";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur1->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur1);
+ ss <<" if(isnan("<<vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR1->GetArrayLength()<<"))\n";
+ ss <<" nDate = 0;\n else\n";
+ }
+ ss << " nDate=(int)";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ if(tmpCur2->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR2= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur2);
+ ss <<" if(isnan("<<vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR2->GetArrayLength()<<"))\n";
+ ss <<" nFirstPer = 0;\n else\n";
+ }
+ ss << " nFirstPer=(int)";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur3->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR3= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur3);
+ ss <<" if(isnan(" <<vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR3->GetArrayLength()<<"))\n";
+ ss <<" fRestVal = 0;\n else\n";
+ }
+ ss << " fRestVal=";
+ ss << vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ if(tmpCur4->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR4= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur4);
+ ss <<" if(isnan(" <<vSubArguments[4]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR4->GetArrayLength()<<"))\n";
+ ss <<" fPer = 0;\n else\n";
+ }
+ ss << " fPer = ";
+ ss << vSubArguments[4]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur5->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR5= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur5);
+ ss <<" if(isnan(" <<vSubArguments[5]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR5->GetArrayLength()<<"))\n";
+ ss <<" fRate = 0;\n else\n";
+ }
+ ss << " fRate=";
+ ss << vSubArguments[5]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ if(tmpCur6 == nullptr)
+ {
+ ss << " nBase = 0;\n";
+ }
+ else
+ {
+ if(tmpCur6->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR6=
+ static_cast<const formula::SingleVectorRefToken *>(tmpCur6);
+ ss <<" if(isnan(" <<vSubArguments[6]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR6->GetArrayLength()<<"))\n";
+ ss <<" nBase = 0;\n else\n";
+ }
+ ss << " nBase = (int)";
+ ss << vSubArguments[6]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ ss <<" uint nPer = convert_int( fPer );\n";
+ ss <<" double fUsePer = 1.0 *pow( fRate,-1);\n";
+ ss <<" double fAmorCoeff;\n";
+ ss <<" if( fUsePer < 3.0 )\n";
+ ss <<" fAmorCoeff = 1.0;\n";
+ ss <<" else if( fUsePer < 5.0 )\n";
+ ss <<" fAmorCoeff = 1.5;\n";
+ ss <<" else if( fUsePer <= 6.0 )\n";
+ ss <<" fAmorCoeff = 2.0;\n";
+ ss <<" else\n";
+ ss <<" fAmorCoeff = 2.5;\n";
+ ss <<" fRate *= fAmorCoeff;\n";
+ ss <<" tmp = Round( GetYearFrac( 693594,";
+ ss <<"nDate, nFirstPer, nBase ) * fRate * fCost);\n";
+ ss <<" fCost = fCost-tmp;\n";
+ ss <<" double fRest = fCost - fRestVal;\n";
+ ss <<" for( uint n = 0 ; n < nPer ; n++ )\n";
+ ss <<" {\n";
+ ss <<" tmp = Round( fRate * fCost);\n";
+ ss <<" fRest -= tmp;\n";
+ ss <<" if( fRest < 0.0 )\n";
+ ss <<" {\n";
+ ss <<" switch( nPer - n )\n";
+ ss <<" {\n";
+ ss <<" case 0:\n";
+ ss <<" case 1:\n";
+ ss <<" tmp = Round( fCost * 0.5);\n";
+ ss <<" default:\n";
+ ss <<" tmp = 0.0;\n";
+ ss <<" }\n";
+ ss <<" }\n";
+ ss <<" fCost -= tmp;\n";
+ ss <<" }\n";
+ ss <<" return tmp;\n";
+ ss <<"}";
+}
+void OpAmorlinc::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(nCorrValDecl); decls.insert(RoundDecl);
+ decls.insert(IsLeapYearDecl);decls.insert(DaysInMonthDecl);
+ decls.insert(DaysToDateDecl); decls.insert(DateToDaysDecl);
+ decls.insert(GetYearFracDecl);
+ funs.insert(Round);
+ funs.insert(IsLeapYear);funs.insert(DaysInMonth);
+ funs.insert(DaysToDate);funs.insert(DateToDays);
+ funs.insert(GetYearFrac);
+}
+void OpAmorlinc::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double tmp = 0;\n";
+ ss << " double fCost,fRestVal,fPer,fRate;\n";
+ ss << " int nDate,nFirstPer,nBase;\n";
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ FormulaToken *tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ FormulaToken *tmpCur2 = vSubArguments[2]->GetFormulaToken();
+ FormulaToken *tmpCur3 = vSubArguments[3]->GetFormulaToken();
+ FormulaToken *tmpCur4 = vSubArguments[4]->GetFormulaToken();
+ FormulaToken *tmpCur5 = vSubArguments[5]->GetFormulaToken();
+ FormulaToken *tmpCur6 = vSubArguments.size() < 7 ? nullptr : vSubArguments[6]->GetFormulaToken();
+ if(tmpCur0->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR0= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+ ss <<" if(isnan("<<vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR0->GetArrayLength()<<"))\n";
+ ss <<" fCost = 0;\n else\n";
+ }
+ ss << " fCost=";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur1->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur1);
+ ss <<" if(isnan("<<vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR1->GetArrayLength()<<"))\n";
+ ss <<" nDate = 0;\n else\n";
+ }
+ ss << " nDate=(int)";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ if(tmpCur2->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR2= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur2);
+ ss <<" if(isnan("<<vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR2->GetArrayLength()<<"))\n";
+ ss <<" nFirstPer = 0;\n else\n";
+ }
+ ss << " nFirstPer=(int)";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur3->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR3= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur3);
+ ss <<" if(isnan(" <<vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR3->GetArrayLength()<<"))\n";
+ ss <<" fRestVal = 0;\n else\n";
+ }
+ ss << " fRestVal=";
+ ss << vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ if(tmpCur4->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR4= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur4);
+ ss <<" if(isnan(" <<vSubArguments[4]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR4->GetArrayLength()<<"))\n";
+ ss <<" fPer = 0;\n else\n";
+ }
+ ss << " fPer = ";
+ ss << vSubArguments[4]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur5->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR5= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur5);
+ ss <<" if(isnan(" <<vSubArguments[5]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR5->GetArrayLength()<<"))\n";
+ ss <<" fRate = 0;\n else\n";
+ }
+ ss << " fRate=";
+ ss << vSubArguments[5]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ if(tmpCur6 == nullptr)
+ {
+ ss << " nBase = 0;\n";
+ }
+ else
+ {
+ if(tmpCur6->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR6=
+ static_cast<const formula::SingleVectorRefToken *>(tmpCur6);
+ ss <<" if(isnan(" <<vSubArguments[6]->GenSlidingWindowDeclRef();
+ ss <<")||(gid0 >="<<tmpCurDVR6->GetArrayLength()<<"))\n";
+ ss <<" nBase = 0;\n else\n";
+ }
+ ss << " nBase = (int)";
+ ss << vSubArguments[6]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ ss <<" int nPer = convert_int( fPer );\n";
+ ss <<" double fOneRate = fCost * fRate;\n";
+ ss <<" double fCostDelta = fCost - fRestVal;\n";
+ ss <<" double f0Rate = GetYearFrac( 693594,";
+ ss <<"nDate, nFirstPer, nBase )* fRate * fCost;\n";
+ ss <<" int nNumOfFullPeriods = (int)";
+ ss <<"( ( fCost - fRestVal - f0Rate) *pow(fOneRate,-1) );\n";
+ ss <<" if( nPer == 0 )\n";
+ ss <<" tmp = f0Rate;\n";
+ ss <<" else if( nPer <= nNumOfFullPeriods )\n";
+ ss <<" tmp = fOneRate;\n";
+ ss <<" else if( nPer == nNumOfFullPeriods + 1 )\n";
+ ss <<" tmp = fCostDelta - fOneRate * nNumOfFullPeriods - f0Rate;\n";
+ ss <<" else\n";
+ ss <<" tmp = 0.0;\n";
+ ss <<" return tmp;\n";
+ ss <<"}";
+}
+void OpReceived::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(GetYearDiffDecl);decls.insert(GetDiffDateDecl);
+ decls.insert(DaysToDateDecl);decls.insert(DaysInMonthDecl);
+ decls.insert(GetNullDateDecl);decls.insert(IsLeapYearDecl);
+ decls.insert(DateToDaysDecl);
+ funs.insert(GetDiffDate);funs.insert(DaysToDate);
+ funs.insert(DaysInMonth);funs.insert(GetNullDate);
+ funs.insert(DateToDays);funs.insert(IsLeapYear);
+ funs.insert(GetYearDiff);
+}
+
+void OpReceived::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " double tmp = " << GetBottom() <<";\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " int nSettle, nMat;\n";
+ ss << " double fInvest,fDisc;\n";
+ ss << " int rOB;\n";
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR0= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+ FormulaToken *tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur1);
+ FormulaToken *tmpCur2 = vSubArguments[2]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR2= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur2);
+ FormulaToken *tmpCur3 = vSubArguments[3]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR3= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur3);
+ FormulaToken *tmpCur4 = vSubArguments[4]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR4= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur4);
+ ss<< " int buffer_settle_len = ";
+ ss<< tmpCurDVR0->GetArrayLength();
+ ss << ";\n";
+ ss<< " int buffer_mat_len = ";
+ ss<< tmpCurDVR1->GetArrayLength();
+ ss << ";\n";
+ ss<< " int buffer_invest_len = ";
+ ss<< tmpCurDVR2->GetArrayLength();
+ ss << ";\n";
+ ss<< " int buffer_disc_len = ";
+ ss<< tmpCurDVR3->GetArrayLength();
+ ss << ";\n";
+ ss<< " int buffer_rob_len = ";
+ ss<< tmpCurDVR4->GetArrayLength();
+ ss << ";\n";
+ ss <<" if(gid0 >= buffer_settle_len || isnan(";
+ ss <<vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" nSettle = 0;\n\telse\n";
+ ss <<" nSettle = (int)"<<vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ ss <<" if(gid0 >= buffer_mat_len || isnan(";
+ ss <<vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" nMat = 0;\n\telse\n";
+ ss <<" nMat = (int)";
+ ss <<vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ ss <<" if(gid0 >= buffer_invest_len || isnan(";
+ ss <<vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" fInvest = 0;\n\telse\n";
+ ss <<" fInvest = "<<vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ ss <<" if(gid0 >= buffer_disc_len || isnan(";
+ ss <<vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" fDisc = 0;\n\telse\n";
+ ss <<" fDisc = "<<vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ ss <<" if(gid0 >= buffer_rob_len || isnan(";
+ ss <<vSubArguments[4]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" rOB = 0;\n\telse\n";
+ ss <<" rOB = (int)"<<vSubArguments[4]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ ss << " double tmpvalue = (1.0-(fDisc";
+ ss <<" * GetYearDiff( GetNullDate()";
+ ss <<",nSettle,nMat,rOB)));\n";
+ ss << " tmp = fInvest*pow(tmpvalue,-1);\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+
+ void OpYielddisc::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(GetYearFracDecl);decls.insert(GetNullDateDecl);
+ decls.insert(DateToDaysDecl);decls.insert(DaysToDateDecl);
+ decls.insert(DaysInMonthDecl);decls.insert(IsLeapYearDecl);
+
+ funs.insert(GetYearFrac);funs.insert(GetNullDate);
+ funs.insert(DateToDays);funs.insert(DaysToDate);
+ funs.insert(DaysInMonth);funs.insert(IsLeapYear);
+}
+void OpYielddisc::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &vSubArguments)
+{
+ CHECK_PARAMETER_COUNT(5,5);
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n\t";
+ ss << "double tmp = 0;\n\t";
+ ss << "int gid0 = get_global_id(0);\n\t";
+ ss << "double tmp000;\n\t";
+ ss << "double tmp001;\n\t";
+ ss << "double tmp002;\n\t";
+ ss << "double tmp003;\n\t";
+ ss << "double tmp004;\n\t";
+
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR0= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+
+ FormulaToken *tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur1);
+
+ FormulaToken *tmpCur2 = vSubArguments[2]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR2= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur2);
+
+ FormulaToken *tmpCur3 = vSubArguments[3]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR3= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur3);
+
+ FormulaToken *tmpCur4 = vSubArguments[4]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR4= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur4);
+
+ ss<< "int buffer_tmp000_len = ";
+ ss<< tmpCurDVR0->GetArrayLength();
+ ss << ";\n\t";
+
+ ss<< "int buffer_tmp001_len = ";
+ ss<< tmpCurDVR1->GetArrayLength();
+ ss << ";\n\t";
+
+ ss<< "int buffer_tmp002_len = ";
+ ss<< tmpCurDVR2->GetArrayLength();
+ ss << ";\n\t";
+
+ ss<< "int buffer_tmp003_len = ";
+ ss<< tmpCurDVR3->GetArrayLength();
+ ss << ";\n\t";
+
+ ss<< "int buffer_tmp004_len = ";
+ ss<< tmpCurDVR4->GetArrayLength();
+ ss << ";\n\t";
+
+ ss<<"if(gid0>=buffer_tmp000_len || isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"tmp000 = 0;\n\telse \n\t\t";
+ ss<<"tmp000 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+
+ ss<<"if(gid0>=buffer_tmp001_len || isnan(";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"tmp001 = 0;\n\telse \n\t\t";
+ ss<<"tmp001 = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+
+ ss<<"if(gid0>=buffer_tmp002_len || isnan(";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"tmp002 = 0;\n\telse \n\t\t";
+ ss<<"tmp002 = ";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+
+ ss<<"if(gid0>=buffer_tmp003_len || isnan(";
+ ss << vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"tmp003 = 0;\n\telse \n\t\t";
+ ss<<"tmp003 = ";
+ ss << vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+
+ ss<<"if(gid0>=buffer_tmp004_len || isnan(";
+ ss << vSubArguments[4]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"tmp004 = 0;\n\telse \n\t\t";
+ ss<<"tmp004 = ";
+ ss << vSubArguments[4]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+
+ ss<< "if(tmp002 <= 0 || tmp003 <= 0 || tmp000 >= tmp001 )\n";
+ ss<< " return CreateDoubleError(IllegalArgument);\n";
+ ss<< "tmp = (tmp003/tmp002)-1;\n\t";
+ ss << "tmp /= GetYearFrac( GetNullDate(),tmp000,tmp001,tmp004);\n\t";
+ ss << "return tmp;\n";
+ ss << "}";
+}
+
+ void OpTbillprice::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(GetYearFracDecl);
+ decls.insert(DateToDaysDecl);decls.insert(DaysToDateDecl);
+ decls.insert(DaysInMonthDecl);decls.insert(IsLeapYearDecl);
+
+ funs.insert(GetYearFrac);
+ funs.insert(DateToDays);funs.insert(DaysToDate);
+ funs.insert(DaysInMonth);funs.insert(IsLeapYear);
+}
+
+void OpTbillprice::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double tmp = 0;\n";
+
+ ss << " int singleIndex = gid0;\n";
+ ss << " int doubleIndex = gid0;\n";
+ ss << " int i = gid0;\n";
+ GenTmpVariables(ss,vSubArguments);
+ CheckAllSubArgumentIsNan(ss,vSubArguments);
+
+ ss << " tmp1+=1.0;\n";
+ ss << " double fFraction =GetYearFrac(693594,tmp0,tmp1,0);\n";
+ ss << " tmp = 100.0 * ( 1.0 - tmp2 * fFraction );\n";
+ ss << " return tmp;\n";
+ ss << "}\n";
+}
+ void RATE::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(nCorrValDecl);
+ decls.insert(SCdEpsilonDecl);decls.insert(RoundDecl);
+ funs.insert(Round);
+}
+
+void RATE::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+
+ FormulaToken* pCur = vSubArguments[5]->GetFormulaToken();
+ assert(pCur);
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ assert(pSVR);
+ ss << ") {\n";
+ ss << " double result;\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " bool bValid = true, bFound = false;\n";
+ ss << " double fX, fXnew, fTerm, fTermDerivation;\n";
+ ss << " double fGeoSeries, fGeoSeriesDerivation;\n";
+ ss << " int nIterationsMax = 150;\n";
+ ss << " int nCount = 0;\n";
+ ss << " double fEpsilonSmall = 1.0E-14;\n";
+ ss << " double arg0, arg1, arg2, arg3, arg4, arg5;\n";
+ ss << " arg0=" << vSubArguments[0]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " arg1=" << vSubArguments[1]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " arg2=" << vSubArguments[2]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " arg3=" << vSubArguments[3]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " arg4=" << vSubArguments[4]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " arg5=" << vSubArguments[5]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " int guessLen = " << pSVR->GetArrayLength() << ";\n";
+ ss << " if (isnan(arg0) || isnan(arg1) || isnan(arg2)){\n";
+ ss << " result = 523;\n";
+ ss << " return result;\n";
+ ss << " }\n";
+ ss << " if (isnan(arg3))\n";
+ ss << " arg3 = 0.0;\n";
+ ss << " if (isnan(arg4))\n";
+ ss << " arg4 = 0.0;\n";
+ ss << " if (isnan(arg5))\n";
+ ss << " arg5 = 0.1;\n";
+ ss << " if (gid0 >= guessLen)\n";
+ ss << " arg5 = 0.1;\n";
+ ss << " arg3 = arg3 - arg1 * arg4;\n";
+ ss << " arg2 = arg2 + arg1 * arg4;\n";
+ ss << " if (arg0 == Round(arg0)){\n";
+ ss << " fX = arg5;\n";
+ ss << " double fPowN, fPowNminus1;\n";
+ ss << " while (!bFound && nCount < nIterationsMax)\n";
+ ss << " {\n";
+ ss << " fPowNminus1 = pow( 1.0+fX, arg0-1.0);\n";
+ ss << " fPowN = fPowNminus1 * (1.0+fX);\n";
+ ss << " if (fX == 0.0)\n";
+ ss << " {\n";
+ ss << " fGeoSeries = arg0;\n";
+ ss << " fGeoSeriesDerivation = arg0 * (arg0-1.0)";
+ ss << "*pow(2.0,-1);\n";
+ ss << " }\n";
+ ss << " else\n";
+ ss << " {";
+ ss << " fGeoSeries = (fPowN-1.0)*pow(fX,-1);\n";
+ ss << " fGeoSeriesDerivation =";
+ ss << " arg0 * fPowNminus1 * pow( fX , -1) - fGeoSeries * pow(fX, -1);\n";
+ ss << " }\n";
+ ss << " fTerm = arg3 + arg2 *fPowN+ arg1 * fGeoSeries;\n";
+ ss << " fTermDerivation = arg2 * arg0 * fPowNminus1 +";
+ ss << "arg1 * fGeoSeriesDerivation;\n";
+ ss << " if (fabs(fTerm) < fEpsilonSmall)\n";
+ ss << " bFound = true;\n";
+ ss << " else\n";
+ ss << " {\n";
+ ss << " if (fTermDerivation == 0.0)\n";
+ ss << " fXnew = fX + 1.1 * SCdEpsilon;\n";
+ ss << " else\n";
+ ss << " fXnew = fX - fTerm ";
+ ss << "*pow( fTermDerivation,-1);\n";
+ ss << " nCount++;\n";
+ ss << " bFound = (fabs(fXnew - fX) < SCdEpsilon);\n";
+ ss << " fX = fXnew;\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << " else\n";
+ ss << " {";
+ ss << " fX = (arg5 < -1.0) ? -1.0 : arg5;\n";
+ ss << " while (bValid && !bFound && nCount < nIterationsMax)\n";
+ ss << " {\n";
+ ss << " if (fX == 0.0){\n";
+ ss << " fGeoSeries = arg0;\n";
+ ss << " fGeoSeriesDerivation = arg0 * ";
+ ss << "(arg0-1.0)* pow(2.0,-1);\n";
+ ss << " }else{\n";
+ ss << " fGeoSeries = (pow( 1.0+fX, arg0) - 1.0)";
+ ss << " *pow( fX,-1);\n";
+ ss << " fGeoSeriesDerivation =";
+ ss << " arg0 * pow(1.0+fX,arg0-1.0) *pow(fX,-1)";
+ ss << " - fGeoSeries *pow( fX,-1);\n";
+ ss << " }\n";
+ ss << " fTerm = arg3 + arg2 *pow(1.0+fX, arg0)";
+ ss << "+ arg1 * fGeoSeries;\n";
+ ss << " fTermDerivation =";
+ ss << "arg2*arg0*pow(1.0+fX,arg0-1.0)";
+ ss << "+arg1*fGeoSeriesDerivation;\n";
+ ss << " if (fabs(fTerm) < fEpsilonSmall)\n";
+ ss << " bFound = true;\n";
+ ss << " else{\n";
+ ss << " if (fTermDerivation == 0.0)\n";
+ ss << " fXnew = fX + 1.1 * SCdEpsilon;\n";
+ ss << " else\n";
+ ss << " fXnew = fX - fTerm ";
+ ss << "*pow( fTermDerivation,-1);\n";
+ ss << " nCount++;\n";
+ ss << " bFound = (fabs(fXnew - fX) < SCdEpsilon);\n";
+ ss << " fX = fXnew;\n";
+ ss << " bValid = (fX >= -1.0);\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << " if (bValid && bFound)\n";
+ ss << " result = fX;\n";
+ ss << " else\n";
+ ss << " result = 523;\n";
+ ss << " return result;\n";
+ ss << "}";
+}
+
+ void OpTbillyield::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(GetDiffDate360Decl);decls.insert(IsLeapYearDecl);
+ decls.insert(DateToDaysDecl);decls.insert(DaysToDate_LocalBarrierDecl);
+ decls.insert(DaysInMonthDecl);decls.insert(GetNullDateDecl);
+ decls.insert(GetDiffDate360_Decl);
+ funs.insert(GetDiffDate360);funs.insert(DateToDays);
+ funs.insert(DaysToDate_LocalBarrier);funs.insert(IsLeapYear);
+ funs.insert(DaysInMonth);funs.insert(GetNullDate);
+ funs.insert(GetDiffDate360_);
+
+}
+
+void OpTbillyield::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n\t";
+ ss << "int gid0 = get_global_id(0);\n\t";
+ ss << "double tmp = 0;\n\t";
+ ss << "double tmp000;\n\t";
+ ss << "double tmp001;\n\t";
+ ss << "double tmp002;\n\t";
+
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR0= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+
+ FormulaToken *tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur1);
+
+ FormulaToken *tmpCur2 = vSubArguments[2]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR2= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur2);
+
+ ss<< "int buffer_tmp000_len = ";
+ ss<< tmpCurDVR0->GetArrayLength();
+ ss << ";\n\t";
+
+ ss<< "int buffer_tmp001_len = ";
+ ss<< tmpCurDVR1->GetArrayLength();
+ ss << ";\n\t";
+
+ ss<< "int buffer_tmp002_len = ";
+ ss<< tmpCurDVR2->GetArrayLength();
+ ss << ";\n\t";
+
+ ss<<"if(gid0>=buffer_tmp000_len || isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"tmp000 = 0;\n\telse \n\t\t";
+ ss<<"tmp000 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+
+ ss<<"if(gid0>=buffer_tmp001_len || isnan(";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"tmp001 = 0;\n\telse \n\t\t";
+ ss<<"tmp001 = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+
+ ss<<"if(gid0>=buffer_tmp002_len || isnan(";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss<<"))\n\t\t";
+ ss<<"tmp002 = 0;\n\telse \n\t\t";
+ ss<<"tmp002 = ";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss<<";\n\t";
+ ss <<" int nDiff=GetDiffDate360(GetNullDate(),tmp000,tmp001,true);\n";
+ ss <<" nDiff++;\n";
+ ss <<" tmp=100.0;\n";
+ ss <<" tmp = tmp *pow( tmp002,-1);\n";
+ ss <<" tmp = tmp - 1.0;\n";
+ ss <<" tmp = tmp * pow( nDiff,-1.0 );\n";
+ ss <<" tmp = tmp * 360.0;\n";
+ ss <<" return tmp;\n";
+ ss << "}\n";
+}
+void OpDDB::GenSlidingWindowFunction(std::stringstream& ss,
+ const std::string &sSymName, SubArguments& vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double tmp = 0;\n";
+ ss << " double fCost, fSalvage, fLife, fPeriod, fFactor;\n";
+ ss << " double fRate, fOldValue, fNewValue;\n";
+
+ FormulaToken* tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ FormulaToken* tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ FormulaToken* tmpCur2 = vSubArguments[2]->GetFormulaToken();
+ FormulaToken* tmpCur3 = vSubArguments[3]->GetFormulaToken();
+ FormulaToken* tmpCur4 = vSubArguments[4]->GetFormulaToken();
+ if(tmpCur0->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR0= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+ ss <<" if(gid0 >= "<<tmpCurDVR0->GetArrayLength()<<" || isnan(";
+ ss <<vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" fCost = 0;\n else\n";
+ }
+ ss <<" fCost = "<<vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur1->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur1);
+ ss <<" if(gid0 >= "<<tmpCurDVR1->GetArrayLength()<<" || isnan(";
+ ss <<vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" fSalvage = 0;\n else\n";
+ }
+ ss <<" fSalvage = ";
+ ss <<vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur2->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR2= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur2);
+ ss <<" if(gid0 >= "<<tmpCurDVR2->GetArrayLength()<<" || isnan(";
+ ss <<vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" fLife = 0;\n else\n";
+ }
+ ss <<" fLife = ";
+ ss <<vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur3->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR3= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur3);
+ ss <<" if(gid0 >= "<<tmpCurDVR3->GetArrayLength()<<" || isnan(";
+ ss <<vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" fPeriod = 0;\n else\n";
+ }
+ ss <<" fPeriod = "<<vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ if(tmpCur4->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR4= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur4);
+ ss <<" if(gid0 >= "<<tmpCurDVR4->GetArrayLength()<<" || isnan(";
+ ss <<vSubArguments[4]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" fFactor = 0;\n else\n";
+ }
+ ss <<" fFactor = "<<vSubArguments[4]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ ss <<" fRate = fFactor * pow(fLife,-1);\n";
+ ss <<" if (fRate >= 1.0)\n";
+ ss <<" {\n";
+ ss <<" fRate = 1.0;\n";
+ ss <<" if (fPeriod == 1.0)\n";
+ ss <<" fOldValue = fCost;\n";
+ ss <<" else\n";
+ ss <<" fOldValue = 0.0;\n";
+ ss <<" }\n";
+ ss <<" else\n";
+ ss <<" fOldValue = fCost * pow(1.0 - fRate, fPeriod - 1);\n";
+ ss <<" fNewValue = fCost * pow(1.0 - fRate, fPeriod);\n";
+ ss <<" if (fNewValue < fSalvage)\n";
+ ss <<" tmp = fOldValue - fSalvage;\n";
+ ss <<" else\n";
+ ss <<" tmp = fOldValue - fNewValue;\n";
+ ss <<" if (tmp < 0.0)\n";
+ ss <<" tmp = 0.0;\n";
+ ss <<" return tmp;\n";
+ ss <<"}";
+}
+void OpPV::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &
+vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " double result = 0;\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double rate;\n";
+ ss << " double nper;\n";
+ ss << " double pmt;\n";
+ ss << " double fv;\n";
+ ss << " double type;\n";
+
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR0= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+
+ FormulaToken *tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur1);
+
+ FormulaToken *tmpCur2 = vSubArguments[2]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR2= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur2);
+
+ if(vSubArguments.size()>3)
+ {
+ FormulaToken *tmpCur3 = vSubArguments[3]->GetFormulaToken();
+ const formula::SingleVectorRefToken* tmpCurDVR3= static_cast<const formula::SingleVectorRefToken *>(
+tmpCur3);
+ ss<< " int buffer_fv_len = ";
+ ss<< tmpCurDVR3->GetArrayLength();
+ ss << ";\n";
+ }
+
+ if(vSubArguments.size()>4)
+ {
+ FormulaToken *tmpCur4 = vSubArguments[4]->GetFormulaToken();
+ const formula::SingleVectorRefToken* tmpCurDVR4= static_cast<const formula::SingleVectorRefToken *>(
+tmpCur4);
+ ss<< " int buffer_type_len = ";
+ ss<< tmpCurDVR4->GetArrayLength();
+ ss << ";\n";
+ }
+
+ ss<< " int buffer_rate_len = ";
+ ss<< tmpCurDVR0->GetArrayLength();
+ ss << ";\n";
+
+ ss<< " int buffer_nper_len = ";
+ ss<< tmpCurDVR1->GetArrayLength();
+ ss << ";\n";
+
+ ss<< " int buffer_pmt_len = ";
+ ss<< tmpCurDVR2->GetArrayLength();
+ ss << ";\n";
+
+ ss<<" if(gid0>=buffer_rate_len || isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss<<"))\n";
+ ss<<" rate = 0;\n else \n";
+ ss<<" rate = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss<<";\n";
+
+ ss<<" if(gid0>=buffer_nper_len || isnan(";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss<<"))\n";
+ ss<<" nper = 0;\n else \n";
+ ss<<" nper = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss<<";\n";
+
+ ss<<" if(gid0>=buffer_pmt_len || isnan(";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss<<"))\n";
+ ss<<" pmt = 0;\n else \n";
+ ss<<" pmt = ";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss<<";\n";
+
+ if(vSubArguments.size()>3)
+ {
+ ss<<" if(gid0>=buffer_fv_len || isnan(";
+ ss << vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss<<"))\n";
+ ss<<" fv = 0;\n else \n";
+ ss<<" fv = ";
+ ss << vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss<<";\n";
+ }else
+ {
+ ss<<" fv = 0;\n";
+ }
+
+ if(vSubArguments.size()>4)
+ {
+ ss<<" if(gid0>=buffer_type_len || isnan(";
+ ss << vSubArguments[4]->GenSlidingWindowDeclRef();
+ ss<<"))\n";
+ ss<<" type = 0;\n else \n";
+ ss<<" type = ";
+ ss << vSubArguments[4]->GenSlidingWindowDeclRef();
+ ss<<";\n";
+ }else
+ {
+ ss<<" type = 0;\n";
+ }
+ ss << " if(rate == 0)\n";
+ ss << " result=fv+pmt*nper;\n";
+ ss << " else if(type > 0)\n";
+ ss << " result=(fv*pow(1+rate,-nper))+";
+ ss << "(pmt*(1-pow(1+rate,-nper+1))*pow(rate,-1))+pmt;\n";
+ ss << " else\n";
+ ss << " result=(fv*pow(1+rate,-nper))+";
+ ss << "(pmt*(1-pow(1+rate,-nper))*pow(rate,-1));\n";
+ ss << " return -result;\n";
+ ss << "}";
+}
+ void OpVDB::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(ScGetDDBDecl);decls.insert(DblMinDecl);
+ decls.insert(ScInterVDBDecl);decls.insert(VDBImplementDecl);
+ funs.insert(ScGetDDB);funs.insert(DblMin);
+ funs.insert(ScInterVDB);funs.insert(VDBImplement);
+}
+
+void OpVDB::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " int singleIndex = gid0;\n";
+ ss << " double result = 0;\n";
+ if(vSubArguments.size()<5)
+ {
+ ss << " result = -DBL_MAX;\n";
+ ss << " return result;\n";
+ }else
+ {
+ GenTmpVariables(ss,vSubArguments);
+ CheckAllSubArgumentIsNan(ss,vSubArguments);
+ if(vSubArguments.size() <= 6)
+ {
+ ss << " int tmp6 = 0;\n";
+ }
+ if(vSubArguments.size() == 5)
+ {
+ ss << " double tmp5= 2.0;\n";
+ }
+ ss << " if(tmp3 < 0 || tmp4<tmp3 || tmp4>tmp2 || tmp0<0 ||tmp1>tmp0";
+ ss << "|| tmp5 <=0)\n";
+ ss << " result = -DBL_MAX;\n";
+ ss << " else\n";
+ ss << " result =";
+ ss << "VDBImplement(tmp0,tmp1,tmp2,tmp3,tmp4,tmp5,tmp6);\n";
+ ss << " return result;\n";
+ ss << "}";
+ }
+
+}
+
+void OpXirr::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur);
+ size_t nCurWindowSize = pCurDVR->GetArrayLength() <
+ pCurDVR->GetRefRowSize() ? pCurDVR->GetArrayLength():
+ pCurDVR->GetRefRowSize() ;
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " int doubleIndex = gid0;\n";
+ ss << " int singleIndex = gid0;\n";
+ ss << " double result = 0;\n";
+ ss << " int i=0;\n";
+ if(vSubArguments.size()<2)
+ {
+ ss << " result = -DBL_MAX;\n";
+ ss << " return result;\n";
+ }else
+ {
+ GenTmpVariables(ss,vSubArguments);
+ if(vSubArguments.size() == 2)
+ {
+ ss << " double tmp2 = 0.1;\n";
+ }else
+ {
+ CheckSubArgumentIsNan(ss,vSubArguments,2);
+ }
+ ss << " if(tmp2<=-1)\n";
+ ss << " result = -DBL_MAX;\n";
+ ss << " else\n";
+ ss << " {\n";
+ ss << " double fMaxEps = 1e-10;\n";
+ ss << " int nMaxIter = 50;\n";
+ ss << " double fNewRate, fRateEps, fResultValue, fResultValue2;\n";
+ ss << " int nIter = 0;\n";
+ ss << " int bContLoop;\n";
+ ss << " int windowsSize = ";
+ ss << nCurWindowSize;
+ ss << ";\n";
+ CheckSubArgumentIsNan(ss,vSubArguments,0);
+ CheckSubArgumentIsNan(ss,vSubArguments,1);
+ ss << " double D_0 = tmp1;\n";
+ ss << " double V_0 = tmp0;\n";
+ ss << " double fResultRate = tmp2;\n";
+ ss << " double r;\n";
+ ss << " double fResult;\n";
+ ss << " do\n";
+ ss << " {\n";
+ ss << " fResultValue = V_0;\n";
+ ss << " r = fResultRate + 1;\n";
+ ss << " for (i = ";
+ if (!pCurDVR->IsStartFixed() && pCurDVR->IsEndFixed()) {
+ ss << "gid0+1; i < "<< nCurWindowSize <<"; i++)\n";
+ } else if (pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed()) {
+ ss << "1; i < gid0+"<< nCurWindowSize <<"; i++)\n";
+ } else {
+ ss << "1; i < "<< nCurWindowSize <<"; i++)\n";
+ }
+ ss << " {\n";
+ if(!pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed())
+ {
+ ss<< " doubleIndex =i+gid0;\n";
+ }else
+ {
+ ss<< " doubleIndex =i;\n";
+ }
+ CheckSubArgumentIsNan(ss,vSubArguments,0);
+ CheckSubArgumentIsNan(ss,vSubArguments,1);
+ ss << " fResultValue += tmp0/pow(r,(tmp1 - D_0)/365.0);\n";
+ ss << " }\n";
+ ss << " fResultValue2 = 0;\n";
+
+ ss << " for (i = ";
+ if (!pCurDVR->IsStartFixed() && pCurDVR->IsEndFixed()) {
+ ss << "gid0+1; i < "<< nCurWindowSize <<"; i++)\n";
+ } else if (pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed()) {
+ ss << "1; i < gid0+"<< nCurWindowSize <<"; i++)\n";
+ } else {
+ ss << "1; i < "<< nCurWindowSize <<"; i++)\n";
+ }
+ ss << " {\n";
+ if(!pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed())
+ {
+ ss<< " doubleIndex =i+gid0;\n";
+ }else
+ {
+ ss<< " doubleIndex =i;\n";
+ }
+ CheckSubArgumentIsNan(ss,vSubArguments,0);
+ CheckSubArgumentIsNan(ss,vSubArguments,1);
+ ss << " double E_i = (tmp1 - D_0)/365.0;\n";
+ ss << " fResultValue2 -= E_i * tmp0 / pow(r,E_i + 1.0);\n";
+ ss << " }\n";
+ ss << " fNewRate = fResultRate - fResultValue / fResultValue2;\n";
+ ss << " fRateEps = fabs( fNewRate - fResultRate );\n";
+ ss << " fResultRate = fNewRate;\n";
+ ss << " bContLoop = (fRateEps > fMaxEps) && (fabs( fResultValue ) > fMaxEps);\n";
+ ss << " }\n";
+ ss << " while( bContLoop && (++nIter < nMaxIter) );\n";
+ ss << " if( bContLoop )\n";
+ ss << " result = -DBL_MAX;\n";
+ ss << " result = fResultRate;\n";
+ ss << " }\n";
+ ss << " return result;\n";
+ ss << "}";
+ }
+
+}
+void OpDB::GenSlidingWindowFunction(std::stringstream& ss,
+ const std::string &sSymName, SubArguments& vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double fCost, fSalvage, fLife, fPeriod;\n";
+ ss << " int nMonths;\n";
+ ss << " double tmp = 0;\n";
+ FormulaToken* tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR0= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+ FormulaToken* tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur1);
+ FormulaToken* tmpCur2 = vSubArguments[2]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR2= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur2);
+ FormulaToken* tmpCur3 = vSubArguments[3]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR3= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur3);
+ FormulaToken* tmpCur4 = vSubArguments[4]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR4= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur4);
+ ss<< " int buffer_cost_len = ";
+ ss<< tmpCurDVR0->GetArrayLength();
+ ss << ";\n";
+ ss<< " int buffer_salvage_len = ";
+ ss<< tmpCurDVR1->GetArrayLength();
+ ss << ";\n";
+ ss<< " int buffer_life_len = ";
+ ss<< tmpCurDVR2->GetArrayLength();
+ ss << ";\n";
+ ss<< " int buffer_period_len = ";
+ ss<< tmpCurDVR3->GetArrayLength();
+ ss << ";\n";
+ ss<< " int buffer_months_len = ";
+ ss<< tmpCurDVR4->GetArrayLength();
+ ss << ";\n";
+ ss <<" if(gid0 >= buffer_cost_len || isnan(";
+ ss <<vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" fCost = 0;\n else\n";
+ ss <<" fCost = "<<vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ ss <<" if(gid0 >= buffer_salvage_len || isnan(";
+ ss <<vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" fSalvage = 0;\n else\n";
+ ss <<" fSalvage = ";
+ ss <<vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ ss <<" if(gid0 >= buffer_life_len || isnan(";
+ ss <<vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" fLife = 0;\n else\n";
+ ss <<" fLife = "<<vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ ss <<" if(gid0 >= buffer_period_len || isnan(";
+ ss <<vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" fPeriod = 0;\n else\n";
+ ss <<" fPeriod = "<<vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ ss <<" if(gid0 >= buffer_months_len || isnan(";
+ ss <<vSubArguments[4]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" nMonths = 0;\n else\n";
+ ss <<" nMonths = (int)"<<vSubArguments[4]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ ss <<" double fDeprRate = 1.0 - pow(fSalvage / fCost, 1.0 / fLife);\n";
+ ss <<" fDeprRate = ((int)(fDeprRate * 1000.0 + 0.5)) / 1000.0;\n";
+ ss <<" double fFirstDeprRate = fCost * fDeprRate * nMonths / 12.0;\n";
+ ss <<" double fDb = 0.0;\n";
+ ss <<" if ((int)(fPeriod) == 1)\n";
+ ss <<" fDb = fFirstDeprRate;\n";
+ ss <<" else\n";
+ ss <<" {\n";
+ ss <<" double fSumDeprRate = fFirstDeprRate;\n";
+ ss <<" double fMin = fLife;\n";
+ ss <<" if (fMin > fPeriod) fMin = fPeriod;\n";
+ ss <<" int nMax = (int)fMin;\n";
+ ss <<" for (int i = 2; i <= nMax; i++)\n";
+ ss <<" {\n";
+ ss <<" fDb = (fCost - fSumDeprRate) * fDeprRate;\n";
+ ss <<" fSumDeprRate += fDb;\n";
+ ss <<" }\n";
+ ss <<" if (fPeriod > fLife)\n";
+ ss <<" fDb = ((fCost - fSumDeprRate)";
+ ss <<"* fDeprRate * (12.0 - nMonths)) / 12.0;\n";
+ ss <<" }\n";
+ ss <<" tmp = fDb;\n";
+ ss <<" return tmp;\n";
+ ss <<"}";
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/opencl/op_financial.hxx b/sc/source/core/opencl/op_financial.hxx
new file mode 100644
index 000000000..87469c824
--- /dev/null
+++ b/sc/source/core/opencl/op_financial.hxx
@@ -0,0 +1,579 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include "opbase.hxx"
+
+namespace sc::opencl {
+
+class RRI: public SlidingFunctionBase
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual bool takeString() const override { return false; }
+ virtual bool takeNumeric() const override { return true; }
+};
+
+class OpRRI:public RRI
+{
+public:
+ virtual std::string GetBottom() override { return "0"; }
+ virtual std::string BinFuncName() const override { return "RRI"; }
+};
+
+class OpNominal: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "NOMINAL_ADD"; }
+};
+
+class OpDollarde:public Normal
+{
+public:
+ virtual std::string GetBottom() override { return "0"; }
+
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+
+ virtual std::string BinFuncName() const override { return "Dollarde"; }
+
+};
+
+class OpDollarfr:public Normal
+{
+public:
+ virtual std::string GetBottom() override { return "0"; }
+
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+
+ virtual std::string BinFuncName() const override { return "Dollarfr"; }
+
+};
+
+class OpDISC: public Normal
+{
+public:
+ virtual std::string GetBottom() override { return "0"; }
+
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>& ) override;
+
+ virtual std::string BinFuncName() const override { return "DISC"; }
+};
+
+class OpINTRATE: public Normal
+{
+public:
+ virtual std::string GetBottom() override { return "0"; }
+
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>& ) override;
+
+ virtual std::string BinFuncName() const override { return "INTRATE"; }
+};
+
+class OpFV: public Normal
+{
+public:
+ virtual std::string GetBottom() override { return "0"; }
+
+ virtual void GenSlidingWindowFunction(std::stringstream& ss,
+ const std::string &sSymName, SubArguments& vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,
+ std::set<std::string>& ) override;
+
+ virtual std::string BinFuncName() const override {
+ return "FV"; }
+};
+
+class OpIPMT: public Normal
+{
+public:
+ virtual std::string GetBottom() override { return "0"; }
+
+ virtual void GenSlidingWindowFunction(std::stringstream& ss,
+ const std::string &sSymName, SubArguments& vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,
+ std::set<std::string>& ) override;
+
+ virtual std::string BinFuncName() const override {
+ return "IPMT"; }
+};
+
+class OpISPMT: public Normal
+{
+public:
+ virtual std::string GetBottom() override { return "0"; }
+
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+
+ virtual std::string BinFuncName() const override { return "ISPMT"; }
+};
+
+class OpPDuration: public Normal
+{
+public:
+ virtual std::string GetBottom() override { return "0"; }
+
+ virtual void GenSlidingWindowFunction(std::stringstream& ss,
+ const std::string &sSymName, SubArguments& vSubArguments) override;
+
+ virtual std::string BinFuncName() const override { return "Duration"; }
+};
+
+class OpDuration_ADD: public Normal
+{
+public:
+ virtual std::string GetBottom() override { return "0"; }
+
+ virtual void GenSlidingWindowFunction(std::stringstream& ss,
+ const std::string &sSymName, SubArguments& vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,
+ std::set<std::string>& ) override;
+
+ virtual std::string BinFuncName() const override {
+ return "Duration_ADD"; }
+};
+class OpMDuration: public Normal
+{
+public:
+ virtual std::string GetBottom() override { return "0"; }
+
+ virtual void GenSlidingWindowFunction(std::stringstream& ss,
+ const std::string &sSymName, SubArguments& vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,
+ std::set<std::string>& ) override;
+
+ virtual std::string BinFuncName() const override {return "MDuration"; }
+};
+
+class Fvschedule: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+};
+
+class Cumipmt: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>& ) override;
+};
+
+class IRR: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+};
+
+class OpIRR: public IRR
+{
+public:
+ virtual std::string GetBottom() override { return "0"; }
+ virtual std::string BinFuncName() const override { return "IRR"; }
+};
+
+class XNPV: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+};
+
+class PriceMat: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>& ) override;
+};
+class OpSYD: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+
+ virtual std::string BinFuncName() const override { return "SYD"; }
+};
+
+class MIRR: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+};
+
+class OpEffective:public Normal
+{
+public:
+ virtual std::string GetBottom() override { return "0"; }
+
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+
+ virtual std::string BinFuncName() const override { return "Effect_Add"; }
+};
+
+class OpCumipmt: public Cumipmt
+{
+public:
+ virtual std::string GetBottom() override { return "0"; }
+ virtual std::string BinFuncName() const override { return "Cumipmt"; }
+};
+
+class OpXNPV: public XNPV
+{
+public:
+ virtual std::string GetBottom() override { return "0"; }
+ virtual std::string BinFuncName() const override { return "XNPV"; }
+
+};
+
+class OpTbilleq: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+
+ virtual std::string BinFuncName() const override { return "fTbilleq"; }
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>& ) override;
+};
+
+class OpCumprinc: public Normal
+{
+public:
+ virtual std::string GetBottom() override { return "0"; }
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>& ) override;
+ virtual std::string BinFuncName() const override { return "cumprinc"; }
+};
+
+class OpAccrintm: public Normal
+{
+ public:
+ virtual std::string GetBottom() override { return "0"; }
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>& ) override;
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Accrintm"; }
+};
+class OpAccrint: public Normal
+{
+ public:
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>&) override;
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Accrint"; }
+};
+
+class OpYield: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Yield"; }
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>& ) override;
+};
+
+class OpSLN: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "SLN"; }
+};
+
+class OpFvschedule: public Fvschedule
+{
+public:
+ virtual std::string GetBottom() override { return "0"; }
+ virtual std::string BinFuncName() const override { return "Fvschedule"; }
+};
+
+class OpYieldmat: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Yieldmat"; }
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>& ) override;
+};
+
+class OpPMT: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "PMT"; }
+};
+class OpNPV: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "NPV"; }
+};
+
+class OpPrice: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>& ) override;
+ virtual std::string BinFuncName() const override { return "Price"; }
+};
+
+class OpNper: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "NPER"; }
+};
+class OpOddlprice: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>&,
+ std::set<std::string>& ) override;
+ virtual std::string BinFuncName() const override { return "Oddlprice"; }
+};
+class OpOddlyield: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,
+ std::set<std::string>& ) override;
+ virtual std::string BinFuncName() const override { return "Oddlyield"; }
+};
+class OpPriceDisc: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>&,
+ std::set<std::string>& ) override;
+ virtual std::string BinFuncName() const override { return "PriceDisc"; }
+};
+class OpPPMT: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>& ) override;
+ virtual std::string BinFuncName() const override { return "PPMT"; }
+};
+
+class OpCoupdaybs:public Normal
+{
+public:
+ virtual std::string GetBottom() override { return "0";}
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>& ) override;
+ virtual std::string BinFuncName() const override { return "Coupdaybs"; }
+
+};
+
+class OpCoupdays:public Normal
+{
+public:
+ virtual std::string GetBottom() override { return "0";}
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>& ) override;
+ virtual std::string BinFuncName() const override { return "Coupdays";}
+
+};
+
+class OpCoupdaysnc:public Normal
+{
+public:
+ virtual std::string GetBottom() override { return "0";}
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>& ) override;
+ virtual std::string BinFuncName() const override { return "Coupdaysnc"; }
+
+};
+
+class OpCouppcd:public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>& ) override;
+ virtual std::string BinFuncName() const override { return "Couppcd"; }
+
+};
+
+class OpCoupncd:public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>&) override;
+ virtual std::string BinFuncName() const override { return "Coupncd"; }
+
+};
+
+class OpCoupnum:public Normal
+{
+public:
+ virtual std::string GetBottom() override { return "0";}
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>&) override;
+ virtual std::string BinFuncName() const override { return "Coupnum"; }
+
+};
+class OpDDB:public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+
+ virtual std::string BinFuncName() const override { return "DDB"; }
+};
+class OpDB:public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+
+ virtual std::string BinFuncName() const override { return "DB"; }
+};
+class OpAmordegrc:public Normal
+{
+public:
+ virtual std::string GetBottom() override { return "0";}
+ virtual void GenSlidingWindowFunction(std::stringstream& ss,
+ const std::string &sSymName, SubArguments& vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>&) override;
+ virtual std::string BinFuncName() const override { return "Amordegrc"; }
+};
+class OpAmorlinc:public Normal
+{
+public:
+ virtual std::string GetBottom() override { return "0";}
+ virtual void GenSlidingWindowFunction(std::stringstream& ss,
+ const std::string &sSymName, SubArguments& vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>&) override;
+ virtual std::string BinFuncName() const override { return "Amorlinc"; }
+};
+
+class OpReceived:public Normal
+{
+public:
+ virtual std::string GetBottom() override { return "0"; }
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Received"; }
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>& ) override;
+};
+
+class OpYielddisc: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Yielddisc"; }
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>& ) override;
+};
+
+class OpTbillprice: public CheckVariables
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "fTbillprice"; }
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>& ) override;
+};
+
+class OpPriceMat:public PriceMat
+{
+public:
+ virtual std::string GetBottom() override { return "0"; }
+ virtual std::string BinFuncName() const override { return "PriceMat"; }
+};
+
+class RATE: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>& ) override;
+};
+class OpIntrate: public RATE {
+public:
+ virtual std::string GetBottom() override { return "0"; }
+ virtual std::string BinFuncName() const override { return "rate"; }
+};
+
+class OpTbillyield: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+
+ virtual std::string BinFuncName() const override { return "fTbillyield"; }
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>& ) override;
+};
+
+class OpMIRR: public MIRR
+{
+public:
+ virtual std::string GetBottom() override { return "0"; }
+ virtual std::string BinFuncName() const override { return "MIRR"; }
+};
+
+class OpPV: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "PV"; }
+};
+
+class OpVDB: public CheckVariables
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+
+ virtual std::string BinFuncName() const override { return "VDB"; }
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>& ) override;
+};
+
+class OpXirr: public CheckVariables
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+
+ virtual std::string BinFuncName() const override { return "Xirr"; }
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/opencl/op_logical.cxx b/sc/source/core/opencl/op_logical.cxx
new file mode 100644
index 000000000..b9c70a1c7
--- /dev/null
+++ b/sc/source/core/opencl/op_logical.cxx
@@ -0,0 +1,360 @@
+/* -*- 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 "op_logical.hxx"
+
+#include <formula/vectortoken.hxx>
+#include <sstream>
+
+using namespace formula;
+
+namespace sc::opencl {
+void OpAnd::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double t = 1,tmp=0;\n";
+ for(size_t j = 0; j< vSubArguments.size(); j++)
+ {
+ ss << " double tmp"<<j<<" = 1;\n";
+ FormulaToken *tmpCur0 = vSubArguments[j]->GetFormulaToken();
+ if(tmpCur0->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*pCurDVR= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+ ss<< " int buffer_len"<<j<<" = "<<pCurDVR->GetArrayLength();
+ ss<< ";\n";
+ ss <<" if(gid0 >= buffer_len"<<j<<" || isnan(";
+ ss <<vSubArguments[j]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" tmp = 1;\n else\n";
+ ss <<" tmp = ";
+ ss <<vSubArguments[j]->GenSlidingWindowDeclRef()<<";\n";
+ ss <<" tmp"<<j<<" = tmp"<<j<<" && tmp;\n";
+ }
+ else if(tmpCur0->GetType() == formula::svDouble)
+ {
+ ss <<" tmp = ";
+ ss <<vSubArguments[j]->GenSlidingWindowDeclRef()<<";\n";
+ ss <<" tmp"<<j<<" = tmp"<<j<<" && tmp;\n";
+ }
+ else if(tmpCur0->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken*pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur0);
+ size_t nCurWindowSize = pCurDVR->GetArrayLength() <
+ pCurDVR->GetRefRowSize() ? pCurDVR->GetArrayLength():
+ pCurDVR->GetRefRowSize() ;
+ ss << " for(int i = ";
+ if (!pCurDVR->IsStartFixed() && pCurDVR->IsEndFixed()) {
+ ss << "gid0; i < " << nCurWindowSize << "; i++) {\n";
+ }
+ else if(pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed()){
+ ss << "0; i < gid0 + " << nCurWindowSize << "; i++) {\n";
+ }
+ else{
+ ss << "0; i < " << nCurWindowSize << "; i++) {\n";
+ }
+ if(!pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed())
+ {
+ ss <<" if(isnan("<<vSubArguments[j]->GenSlidingWindowDeclRef();
+ ss <<")||i+gid0>="<<pCurDVR->GetArrayLength();
+ ss <<")\n";
+ ss <<" tmp = 1;\n else\n";
+ }
+ else
+ {
+ ss <<" if(isnan("<<vSubArguments[j]->GenSlidingWindowDeclRef();
+ ss <<")||i>="<<pCurDVR->GetArrayLength();
+ ss <<")\n";
+ ss <<" tmp = 1;\n else\n";
+ }
+ ss <<" tmp = ";
+ ss <<vSubArguments[j]->GenSlidingWindowDeclRef()<<";\n";
+ ss <<" tmp"<<j<<" = tmp"<<j<<" && tmp;\n";
+ ss <<" }\n";
+ }
+ else
+ {
+ ss <<" tmp"<<j<<" = ";
+ ss <<vSubArguments[j]->GenSlidingWindowDeclRef()<<";\n";
+ }
+ ss <<" t = t && tmp"<<j<<";\n";
+ }
+ ss << " return t;\n";
+ ss << "}\n";
+}
+
+void OpOr::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double t = 0,tmp=0;\n";
+ for(size_t j = 0; j< vSubArguments.size(); j++)
+ {
+ ss << " double tmp"<<j<<" = 0;\n";
+ FormulaToken *tmpCur0 = vSubArguments[j]->GetFormulaToken();
+ if(tmpCur0->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*pCurDVR= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+ ss<< " int buffer_len"<<j<<" = "<<pCurDVR->GetArrayLength();
+ ss<< ";\n";
+ ss <<" if(gid0 >= buffer_len"<<j<<" || isnan(";
+ ss <<vSubArguments[j]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" tmp = 0;\n else\n";
+ ss <<" tmp = ";
+ ss <<vSubArguments[j]->GenSlidingWindowDeclRef()<<";\n";
+ ss <<" tmp"<<j<<" = tmp"<<j<<" || tmp;\n";
+ }
+ else if(tmpCur0->GetType() == formula::svDouble)
+ {
+ ss <<" tmp = ";
+ ss <<vSubArguments[j]->GenSlidingWindowDeclRef()<<";\n";
+ ss <<" tmp"<<j<<" = tmp"<<j<<" || tmp;\n";
+ }
+ else if(tmpCur0->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken*pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur0);
+ size_t nCurWindowSize = pCurDVR->GetArrayLength() <
+ pCurDVR->GetRefRowSize() ? pCurDVR->GetArrayLength():
+ pCurDVR->GetRefRowSize() ;
+ ss << " for(int i = ";
+ if (!pCurDVR->IsStartFixed() && pCurDVR->IsEndFixed()) {
+ ss << "gid0; i < " << nCurWindowSize << "; i++) {\n";
+ }
+ else if(pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed()){
+ ss << "0; i < gid0 + " << nCurWindowSize << "; i++) {\n";
+ }
+ else{
+ ss << "0; i < " << nCurWindowSize << "; i++) {\n";
+ }
+ if(!pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed())
+ {
+ ss <<" if(isnan("<<vSubArguments[j]->GenSlidingWindowDeclRef();
+ ss <<")||i+gid0>="<<pCurDVR->GetArrayLength();
+ ss <<")\n";
+ ss <<" tmp = 0;\n else\n";
+ }
+ else
+ {
+ ss <<" if(isnan("<<vSubArguments[j]->GenSlidingWindowDeclRef();
+ ss <<")||i>="<<pCurDVR->GetArrayLength();
+ ss <<")\n";
+ ss <<" tmp = 0;\n else\n";
+ }
+ ss <<" tmp = ";
+ ss <<vSubArguments[j]->GenSlidingWindowDeclRef()<<";\n";
+ ss <<" tmp"<<j<<" = tmp"<<j<<" || tmp;\n";
+ ss <<" }\n";
+ }
+ ss <<" t = t || tmp"<<j<<";\n";
+ }
+ ss << " return t;\n";
+ ss << "}\n";
+}
+void OpNot::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double tmp=0;\n";
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ if(tmpCur0->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*pCurDVR= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+ ss <<" if(gid0 >= "<<pCurDVR->GetArrayLength()<<" || isnan(";
+ ss <<vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" tmp = 0;\n else\n";
+ ss <<" tmp = ";
+ ss <<vSubArguments[0]->GenSlidingWindowDeclRef()<<";\n";
+ ss <<" tmp = (tmp == 0.0);\n";
+ }
+ else if(tmpCur0->GetType() == formula::svDouble)
+ {
+ ss <<" tmp = ";
+ ss <<vSubArguments[0]->GenSlidingWindowDeclRef()<<";\n";
+ ss <<" tmp = (tmp == 0.0);\n";
+ }
+ ss << " return tmp;\n";
+ ss << "}\n";
+}
+
+void OpXor::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " int t = 0,tmp0 = 0;\n";
+ ss << " double tmp = 0;\n";
+ for(DynamicKernelArgumentRef & rArg : vSubArguments)
+ {
+ FormulaToken *tmpCur0 = rArg->GetFormulaToken();
+ if(tmpCur0->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*pCurDVR= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+ ss <<" if(gid0 >= "<<pCurDVR->GetArrayLength()<<" || isnan(";
+ ss <<rArg->GenSlidingWindowDeclRef();
+ ss <<"))\n";
+ ss <<" tmp = 0;\n else\n";
+ ss <<" tmp = ";
+ ss <<rArg->GenSlidingWindowDeclRef()<<";\n";
+ ss <<" tmp0 = (tmp != 0);\n";
+ ss <<" t = t ^tmp0;\n";
+ }
+ else if(tmpCur0->GetType() == formula::svDouble)
+ {
+ ss <<" tmp = ";
+ ss <<rArg->GenSlidingWindowDeclRef()<<";\n";
+ ss <<" tmp0 = (tmp != 0);\n";
+ ss <<" t = t ^tmp0;\n";
+ }
+ else if(tmpCur0->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken*pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur0);
+ size_t nCurWindowSize = pCurDVR->GetArrayLength() <
+ pCurDVR->GetRefRowSize() ? pCurDVR->GetArrayLength():
+ pCurDVR->GetRefRowSize() ;
+ ss << " for(int i = ";
+ if (!pCurDVR->IsStartFixed() && pCurDVR->IsEndFixed()) {
+ ss << "gid0; i < " << nCurWindowSize << "; i++) {\n";
+ }
+ else if(pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed()){
+ ss << "0; i < gid0 + " << nCurWindowSize << "; i++) {\n";
+ }
+ else{
+ ss << "0; i < " << nCurWindowSize << "; i++) {\n";
+ }
+ if(!pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed())
+ {
+ ss <<" if(isnan("<<rArg->GenSlidingWindowDeclRef();
+ ss <<")||i+gid0>="<<pCurDVR->GetArrayLength();
+ ss <<")\n";
+ ss <<" tmp = 0;\n else\n";
+ }
+ else
+ {
+ ss <<" if(isnan("<<rArg->GenSlidingWindowDeclRef();
+ ss <<")||i>="<<pCurDVR->GetArrayLength();
+ ss <<")\n";
+ ss <<" tmp = 0;\n else\n";
+ }
+ ss <<" tmp = ";
+ ss <<rArg->GenSlidingWindowDeclRef()<<";\n";
+ ss <<" tmp0 = (tmp != 0);\n";
+ ss <<" t = t ^tmp0;\n";
+ ss <<" }\n";
+ }
+ }
+ ss << " return t;\n";
+ ss << "}\n";
+}
+void OpIf::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ if(tmpCur0->GetType() == formula::svDoubleVectorRef)
+ {
+ throw UnhandledToken("unknown operand for ocPush", __FILE__, __LINE__);
+ }
+ if(vSubArguments.size()==3)
+ {
+ ss << " if(isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ")|| ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << " == 0)\n";
+ ss << " return ";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " else";
+ ss <<" return ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ }
+ if(vSubArguments.size()==2)
+ {
+ ss << " if(isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ")|| ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << " == 0)\n";
+ ss << " return 0;\n";
+ ss << " else";
+ ss <<" return ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ }
+ if(vSubArguments.size()==1)
+ {
+ ss << " if(isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ")|| ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << " == 0)\n";
+ ss << " return 0;\n";
+ ss << " else";
+ ss <<" return 1;\n";
+ }
+ ss << "}\n";
+}
+
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/opencl/op_logical.hxx b/sc/source/core/opencl/op_logical.hxx
new file mode 100644
index 000000000..2f04cae6a
--- /dev/null
+++ b/sc/source/core/opencl/op_logical.hxx
@@ -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/.
+ */
+
+#pragma once
+
+#include "opbase.hxx"
+
+namespace sc::opencl {
+
+class OpAnd: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "And"; }
+ virtual bool canHandleMultiVector() const override { return true; }
+};
+
+class OpOr: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Or"; }
+ virtual bool canHandleMultiVector() const override { return true; }
+};
+class OpNot: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Not"; }
+};
+class OpXor: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Xor"; }
+ virtual bool canHandleMultiVector() const override { return true; }
+};
+class OpIf:public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "IF"; }
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/opencl/op_math.cxx b/sc/source/core/opencl/op_math.cxx
new file mode 100644
index 000000000..4042e2511
--- /dev/null
+++ b/sc/source/core/opencl/op_math.cxx
@@ -0,0 +1,3207 @@
+/* -*- 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 "op_math.hxx"
+
+#include <formula/vectortoken.hxx>
+#include "opinlinefun_math.hxx"
+#include <sstream>
+
+using namespace formula;
+
+namespace sc::opencl {
+
+void OpCos::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i) ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double arg0 = 0.0f;\n";
+ FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ assert(tmpCur);
+ if(ocPush == vSubArguments[0]->GetFormulaToken()->GetOpCode())
+ {
+ if(tmpCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR=
+ static_cast
+ <const formula::SingleVectorRefToken *>(tmpCur);
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ")||(gid0>=";
+ ss << tmpCurDVR->GetArrayLength();
+ ss << "))\n";
+ ss << " { arg0 = 0.0f; }\n";
+ }
+ else if(tmpCur->GetType() == formula::svDouble)
+ {
+ ss << " arg0=" << tmpCur->GetDouble() << ";\n";
+ }
+ }
+ else
+ {
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ ss << " return cos(arg0);\n";
+ ss << "}";
+
+}
+void OpSec::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur);
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss <<" int gid0=get_global_id(0);\n";
+ ss <<" double arg0 = " << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ ss<<" if(isnan(arg0)||(gid0>=";
+ ss<<tmpCurDVR->GetArrayLength();
+ ss<<"))\n";
+ ss<<" arg0 = 0;\n";
+ ss << " return pow(cos(arg0),-1 );\n";
+ ss << "}";
+}
+void OpCosh::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(local_coshDecl);
+ funs.insert(local_cosh);
+}
+void OpSecH::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur);
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss <<" int gid0=get_global_id(0);\n";
+ ss <<" double arg0 = " << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ ss<<" if(isnan(arg0)||(gid0>=";
+ ss<<tmpCurDVR->GetArrayLength();
+ ss<<"))\n";
+ ss<<" arg0 = 0;\n";
+ ss << " return pow(cosh(arg0),-1 );\n";
+ ss << "}";
+}
+void OpMROUND::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ CHECK_PARAMETER_COUNT(2, 2);
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ", ";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss<<") {\n";
+ ss<<" double tmp = 0;\n";
+ ss<<" int gid0 = get_global_id(0);\n";
+ ss<<" double arg0=0;\n";
+ ss<<" double arg1=0;\n";
+ ss <<"\n ";
+ //while (i-- > 1)
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << "if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << "{\n";
+ }
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " tmp=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (isnan(tmp))\n";
+ ss << " arg"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " arg"<<i<<"=tmp;\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss<<" arg"<<i<<"="<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss<<";\n";
+ }
+ }
+ ss<<" if(arg1==0)\n";
+ ss<<" return arg1;\n";
+ ss<<" tmp=arg1 * round(arg0 * pow(arg1,-1));\n";
+ ss<<" return tmp;\n";
+ ss<<"}";
+}
+void OpCosh::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur);
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n{\n";
+ ss <<" int gid0=get_global_id(0);\n";
+ ss << " double arg0 = " << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss<< " if(isnan(arg0)||(gid0>=";
+ ss<<tmpCurDVR->GetArrayLength();
+ ss<<"))\n";
+ ss<<" arg0 = 0;\n";
+ ss << " double tmp=local_cosh(arg0);\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+
+void OpCot::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double arg0 = 0.0f;\n";
+ FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ assert(tmpCur);
+ if(ocPush == vSubArguments[0]->GetFormulaToken()->GetOpCode())
+ {
+ if(tmpCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR=
+ static_cast
+ <const formula::SingleVectorRefToken *>(tmpCur);
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ")||(gid0>=";
+ ss << tmpCurDVR->GetArrayLength();
+ ss << "))\n";
+ ss << " { arg0 = 0.0f; }\n";
+ }
+ else if(tmpCur->GetType() == formula::svDouble)
+ {
+ ss << " arg0=";
+ ss << tmpCur->GetDouble() << ";\n";
+ }
+ }
+ else
+ {
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ ss << " arg0 = arg0 * M_1_PI;\n";
+ ss << " return cospi(arg0) * pow(sinpi(arg0), -1);\n";
+ ss << "}";
+}
+
+void OpCoth::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(local_cothDecl);
+ funs.insert(local_coth);
+}
+
+void OpCoth::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur);
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n{\n";
+ ss <<" int gid0=get_global_id(0);\n";
+ ss << " double arg0 = " << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss<< " if(isnan(arg0)||(gid0>=";
+ ss<<tmpCurDVR->GetArrayLength();
+ ss<<"))\n";
+ ss<<" arg0 = 0;\n";
+ ss << " double tmp=local_coth(arg0);\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+
+void OpCombinA::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(bikDecl);
+ funs.insert(bik);
+}
+
+void OpCombinA::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n{\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double tem;\n";
+ ss << " double arg0,arg1;\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ ss << " arg"<<i<<" = "<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ if(pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if(isnan(arg" << i <<")||(gid0 >= ";
+ ss << pSVR->GetArrayLength();
+ ss << "))\n";
+ ss << " arg" << i << " = 0;\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << " if(isnan(arg" << i <<"))\n";
+ ss << " arg" << i << " = 0;\n";
+ }
+ }
+ ss << " arg0 = trunc(arg0);\n";
+ ss << " arg1 = trunc(arg1);\n";
+ ss << " if(arg0 >= arg1 && arg0 > 0 && arg1 > 0)\n";
+ ss << " tem = bik(arg0+arg1-1,arg1);\n";
+ ss << " else if(arg0 == 0 && arg1 == 0)\n";
+ ss << " tem = 0;\n";
+ ss << " else if(arg0 > 0 && arg1 == 0)\n";
+ ss << " tem = 1;\n";
+ ss << " else\n";
+ ss << " tem = -1;\n";
+ ss << " double i = tem - trunc(tem);\n";
+ ss << " if(i < 0.5)\n";
+ ss << " tem = trunc(tem);\n";
+ ss << " else\n";
+ ss << " tem = trunc(tem) + 1;\n";
+ ss << " return tem;\n";
+ ss << "}";
+}
+void OpEven::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur);
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n{\n";
+ ss <<" int gid0=get_global_id(0);\n";
+ ss << " double arg0 = " << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss<< " if(isnan(arg0)||(gid0>=";
+ ss<<tmpCurDVR->GetArrayLength();
+ ss<<"))\n";
+ ss<<" arg0 = 0;\n";
+ ss << " double tmp;\n";
+ ss << " tmp = fabs(arg0 / 2);\n";
+ ss << " if ( trunc(tmp) == tmp )\n";
+ ss << " tmp = tmp * 2;\n";
+ ss << " else\n";
+ ss << " tmp = (trunc(tmp) + 1) * 2;\n";
+ ss << " if (arg0 < 0)\n";
+ ss << " tmp = tmp * -1.0;\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+
+void OpMod::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n{\n";
+ ss <<" int gid0=get_global_id(0);\n";
+ ss << " double arg0 = " << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " double arg1 =" << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(isnan(arg0)||arg0 == 0)\n";
+ ss << " return 0;\n";
+ ss << " if(isnan(arg1) || arg1 ==0)\n";
+ ss << " return NAN;\n";
+ ss << " double tem;\n";
+ ss << " if(arg0 < 0 && arg1 > 0)\n";
+ ss << " while(arg0 < 0)\n";
+ ss << " arg0 += arg1;\n";
+ ss << " else if (arg0 > 0 && arg1 < 0)\n";
+ ss << " while(arg0 > 0)\n";
+ ss << " arg0 += arg1;\n";
+ ss << " tem = fmod(arg0,arg1);\n";
+ ss << " if(arg1 < 0 && tem > 0)\n";
+ ss << " tem = -tem;\n";
+ ss << " return tem;\n";
+ ss << "}";
+}
+void OpLog::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n{\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double tem;\n";
+ ss << " double arg0,arg1;\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ ss << " arg"<<i<<" = "<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ if(pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if(isnan(arg" << i <<")||(gid0 >= ";
+ ss << pSVR->GetArrayLength();
+ ss << "))\n";
+ if( i == 0)
+ ss << " arg0 = 0;\n";
+ else if ( i == 1)
+ ss << " arg1 = 10;\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << " if(isnan(arg" << i <<"))\n";
+ if( i == 0)
+ ss << " arg0 = 0;\n";
+ else if ( i == 1)
+ ss << " arg1 = 10;\n";
+ }
+ }
+ if (vSubArguments.size() < 2)
+ ss << " arg1 = 10;\n";
+ ss << " tem = log10(arg0)/log10(arg1);;\n";
+ ss << " return tem;\n";
+ ss << "}";
+}
+void OpCsc::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &vSubArguments)
+{
+ FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur);
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n{\n\t";
+ ss <<"int gid0=get_global_id(0);\n\t";
+ ss << "double arg0 = " << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n\t";
+ ss<< "if(isnan(arg0)||(gid0>=";
+ ss<<tmpCurDVR->GetArrayLength();
+ ss<<"))\n\t\t";
+ ss<<"arg0 = 0;\n\t";
+ ss << "double tmp=1/sin(arg0);\n\t";
+ ss << "return tmp;\n";
+ ss << "}";
+}
+void OpCountIfs::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur);
+ size_t nCurWindowSize = pCurDVR->GetArrayLength() <
+ pCurDVR->GetRefRowSize() ? pCurDVR->GetArrayLength():
+ pCurDVR->GetRefRowSize() ;
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n {\n";
+ ss <<" int gid0=get_global_id(0);\n";
+ ss << " int tmp =0;\n";
+ ss << " int loop;\n";
+ GenTmpVariables(ss,vSubArguments);
+
+ ss<< " int singleIndex =gid0;\n";
+ int m=0;
+
+ std::stringstream tmpss;
+
+ for(size_t j=0;j<vSubArguments.size();j+=2,m++)
+ {
+ CheckSubArgumentIsNan(tmpss,vSubArguments,j);
+ CheckSubArgumentIsNan(ss,vSubArguments,j+1);
+ tmpss <<" if(isequal(";
+ tmpss <<"tmp";
+ tmpss <<j;
+ tmpss <<" , ";
+ tmpss << "tmp";
+ tmpss << j+1;
+ tmpss << ")){\n";
+ }
+ tmpss << " tmp ++;\n";
+ for(size_t j=0;j<vSubArguments.size();j+=2,m--)
+ {
+ for(int n = 0;n<m+1;n++)
+ {
+ tmpss << " ";
+ }
+ tmpss<< "}\n";
+ }
+ UnrollDoubleVector(ss,tmpss,pCurDVR,nCurWindowSize);
+
+ ss << "return tmp;\n";
+ ss << "}";
+}
+void OpSumIfs::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur);
+ size_t nCurWindowSize = pCurDVR->GetArrayLength() <
+ pCurDVR->GetRefRowSize() ? pCurDVR->GetArrayLength():
+ pCurDVR->GetRefRowSize() ;
+
+ mNeedReductionKernel = vSubArguments[0]->NeedParallelReduction();
+ if (mNeedReductionKernel)
+ {
+ // generate reduction functions
+
+ ss << "__kernel void ";
+ ss << vSubArguments[0]->GetName();
+ ss << "_SumIfs_reduction( ";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ", __global double *result,int arrayLength,int windowSize";
+
+ ss << ")\n{\n";
+ ss << " double tmp =0;\n";
+ ss << " int i ;\n";
+
+ GenTmpVariables(ss,vSubArguments);
+ ss << " double current_result = 0.0;\n";
+ ss << " int writePos = get_group_id(1);\n";
+ if (pCurDVR->IsStartFixed() && pCurDVR->IsEndFixed())
+ ss << " int offset = 0;\n";
+ else if (!pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed())
+ ss << " int offset = get_group_id(1);\n";
+ else
+ throw Unhandled(__FILE__, __LINE__);
+ // actually unreachable
+ ss << " int lidx = get_local_id(0);\n";
+ ss << " __local double shm_buf[256];\n";
+ ss << " barrier(CLK_LOCAL_MEM_FENCE);\n";
+ ss << " int loop = arrayLength/512 + 1;\n";
+ ss << " for (int l=0; l<loop; l++){\n";
+ ss << " tmp = 0.0;\n";
+ ss << " int loopOffset = l*512;\n";
+
+ ss << " int p1 = loopOffset + lidx + offset, p2 = p1 + 256;\n";
+ ss << " if (p2 < min(offset + windowSize, arrayLength)) {\n";
+ ss << " tmp0 = 0.0;\n";
+ int mm=0;
+ std::string p1 = "p1";
+ std::string p2 = "p2";
+ for(size_t j=1;j<vSubArguments.size();j+=2,mm++)
+ {
+ CheckSubArgumentIsNan2(ss,vSubArguments,j,p1);
+ CheckSubArgumentIsNan2(ss,vSubArguments,j+1,p1);
+ ss << "";
+ ss <<" if(isequal(";
+ ss <<"tmp";
+ ss <<j;
+ ss <<" , ";
+ ss << "tmp";
+ ss << j+1;
+ ss << "))";
+ ss << "{\n";
+ }
+ CheckSubArgumentIsNan2(ss,vSubArguments,0,p1);
+ ss << " tmp += tmp0;\n";
+ for(size_t j=1;j<vSubArguments.size();j+=2,mm--)
+ {
+ for(int n = 0;n<mm+1;n++)
+ {
+ ss << " ";
+ }
+ ss<< "}\n\n";
+ }
+ mm=0;
+ for(size_t j=1;j<vSubArguments.size();j+=2,mm++)
+ {
+ CheckSubArgumentIsNan2(ss,vSubArguments,j,p2);
+ CheckSubArgumentIsNan2(ss,vSubArguments,j+1,p2);
+ ss <<" if(isequal(";
+ ss <<"tmp";
+ ss <<j;
+ ss <<" , ";
+ ss << "tmp";
+ ss << j+1;
+ ss << ")){\n";
+ }
+ CheckSubArgumentIsNan2(ss,vSubArguments,0,p2);
+ ss << " tmp += tmp0;\n";
+ for(size_t j=1;j< vSubArguments.size();j+=2,mm--)
+ {
+ for(int n = 0;n<mm+1;n++)
+ {
+ ss << " ";
+ }
+ ss<< "}\n";
+ }
+ ss << " }\n";
+
+ ss << " else if (p1 < min(arrayLength, offset + windowSize)) {\n";
+ mm=0;
+ for(size_t j=1;j<vSubArguments.size();j+=2,mm++)
+ {
+ CheckSubArgumentIsNan2(ss,vSubArguments,j,p1);
+ CheckSubArgumentIsNan2(ss,vSubArguments,j+1,p1);
+
+ ss <<" if(isequal(";
+ ss <<"tmp";
+ ss <<j;
+ ss <<" , ";
+ ss << "tmp";
+ ss << j+1;
+ ss << ")){\n";
+ }
+ CheckSubArgumentIsNan2(ss,vSubArguments,0,p1);
+ ss << " tmp += tmp0;\n";
+ for(size_t j=1;j<vSubArguments.size();j+=2,mm--)
+ {
+ for(int n = 0;n<mm+1;n++)
+ {
+ ss << " ";
+ }
+ ss<< "}\n\n";
+ }
+
+ ss << " }\n";
+ ss << " shm_buf[lidx] = tmp;\n";
+ ss << " barrier(CLK_LOCAL_MEM_FENCE);\n";
+ ss << " for (int i = 128; i >0; i/=2) {\n";
+ ss << " if (lidx < i)\n";
+ ss << " shm_buf[lidx] += shm_buf[lidx + i];\n";
+ ss << " barrier(CLK_LOCAL_MEM_FENCE);\n";
+ ss << " }\n";
+ ss << " if (lidx == 0)\n";
+ ss << " current_result += shm_buf[0];\n";
+ ss << " barrier(CLK_LOCAL_MEM_FENCE);\n";
+ ss << " }\n";
+
+ ss << " if (lidx == 0)\n";
+ ss << " result[writePos] = current_result;\n";
+ ss << "}\n";
+ }// finish generate reduction code
+ // generate functions as usual
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n {\n";
+ ss <<" int gid0=get_global_id(0);\n";
+ ss << " double tmp =0;\n";
+ if (!mNeedReductionKernel)
+ {
+ ss << " int i ;\n";
+ GenTmpVariables(ss,vSubArguments);
+ ss << " for (i = ";
+ if (!pCurDVR->IsStartFixed() && pCurDVR->IsEndFixed()) {
+ ss << "gid0; i < "<< nCurWindowSize <<"; i++)\n";
+ } else if (pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed()) {
+ ss << "0; i < gid0+"<< nCurWindowSize <<"; i++)\n";
+ } else {
+ ss << "0; i < "<< nCurWindowSize <<"; i++)\n";
+ }
+ ss << " {\n";
+ if(!pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed())
+ {
+ ss<< " int doubleIndex =i+gid0;\n";
+ }else
+ {
+ ss<< " int doubleIndex =i;\n";
+ }
+ ss<< " int singleIndex =gid0;\n";
+ int m=0;
+ for(size_t j=1;j<vSubArguments.size();j+=2,m++)
+ {
+ CheckSubArgumentIsNan(ss,vSubArguments,j);
+ CheckSubArgumentIsNan(ss,vSubArguments,j+1);
+ ss <<" if(isequal(";
+ ss <<"tmp";
+ ss <<j;
+ ss <<" , ";
+ ss << "tmp";
+ ss << j+1;
+ ss << ")){\n";
+ }
+ CheckSubArgumentIsNan(ss,vSubArguments,0);
+ ss << " tmp += tmp0;\n";
+ for(size_t j=1;j<=vSubArguments.size();j+=2,m--)
+ {
+ for(int n = 0;n<m+1;n++)
+ {
+ ss << " ";
+ }
+ ss<< "}\n";
+ }
+ }
+ if (mNeedReductionKernel)
+ {
+ ss << "tmp =";
+ vSubArguments[0]->GenDeclRef(ss);
+ ss << "[gid0];\n";
+ }
+ ss << "return tmp;\n";
+ ss << "}";
+}
+void OpCscH::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &vSubArguments)
+{
+ FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur);
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n{\n\t";
+ ss <<"int gid0=get_global_id(0);\n\t";
+ ss << "double arg0 = " << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n\t";
+ ss<< "if(isnan(arg0)||(gid0>=";
+ ss<<tmpCurDVR->GetArrayLength();
+ ss<<"))\n\t\t";
+ ss<<"arg0 = 0;\n\t";
+ ss << "double tmp=1/sinh(arg0);\n\t";
+ ss << "return tmp;\n";
+ ss << "}";
+}
+void OpExp::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i) ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double arg0 = 0.0f;\n";
+ FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ assert(tmpCur);
+ if(ocPush == vSubArguments[0]->GetFormulaToken()->GetOpCode())
+ {
+ if(tmpCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR=
+ static_cast
+ <const formula::SingleVectorRefToken *>(tmpCur);
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ")||(gid0>=";
+ ss << tmpCurDVR->GetArrayLength();
+ ss << "))\n";
+ ss << " { arg0 = 0.0f; }\n";
+ }
+ else if(tmpCur->GetType() == formula::svDouble)
+ {
+ ss << " arg0=" << tmpCur->GetDouble() << ";\n";
+ }
+ }
+ else
+ {
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ ss << " return pow(M_E, arg0);\n";
+ ss << "}";
+}
+
+void OpAverageIfs::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur);
+ size_t nCurWindowSize = pCurDVR->GetArrayLength() <
+ pCurDVR->GetRefRowSize() ? pCurDVR->GetArrayLength():
+ pCurDVR->GetRefRowSize() ;
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n {\n";
+ ss <<" int gid0=get_global_id(0);\n";
+ ss << " double tmp =0;\n";
+ ss << " int count=0;\n";
+ ss << " int loop;";
+ GenTmpVariables(ss,vSubArguments);
+ ss<< " int singleIndex =gid0;\n";
+ int m=0;
+ std::stringstream tmpss;
+ for(size_t j=1;j<vSubArguments.size();j+=2,m++)
+ {
+ CheckSubArgumentIsNan(tmpss,vSubArguments,j);
+ CheckSubArgumentIsNan(ss,vSubArguments,j+1);
+ tmpss <<" if(isequal(";
+ tmpss <<"tmp";
+ tmpss <<j;
+ tmpss <<" , ";
+ tmpss << "tmp";
+ tmpss << j+1;
+ tmpss << ")){\n";
+ }
+ CheckSubArgumentIsNan(tmpss,vSubArguments,0);
+ tmpss << " tmp += tmp0;\n";
+ tmpss << " count++;\n";
+ for(size_t j=1;j<vSubArguments.size();j+=2,m--)
+ {
+ for(int n = 0;n<m+1;n++)
+ {
+ tmpss << " ";
+ }
+ tmpss<< "}\n";
+ }
+
+ UnrollDoubleVector(ss,tmpss,pCurDVR,nCurWindowSize);
+
+ ss << " if(count!=0)\n";
+ ss << " tmp=tmp/count;\n";
+ ss << " else\n";
+ ss << " tmp= 0 ;\n";
+ ss << "return tmp;\n";
+ ss << "}";
+}
+
+void OpLog10::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur);
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n{\n\t";
+ ss <<"int gid0=get_global_id(0);\n\t";
+ ss << "double arg0 = " << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n\t";
+ ss<< "if(isnan(arg0)||(gid0>=";
+ ss<<tmpCurDVR->GetArrayLength();
+ ss<<"))\n\t\t";
+ ss<<"arg0 = 0;\n\t";
+ ss << "double tmp=log10(arg0);\n\t";
+ ss << "return tmp;\n";
+ ss << "}";
+}
+
+void OpSinh::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss <<") {\n";
+ ss <<" int gid0=get_global_id(0);\n";
+ ss <<" double arg0 = " <<
+ vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ ss<< " if(isnan(arg0))\n";
+ ss<<" arg0 = 0;\n";
+ ss << " double tmp=( exp(arg0)-exp(-arg0) )/2;\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+void OpSin::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i) ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double arg0 = 0.0f;\n";
+ FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ assert(tmpCur);
+ if(ocPush == vSubArguments[0]->GetFormulaToken()->GetOpCode())
+ {
+ if(tmpCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR=
+ static_cast
+ <const formula::SingleVectorRefToken *>(tmpCur);
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ")||(gid0>=";
+ ss << tmpCurDVR->GetArrayLength();
+ ss << "))\n";
+ ss << " { arg0 = 0.0f; }\n";
+ }
+ else if(tmpCur->GetType() == formula::svDouble)
+ {
+ ss << " arg0=" << tmpCur->GetDouble() << ";\n";
+ }
+ }
+ else
+ {
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ ss << " arg0 = arg0 * M_1_PI;\n";
+ ss << " return sinpi(arg0);\n";
+ ss << "}";
+}
+
+void OpAbs::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double tmp = " << GetBottom() << ";\n";
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR0=
+ static_cast<const formula::SingleVectorRefToken *>(tmpCur0);
+ ss << " int buffer_len = ";
+ ss << tmpCurDVR0->GetArrayLength();
+ ss << ";\n";
+ ss << " if((gid0)>=buffer_len || isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " tmp = " << GetBottom() << ";\n else \n";
+ ss << " tmp = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " return fabs(tmp);\n";
+ ss << "}";
+}
+void OpArcCos::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(atan2Decl);
+ funs.insert(atan2Content);
+}
+void OpArcCos::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i) ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double tmp = " << GetBottom() << ";\n";
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR0=
+ static_cast<const formula::SingleVectorRefToken *>(tmpCur0);
+ ss << " int buffer_len = "<< tmpCurDVR0->GetArrayLength()<< ";\n";
+ ss << " if((gid0)>=buffer_len || isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef()<< "))\n";
+ ss << " tmp = " << GetBottom() << ";\n";
+ ss << " else \n ";
+ ss << " tmp = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef()<< ";\n";
+ ss << " return arctan2(sqrt(1.0 - pow(tmp, 2)), tmp);\n";
+ ss << "}";
+}
+void OpArcCosHyp::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i) ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double tmp = " << GetBottom() << ";\n";
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ assert(tmpCur0);
+ if(ocPush == vSubArguments[0]->GetFormulaToken()->GetOpCode())
+ {
+ if(tmpCur0->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR0=
+ static_cast<const formula::SingleVectorRefToken *>(tmpCur0);
+ ss << " int buffer_len = "<<tmpCurDVR0->GetArrayLength()<<";\n";
+ ss << " if((gid0)>=buffer_len || isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << "))\n";
+ ss << " tmp = " << GetBottom() << ";\n";
+ ss << " else \n ";
+ ss << " tmp = " << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ else if(tmpCur0->GetType() == formula::svDouble)
+ {
+ ss << " tmp = " << tmpCur0->GetDouble() << ";\n";
+ }
+ }
+ else
+ {
+ ss << " tmp = " << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+
+ ss << " return log( tmp + pow( (pown(tmp, 2) - 1.0), 0.5));\n";
+ ss << "}";
+}
+void OpTan::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i) ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double arg0 = 0.0f;\n";
+ FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ assert(tmpCur);
+ if(ocPush == vSubArguments[0]->GetFormulaToken()->GetOpCode())
+ {
+ if(tmpCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR=
+ static_cast
+ <const formula::SingleVectorRefToken *>(tmpCur);
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ")||(gid0>=";
+ ss << tmpCurDVR->GetArrayLength();
+ ss << "))\n";
+ ss << " { arg0 = 0.0f; }\n";
+ }
+ else if(tmpCur->GetType() == formula::svDouble)
+ {
+ ss << " arg0=" << tmpCur->GetDouble() << ";\n";
+ }
+ }
+ else
+ {
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ ss << " arg0 = arg0 * M_1_PI;\n";
+ ss << " return sinpi(arg0) * pow(cospi(arg0), -1);\n";
+ ss << "}";
+}
+void OpTanH::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur);
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double arg0 = "<< vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(isnan(arg0)||(gid0>=";
+ ss << tmpCurDVR->GetArrayLength();
+ ss << "))\n";
+ ss << " arg0 = 0;\n";
+ ss << " double tmp=tanh(arg0);\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+void OpPower::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double arg[2];\n";
+ for( size_t i=0; i < vSubArguments.size(); ++i)
+ {
+ FormulaToken *tmpCur = vSubArguments[i]->GetFormulaToken();
+ assert(tmpCur);
+ if(ocPush == vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ if(tmpCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* tmpCurDVR =
+ static_cast<
+ const formula::DoubleVectorRefToken *>(tmpCur);
+ ss << " int i = 0;\n";
+ ss << " arg["<<i<<"] = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(isnan(arg["<<i;
+ ss << "])||(gid0>=";
+ ss << tmpCurDVR->GetArrayLength();
+ ss << "))\n";
+ ss << " arg["<<i;
+ ss << "] = 0;\n";
+ }
+ else if(tmpCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* tmpCurDVR=
+ static_cast<
+ const formula::SingleVectorRefToken *>(tmpCur);
+ ss << " arg["<<i<<"] = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(isnan(arg["<<i;
+ ss << "])||(gid0>=";
+ ss << tmpCurDVR->GetArrayLength();
+ ss << "))\n";
+ ss << " arg["<<i;
+ ss << "] = 0;\n";
+ }
+ else if(tmpCur->GetType() == formula::svDouble)
+ {
+ ss << " arg["<<i<<"] = ";
+ ss << tmpCur->GetDouble() << ";\n";
+ }
+ }
+ else
+ {
+ ss << " arg["<<i<<"] = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ }
+ ss << " return pow(arg[0],arg[1]);\n";
+ ss << "}";
+}
+void OpSqrt::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double arg0 = 0.0f;\n";
+ FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ assert(tmpCur);
+ if(ocPush == vSubArguments[0]->GetFormulaToken()->GetOpCode())
+ {
+ if(tmpCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR=
+ static_cast
+ <const formula::SingleVectorRefToken *>(tmpCur);
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ")||(gid0>=";
+ ss << tmpCurDVR->GetArrayLength();
+ ss << "))\n";
+ ss << " { arg0 = 0; }\n";
+ }
+ else if(tmpCur->GetType() == formula::svDouble)
+ {
+ ss << " arg0=";
+ ss << tmpCur->GetDouble() << ";\n";
+ }
+ else
+ {
+ throw Unhandled( __FILE__, __LINE__ );
+ }
+ }
+ else
+ {
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ ss << " if( arg0 < 0 )\n";
+ ss << " return CreateDoubleError(IllegalArgument);\n";
+ ss << " return sqrt(arg0);\n";
+ ss << "}";
+}
+void OpArcCot::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i) ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double tmp = " << GetBottom() << ";\n";
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR0=
+ static_cast<const formula::SingleVectorRefToken *>(tmpCur0);
+ ss << " int buffer_len = " << tmpCurDVR0->GetArrayLength()<< ";\n";
+ ss << " if((gid0)>=buffer_len || isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << "))\n";
+ ss << " tmp = " << GetBottom() << ";\n";
+ ss << " else \n ";
+ ss << " tmp = " << vSubArguments[0]->GenSlidingWindowDeclRef() <<";\n";
+ ss << " return M_PI_2 - atan(tmp);\n";
+ ss << "}";
+}
+void OpArcCotHyp::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i) ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double arg0 = 0.0f;\n";
+ FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ assert(tmpCur);
+ if(ocPush == vSubArguments[0]->GetFormulaToken()->GetOpCode())
+ {
+ if(tmpCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR=
+ static_cast
+ <const formula::SingleVectorRefToken *>(tmpCur);
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ")||(gid0>=";
+ ss << tmpCurDVR->GetArrayLength();
+ ss << "))\n";
+ ss << " { arg0 = 0.0f; }\n";
+ }
+ else if(tmpCur->GetType() == formula::svDouble)
+ {
+ ss << " arg0=";
+ ss << tmpCur->GetDouble() << ";\n";
+ }
+ }
+ else
+ {
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ ss << " return 0.5 * log(1 + 2 * pown(arg0 - 1.0, -1));\n";
+ ss << "}";
+}
+void OpArcSin::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(atan2Decl);
+ funs.insert(atan2Content);
+}
+void OpArcSin::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i) ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double tmp = " << GetBottom() << ";\n";
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR0=
+ static_cast<const formula::SingleVectorRefToken *>(tmpCur0);
+ ss << " int buffer_len = " << tmpCurDVR0->GetArrayLength() << ";\n";
+ ss << " if((gid0)>=buffer_len || isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << "))\n";
+ ss << " tmp = " << GetBottom() << ";\n";
+ ss << " else \n ";
+ ss << " tmp = " << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " return arctan2(tmp, sqrt(1.0 - pow(tmp, 2)));\n";
+ ss << "}";
+}
+void OpArcSinHyp::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i) ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double tmp = " << GetBottom() << ";\n";
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ assert(tmpCur0);
+ if(ocPush == vSubArguments[0]->GetFormulaToken()->GetOpCode())
+ {
+ if(tmpCur0->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR0=
+ static_cast<const formula::SingleVectorRefToken *>(tmpCur0);
+ ss << " int buffer_len = "<<tmpCurDVR0->GetArrayLength()<<";\n";
+ ss << " if((gid0)>=buffer_len || isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << "))\n";
+ ss << " tmp = " << GetBottom() << ";\n";
+ ss << " else \n ";
+ ss << " tmp = " << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ else if(tmpCur0->GetType() == formula::svDouble)
+ {
+ ss << " tmp = " << tmpCur0->GetDouble() << ";\n";
+ }
+ }
+ else
+ {
+ ss << " tmp = " << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ ss << " return log( tmp + pow((pown(tmp, 2) + 1.0), 0.5));\n";
+ ss << "}";
+}
+void OpArcTan2::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(atan2Decl);
+ funs.insert(atan2Content);
+}
+void OpArcTan2::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i) ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double x_num = " << GetBottom() << ";\n";
+ ss << " double y_num = " << GetBottom() << ";\n";
+ FormulaToken *iXNum = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVRX=
+ static_cast<const formula::SingleVectorRefToken *>(iXNum);
+ FormulaToken *iYNum = vSubArguments[1]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVRY=
+ static_cast<const formula::SingleVectorRefToken *>(iYNum);
+ ss << " int buffer_x_len = " << tmpCurDVRX->GetArrayLength() << ";\n";
+ ss << " int buffer_y_len = " << tmpCurDVRY->GetArrayLength() << ";\n";
+ ss << " if((gid0)>=buffer_x_len || isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << "))\n";
+ ss << " x_num = " << GetBottom() << ";\n";
+ ss << " else \n ";
+ ss << " x_num = "<< vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if((gid0)>=buffer_y_len || isnan(";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << "))\n";
+ ss << " y_num = " << GetBottom() << ";\n";
+ ss << " else \n ";
+ ss << " y_num = "<< vSubArguments[1]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " return arctan2(y_num, x_num);\n";
+ ss << "}";
+}
+void OpArcTan::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i) ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double arg0 = 0.0f;\n";
+ FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ assert(tmpCur);
+ if(ocPush == vSubArguments[0]->GetFormulaToken()->GetOpCode())
+ {
+ if(tmpCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR=
+ static_cast
+ <const formula::SingleVectorRefToken *>(tmpCur);
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ")||(gid0>=";
+ ss << tmpCurDVR->GetArrayLength();
+ ss << "))\n";
+ ss << " { arg0 = 0.0f; }\n";
+ }
+ else if(tmpCur->GetType() == formula::svDouble)
+ {
+ ss << " arg0=" << tmpCur->GetDouble() << ";\n";
+ }
+ }
+ else
+ {
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ ss << " return atan(arg0);\n";
+ ss << "}";
+}
+void OpArcTanH::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i) ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double tmp = " << GetBottom() << ";\n";
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR0=
+ static_cast<const formula::SingleVectorRefToken *>(tmpCur0);
+ ss << " int buffer_len = " << tmpCurDVR0->GetArrayLength() << ";\n";
+ ss << " if((gid0)>=buffer_len || isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << "))\n";
+ ss << " tmp = " << GetBottom() << ";\n";
+ ss << " else \n ";
+ ss << " tmp = " << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " double a = 1.0 + tmp;\n";
+ ss << " double b = 1.0 - tmp;\n";
+ ss << " return log(pow(a/b, 0.5));\n";
+ ss << "}";
+}
+void OpBitAnd::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i) ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double num1 = " << GetBottom() << ";\n";
+ ss << " double num2 = " << GetBottom() << ";\n";
+ FormulaToken *iNum1 = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken* tmpCurDVRNum1=
+ static_cast<const formula::SingleVectorRefToken *>(iNum1);
+ FormulaToken *iNum2 = vSubArguments[1]->GetFormulaToken();
+ const formula::SingleVectorRefToken* tmpCurDVRNum2=
+ static_cast<const formula::SingleVectorRefToken *>(iNum2);
+ ss << " int buffer_num1_len = "<<tmpCurDVRNum1->GetArrayLength()<<";\n";
+ ss << " int buffer_num2_len = "<<tmpCurDVRNum2->GetArrayLength()<<";\n";
+ ss << " if((gid0)>=buffer_num1_len || isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << "))\n";
+ ss << " num1 = " << GetBottom() << ";\n";
+ ss << " else \n ";
+ ss << " num1 = " << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if((gid0)>=buffer_num2_len || isnan(";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << "))\n";
+ ss << " num2 = " << GetBottom() << ";\n";
+ ss << " else \n ";
+ ss << " num2 = " << vSubArguments[1]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " return (long)num1 & (long)num2;\n";
+ ss << "}";
+}
+void OpLn::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " int singleIndex = gid0;\n";
+
+ GenTmpVariables(ss,vSubArguments);
+ CheckAllSubArgumentIsNan(ss,vSubArguments);
+
+ ss << " double tmp=log1p(tmp0-1);\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+
+void OpRound::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ CHECK_PARAMETER_COUNT( 1, 2 );
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " int singleIndex = gid0;\n";
+ GenTmpVariables(ss,vSubArguments);
+ CheckAllSubArgumentIsNan(ss,vSubArguments);
+ if(vSubArguments.size() ==2)
+ {
+ ss << " for(int i=0;i<tmp1;i++)\n";
+ ss << " tmp0 = tmp0 * 10;\n";
+ ss << " for(int i=0;i>tmp1;i--)\n";
+ ss << " tmp0 = tmp0 / 10;\n";
+ }
+ ss << " double tmp=round(tmp0);\n";
+ if(vSubArguments.size() ==2)
+ {
+ ss << " for(int i=0;i<tmp1;i++)\n";
+ ss << " tmp = tmp / 10;\n";
+ ss << " for(int i=0;i>tmp1;i--)\n";
+ ss << " tmp = tmp * 10;\n";
+ }
+ ss << " return tmp;\n";
+ ss << "}";
+}
+void OpRoundUp::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " int singleIndex = gid0;\n";
+ ss << " int intTmp;\n";
+ ss << " double doubleTmp;\n";
+ ss << " double tmp;\n";
+ GenTmpVariables(ss,vSubArguments);
+ CheckAllSubArgumentIsNan(ss,vSubArguments);
+ ss << " if(tmp1 >20 || tmp1 < -20)";
+ ss << " {\n";
+ ss << " tmp = NAN;\n";
+ ss << " }else\n";
+ ss << " {\n";
+ ss << " for(int i=0;i<tmp1;i++)\n";
+ ss << " tmp0 = tmp0 * 10;\n";
+ ss << " intTmp = (int)tmp0;\n";
+ ss << " doubleTmp = intTmp;\n";
+ ss << " if(isequal(doubleTmp,tmp0))\n";
+ ss << " tmp = doubleTmp;\n";
+ ss << " else\n";
+ ss << " tmp = doubleTmp + 1;\n";
+ ss << " for(int i=0;i<tmp1;i++)\n";
+ ss << " tmp = tmp / 10;\n";
+ ss << " }\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+void OpRoundDown::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " int singleIndex = gid0;\n";
+ ss << " int intTmp;\n";
+ ss << " double tmp;\n";
+ GenTmpVariables(ss,vSubArguments);
+ CheckAllSubArgumentIsNan(ss,vSubArguments);
+ ss << " if(tmp1 >20 || tmp1 < -20)";
+ ss << " {\n";
+ ss << " tmp = NAN;\n";
+ ss << " }else\n";
+ ss << " {\n";
+ ss << " for(int i=0;i<tmp1;i++)\n";
+ ss << " tmp0 = tmp0 * 10;\n";
+ ss << " intTmp = (int)tmp0;\n";
+ ss << " tmp = intTmp;\n";
+ ss << " for(int i=0;i<tmp1;i++)\n";
+ ss << " tmp = tmp / 10;\n";
+ ss << " }\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+void OpInt::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " int singleIndex = gid0;\n";
+ ss << " int intTmp;\n";
+ ss << " double tmp;\n";
+ GenTmpVariables(ss,vSubArguments);
+ CheckAllSubArgumentIsNan(ss,vSubArguments);
+ ss << " intTmp = (int)tmp0;\n";
+ ss << " tmp = intTmp;\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+void OpNegSub::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " int singleIndex = gid0;\n";
+ GenTmpVariables(ss,vSubArguments);
+ CheckAllSubArgumentIsNan(ss,vSubArguments);
+ ss << " return -tmp0;\n";
+ ss << "}";
+}
+
+void OpRadians::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " int singleIndex = gid0;\n";
+ ss << " double tmp;\n";
+ GenTmpVariables(ss,vSubArguments);
+ CheckAllSubArgumentIsNan(ss,vSubArguments);
+ ss << " tmp = tmp0 * 3.14159265358979 * pow(180.0,-1);\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+void OpIsEven::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " int singleIndex = gid0;\n";
+ ss << " double tmp;\n";
+ GenTmpVariables(ss,vSubArguments);
+ CheckAllSubArgumentIsNan(ss,vSubArguments);
+ ss << " tmp = (fmod(floor(fabs(tmp0)), 2.0)<0.5);\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+void OpIsOdd::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " int singleIndex = gid0;\n";
+ ss << " double tmp;\n";
+ GenTmpVariables(ss,vSubArguments);
+ CheckAllSubArgumentIsNan(ss,vSubArguments);
+ ss << " tmp = !(fmod(floor(fabs(tmp0)), 2.0)<0.5);\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+void OpOdd::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName,
+ SubArguments &vSubArguments)
+{
+ FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur);
+ ss << Math_Intg_Str;
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n{\n";
+ ss <<" int gid0=get_global_id(0);\n";
+ ss << " double tmp=0;\n";
+ ss << " double arg0 = " << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss<< " if(isnan(arg0)||(gid0>=";
+ ss<<tmpCurDVR->GetArrayLength();
+ ss<<"))\n";
+ ss<<" arg0 = 0;\n";
+ ss << " if (arg0 > 0.0 ){\n";
+ ss << " tmp=Intg(arg0);\n";
+ ss << " if(tmp-trunc(tmp/2)*2 == 0)\n";
+ ss << " tmp=tmp+1;\n";
+ ss << " }else if (arg0 < 0.0 ){\n";
+ ss << " tmp=Intg(arg0);\n";
+ ss << " if(tmp-trunc(tmp/2)*2 == 0)\n";
+ ss << " tmp=tmp-1.0;\n";
+ ss << " }else if (arg0 == 0.0 )\n";
+ ss << " tmp=1.0;\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+void OpCountIf::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (unsigned i = 0; i < 2; i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double vara, varb;\n";
+ ss << " int varc = 0;\n";
+ FormulaToken *tmpCur = vSubArguments[1]->GetFormulaToken();
+ assert(tmpCur);
+ if(ocPush == vSubArguments[1]->GetFormulaToken()->GetOpCode())
+ {
+ if(tmpCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* tmpCurDVR=
+ static_cast<
+ const formula::SingleVectorRefToken *>(tmpCur);
+ ss << " varb = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(isnan(varb)||(gid0>=";
+ ss << tmpCurDVR->GetArrayLength();
+ ss << "))\n";
+ ss << " varb = 0;\n";
+ }
+ else if(tmpCur->GetType() == formula::svDouble)
+ {
+ ss << " varb = ";
+ ss << tmpCur->GetDouble() << ";\n";
+ }
+ }
+ else
+ {
+ ss << " varb = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ tmpCur = vSubArguments[0]->GetFormulaToken();
+ assert(tmpCur);
+ if(ocPush == vSubArguments[0]->GetFormulaToken()->GetOpCode())
+ {
+ //TODO DoubleVector
+ if (tmpCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(tmpCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; ++i)\n";
+ ss << " {\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+"<< nCurWindowSize << "; ++i)\n";
+ ss << " {\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < "<< nCurWindowSize << "; ++i)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < "<< nCurWindowSize << "; ++i)\n";
+ ss << " {\n";
+ }
+ ss << " vara = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (isnan(vara))\n";
+ ss << " continue;\n";
+ ss << " (vara == varb) && varc++;\n";
+ ss << " }\n";
+ }
+ else if(tmpCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* tmpCurDVR=
+ static_cast<
+ const formula::SingleVectorRefToken *>(tmpCur);
+ ss << " vara = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(isnan(vara)||(gid0>=";
+ ss << tmpCurDVR->GetArrayLength();
+ ss << "))\n";
+ ss << " return 0;\n";
+ ss << " (vara == varb) && varc++;\n";
+ }
+ }
+ ss << " return varc;\n";
+ ss << "}";
+}
+void OpSumIf::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ int flag = 3 == vSubArguments.size() ? 2 : 0;
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double vara, varb, varc, sum = 0.0f;\n";
+ FormulaToken *tmpCur = vSubArguments[1]->GetFormulaToken();
+ assert(tmpCur);
+ if(ocPush == vSubArguments[1]->GetFormulaToken()->GetOpCode())
+ {
+ if(tmpCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* tmpCurDVR=
+ static_cast<
+ const formula::SingleVectorRefToken *>(tmpCur);
+ ss << " varb = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(isnan(varb)||(gid0>=";
+ ss << tmpCurDVR->GetArrayLength();
+ ss << "))\n";
+ ss << " varb = 0;\n";
+ }
+ else if(tmpCur->GetType() == formula::svDouble)
+ {
+ ss << " varb = ";
+ ss << tmpCur->GetDouble() << ";\n";
+ }
+ }
+ else
+ {
+ ss << " varb = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ tmpCur = vSubArguments[0]->GetFormulaToken();
+ assert(tmpCur);
+ if(ocPush == vSubArguments[0]->GetFormulaToken()->GetOpCode())
+ {
+ //TODO DoubleVector
+ if (tmpCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(tmpCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; ++i)\n";
+ ss << " {\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+"<< nCurWindowSize << "; ++i)\n";
+ ss << " {\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < "<< nCurWindowSize << "; ++i)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < "<< nCurWindowSize << "; ++i)\n";
+ ss << " {\n";
+ }
+ ss << " vara = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (isnan(vara))\n";
+ ss << " continue;\n";
+ ss << " varc = ";
+ ss << vSubArguments[flag]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (isnan(varc))\n";
+ ss << " varc = 0.0f;\n";
+ ss << " (vara == varb)&&(sum = sum + varc);\n";
+ ss << " }\n";
+ }
+ else if(tmpCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* tmpCurDVR=
+ static_cast<
+ const formula::SingleVectorRefToken *>(tmpCur);
+ ss << " vara = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(isnan(vara)||(gid0>=";
+ ss << tmpCurDVR->GetArrayLength();
+ ss << "))\n";
+ ss << " return 0;\n";
+ ss << " int i = 0;\n";
+ ss << " varc = ";
+ ss << vSubArguments[flag]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(isnan(varc)||(gid0>=";
+ ss << tmpCurDVR->GetArrayLength();
+ ss << "))\n";
+ ss << " varc = 0.0f;\n";
+
+ ss << " (vara == varb)&&(sum = sum + varc);\n";
+
+ }
+ }
+ ss << " return sum;\n";
+ ss << "}";
+}
+void OpTrunc::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double arg[2];\n";
+ for( size_t i=0; i < vSubArguments.size(); ++i)
+ {
+ FormulaToken *tmpCur = vSubArguments[i]->GetFormulaToken();
+ assert(tmpCur);
+ if(ocPush == vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ if(tmpCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* tmpCurDVR =
+ static_cast<
+ const formula::DoubleVectorRefToken *>(tmpCur);
+ ss << " int i = 0;\n";
+ ss << " arg["<<i<<"] = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(isnan(arg["<<i;
+ ss << "])||(gid0>=";
+ ss << tmpCurDVR->GetArrayLength();
+ ss << "))\n";
+ ss << " arg["<<i;
+ ss << "] = 0;\n";
+ }
+ else if(tmpCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* tmpCurDVR=
+ static_cast<
+ const formula::SingleVectorRefToken *>(tmpCur);
+ ss << " arg["<<i<<"] = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(isnan(arg["<<i;
+ ss << "])||(gid0>=";
+ ss << tmpCurDVR->GetArrayLength();
+ ss << "))\n";
+ ss << " arg["<<i;
+ ss << "] = 0;\n";
+ }
+ else if(tmpCur->GetType() == formula::svDouble)
+ {
+ ss << " arg["<<i<<"] = ";
+ ss << tmpCur->GetDouble() << ";\n";
+ }
+ }
+ else
+ {
+ ss << " arg["<<i<<"] = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ }
+ ss << " double argm = arg[0];\n";
+ ss << " int n = (int)arg[1];\n";
+ ss << " double nn = 1.0f;\n";
+ ss << " for(int i = 0; i < n; ++i)\n";
+ ss << " {\n";
+ ss << " argm = argm * 10;\n";
+ ss << " nn = nn * 10;\n";
+ ss << " }\n";
+ ss << " modf(argm, &argm);\n";
+ ss << " return argm / nn;\n";
+ ss << "}";
+}
+void OpFloor::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName,
+ SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double arg0,arg1,arg2=0.0;\n";
+ ss << " arg0 = " << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " arg1 = " << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ if ( 3 == vSubArguments.size() )
+ {
+ ss << " arg2 = " << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ ss << " if(isnan(arg0) || isnan(arg1))\n";
+ ss << " return 0;\n";
+ ss << " if(isnan(arg2))\n";
+ ss << " arg2 = 0.0;\n";
+ ss << " if(arg0*arg1<0)\n";
+ ss << " return NAN;\n";
+ ss << " else if(arg2==0.0&&arg0<0.0)\n";
+ ss << " return (trunc(arg0/arg1)+1)*arg1;\n";
+ ss << " else\n";
+ ss << " return trunc(arg0/arg1)*arg1;\n";
+ ss << "}\n";
+}
+void OpBitOr::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i) ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double num1 = " << GetBottom() << ";\n";
+ ss << " double num2 = " << GetBottom() << ";\n";
+ FormulaToken *iNum1 = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken* tmpCurDVRNum1=
+ static_cast<const formula::SingleVectorRefToken *>(iNum1);
+ FormulaToken *iNum2 = vSubArguments[1]->GetFormulaToken();
+ const formula::SingleVectorRefToken* tmpCurDVRNum2=
+ static_cast<const formula::SingleVectorRefToken *>(iNum2);
+ ss << " int buffer_num1_len = "<<tmpCurDVRNum1->GetArrayLength()<<";\n";
+ ss << " int buffer_num2_len = "<<tmpCurDVRNum2->GetArrayLength()<<";\n";
+ ss << " if((gid0)>=buffer_num1_len || isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << "))\n";
+ ss << " num1 = " << GetBottom() << ";\n";
+ ss << " else \n ";
+ ss << " num1 = floor(" << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ");\n";
+ ss << " if((gid0)>=buffer_num2_len || isnan(";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << "))\n";
+ ss << " num2 = " << GetBottom() << ";\n";
+ ss << " else\n ";
+ ss << " num2 = floor(" << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss << ");\n";
+ ss << " return (long)num1 | (long)num2;\n";
+ ss << "}";
+}
+void OpBitXor::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i) ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double num1 = " << GetBottom() << ";\n";
+ ss << " double num2 = " << GetBottom() << ";\n";
+ FormulaToken *iNum1 = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken* tmpCurDVRNum1=
+ static_cast<const formula::SingleVectorRefToken *>(iNum1);
+ FormulaToken *iNum2 = vSubArguments[1]->GetFormulaToken();
+ const formula::SingleVectorRefToken* tmpCurDVRNum2=
+ static_cast<const formula::SingleVectorRefToken *>(iNum2);
+ ss << " int buffer_num1_len = " << tmpCurDVRNum1->GetArrayLength() << ";\n";
+ ss << " int buffer_num2_len = " << tmpCurDVRNum2->GetArrayLength() << ";\n";
+
+ ss << " if((gid0)>=buffer_num1_len || isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << "))\n";
+ ss << " num1 = " << GetBottom() << ";\n";
+ ss << " else\n ";
+ ss << " num1 = floor(" << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ");\n";
+ ss << " if((gid0)>=buffer_num2_len || isnan(";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << "))\n";
+ ss << " num2 = " << GetBottom() << ";\n";
+ ss << " else\n ";
+ ss << " num2 = floor(" << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss << ");\n";
+ ss << " return (long)num1 ^ (long)num2;\n";
+ ss << "}";
+}
+void OpBitLshift::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i) ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double num = " << GetBottom() << ";\n";
+ ss << " double shift_amount = " << GetBottom() << ";\n";
+ FormulaToken *iNum = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken* tmpCurDVRNum=
+ static_cast<const formula::SingleVectorRefToken*>(iNum);
+ FormulaToken *iShiftAmount = vSubArguments[1]->GetFormulaToken();
+ const formula::SingleVectorRefToken* tmpCurDVRShiftAmount=
+ static_cast<const formula::SingleVectorRefToken*>(iShiftAmount);
+ ss << " int buffer_num_len = "<< tmpCurDVRNum->GetArrayLength()<<";\n";
+ ss << " int buffer_shift_amount_len = ";
+ ss << tmpCurDVRShiftAmount->GetArrayLength() << ";\n";
+ ss << " if((gid0)>=buffer_num_len || isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << "))\n";
+ ss << " num = " << GetBottom() << ";\n";
+ ss << " else\n ";
+ ss << " num = floor(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ");\n";
+ ss << " if((gid0)>=buffer_shift_amount_len || isnan(";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << "))\n";
+ ss << " shift_amount = " << GetBottom() << ";\n";
+ ss << " else\n ";
+ ss << " shift_amount = floor(";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ");\n";
+ ss << " return floor(shift_amount >= 0 ? ";
+ ss << "num * pow(2.0, shift_amount) : ";
+ ss << "num / pow(2.0, fabs(shift_amount)));\n";
+ ss << "}";
+}
+void OpBitRshift::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i) ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double num = " << GetBottom() << ";\n";
+ ss << " double shift_amount = " << GetBottom() << ";\n";
+ FormulaToken *iNum = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken* tmpCurDVRNum=
+ static_cast<const formula::SingleVectorRefToken*>(iNum);
+ FormulaToken *iShiftAmount = vSubArguments[1]->GetFormulaToken();
+ const formula::SingleVectorRefToken* tmpCurDVRShiftAmount=
+ static_cast<const formula::SingleVectorRefToken*>(iShiftAmount);
+ ss << " int buffer_num_len = ";
+ ss << tmpCurDVRNum->GetArrayLength() << ";\n";
+ ss << " int buffer_shift_amount_len = ";
+ ss << tmpCurDVRShiftAmount->GetArrayLength() << ";\n";
+
+ ss << " if((gid0)>=buffer_num_len || isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << "))\n";
+ ss << " num = " << GetBottom() << ";\n";
+ ss << " else\n ";
+ ss << " num = floor(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ");\n";
+ ss << " if((gid0)>=buffer_shift_amount_len || isnan(";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << "))\n";
+ ss << " shift_amount = " <<GetBottom()<< ";\n";
+ ss << " else\n ";
+ ss << " shift_amount = floor(";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ");\n";
+ ss << " return floor(";
+ ss << "shift_amount >= 0 ? num / pow(2.0, shift_amount) : ";
+ ss << "num * pow(2.0, fabs(shift_amount)));\n";
+ ss << "}";
+}
+void OpSumSQ::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); ++i)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double sum = 0.0f, arg;\n";
+ for(const DynamicKernelArgumentRef & rArg : vSubArguments)
+ {
+ FormulaToken *tmpCur = rArg->GetFormulaToken();
+ assert(tmpCur);
+ if(ocPush == rArg->GetFormulaToken()->GetOpCode())
+ {
+ if (tmpCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(tmpCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; ++i)\n";
+ ss << " {\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+"<< nCurWindowSize << "; ++i)\n";
+ ss << " {\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < "<< nCurWindowSize << "; ++i)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < "<< nCurWindowSize << "; ++i)\n";
+ ss << " {\n";
+ }
+ ss << " arg = ";
+ ss << rArg->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (isnan(arg))\n";
+ ss << " continue;\n";
+ ss << " sum += pown(arg, 2);\n";
+ ss << " }\n";
+ }
+ else if(tmpCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* tmpCurDVR=
+ static_cast<
+ const formula::SingleVectorRefToken *>(tmpCur);
+ ss << " arg = ";
+ ss << rArg->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(isnan(arg)||(gid0>=";
+ ss << tmpCurDVR->GetArrayLength();
+ ss << "))\n";
+ ss << " arg = 0.0f;\n";
+ ss << " sum += pown(arg, 2);\n";
+ }
+ else if(tmpCur->GetType() == formula::svDouble)
+ {
+ ss << " arg = ";
+ ss << tmpCur->GetDouble() << ";\n";
+ ss << " sum += pown(arg, 2);\n";
+ }
+ }
+ else
+ {
+ ss << " arg = ";
+ ss << rArg->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " sum += pown(arg, 2);\n";
+ }
+ }
+ ss << " return sum;\n";
+ ss << "}";
+}
+void OpSqrtPi::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double arg0 = 0.0f;\n";
+ FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ assert(tmpCur);
+ if(ocPush == vSubArguments[0]->GetFormulaToken()->GetOpCode())
+ {
+ if(tmpCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR=
+ static_cast
+ <const formula::SingleVectorRefToken *>(tmpCur);
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ")||(gid0>=";
+ ss << tmpCurDVR->GetArrayLength();
+ ss << "))\n";
+ ss << " { arg0 = 0.0f; }\n";
+ }
+ else if(tmpCur->GetType() == formula::svDouble)
+ {
+ ss << " arg0=";
+ ss << tmpCur->GetDouble() << ";\n";
+ }
+ }
+ else
+ {
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ ss << " return (double)sqrt(arg0 *";
+ ss << " 3.1415926535897932);\n";
+ ss << "}";
+}
+void OpCeil::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i) ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double num = " << GetBottom() << ";\n";
+ ss << " double significance = " << GetBottom() << ";\n";
+ ss << " double bAbs = 0;\n";
+ ss << " if(isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << "))\n";
+ ss << " num = " << GetBottom() << ";\n";
+ ss << " else\n ";
+ ss << " num = " << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if(isnan(";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << "))\n";
+ ss << " return 0.0;\n";
+ ss << " else\n ";
+ ss << " significance = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ";\n";
+ if (vSubArguments.size() > 2)
+ {
+ FormulaToken *bAbs = vSubArguments[2]->GetFormulaToken();
+ if(bAbs->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* tmpCurSVRIsAbs=
+ static_cast<const formula::SingleVectorRefToken*>(bAbs);
+ ss<< " if((gid0)>=" << tmpCurSVRIsAbs->GetArrayLength() << " ||";
+ }
+ if(bAbs->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* tmpCurDVRIsAbs=
+ static_cast<const formula::DoubleVectorRefToken*>(bAbs);
+ ss<< " if((gid0)>=" << tmpCurDVRIsAbs->GetArrayLength() << " ||";
+ }
+ if(bAbs->GetType() == formula::svDouble)
+ {
+ ss<< " if(";
+ }
+ ss << "isnan(";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef() << "))\n";
+ ss << " bAbs = 0;\n";
+ ss << " else\n ";
+ ss << " bAbs = "<<vSubArguments[2]->GenSlidingWindowDeclRef()<<";\n";
+ }
+ ss << " if(significance == 0.0)\n";
+ ss << " return 0.0;\n";
+ ss << " return ";
+ ss << "( !(int)bAbs && num < 0.0 ? floor( num / significance ) : ";
+ ss << "ceil( num / significance ) )";
+ ss << "*significance;\n";
+ ss << "}";
+}
+void OpCombin::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i) ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double num = " << GetBottom() << ";\n";
+ ss << " double num_chosen = " << GetBottom() << ";\n";
+ ss << " double result = -1.0;\n";
+ FormulaToken *iNum = vSubArguments[0]->GetFormulaToken();
+ FormulaToken *iNumChosen = vSubArguments[1]->GetFormulaToken();
+
+ assert(iNum);
+ if(ocPush == vSubArguments[0]->GetFormulaToken()->GetOpCode())
+ {
+ if(iNum->GetType() == formula::svSingleVectorRef &&
+ iNumChosen->GetType() == formula::svSingleVectorRef)
+ {
+ ss << " if(isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << "))\n";
+ ss << " num = " << GetBottom() << ";\n";
+ ss << " else\n ";
+ ss << " num = floor(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ");\n";
+ ss << " if(isnan(";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << "))\n";
+ ss << " num_chosen = " << GetBottom() << ";\n";
+ ss << " else\n ";
+ ss << " num_chosen = floor(";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ");\n";
+ }
+ else if(iNum->GetType() == formula::svDouble &&
+ iNumChosen->GetType() == formula::svDouble)
+ {
+ ss << " num = floor(" << iNum->GetDouble() << ");\n";
+ ss << " num_chosen = floor("<< iNumChosen->GetDouble()<< ");\n";
+ }
+ }
+ else
+ {
+ ss << " num = floor(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ");\n";
+ ss << " num_chosen = floor(";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ");\n";
+ }
+ ss << " result = select(result, 0.0, (ulong)(num < num_chosen));\n";
+ ss << " result = select(result, 1.0, (ulong)(num_chosen == 0.0));\n";
+ ss << " if(result == 0 || result ==1)\n";
+ ss << " return result;\n";
+ ss << " double4 db4num;\n";
+ ss << " double4 db4num_chosen;\n";
+ ss << " double4 db4result;\n";
+ ss << " double2 db2result;\n";
+ ss << " result = 1.0;\n";
+ ss << " int loop = num_chosen/4;\n";
+ ss << " for(int i=0; i<loop; i++)\n";
+ ss << " {\n";
+ ss << " db4num = (double4){num,\n";
+ ss << " num-1.0,\n";
+ ss << " num-2.0,\n";
+ ss << " num-3.0};\n";
+ ss << " db4num_chosen = (double4){num_chosen,\n";
+ ss << " num_chosen-1.0,\n";
+ ss << " num_chosen-2.0,\n";
+ ss << " num_chosen-3.0};\n";
+ ss << " db4result = db4num * pown(db4num_chosen, -1);\n";
+ ss << " db2result = db4result.xy * db4result.zw;\n";
+ ss << " result *= db2result.x * db2result.y;\n";
+ ss << " num = num - 4.0;\n";
+ ss << " num_chosen = num_chosen - 4.0;\n";
+ ss << " }\n";
+ ss << " while ( num_chosen > 0){\n";
+ ss << " result *= num / num_chosen;\n";
+ ss << " num = num - 1.0;\n";
+ ss << " num_chosen = num_chosen - 1.0;\n";
+ ss << " }\n";
+ ss << " return result;\n";
+ ss << "}\n";
+}
+void OpConvert::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName,
+ SubArguments &vSubArguments)
+{
+ // The CONVERT function converts a value from one unit of
+ // measurement to another. It takes the units of measurements to
+ // convert between as string arguments. This implementation
+ // handles just a very small subset of such conversions.
+
+ int arg1=vSubArguments[1]->GetFormulaToken()->GetString().
+ getString().toAsciiUpperCase().hashCode();
+ int arg2=vSubArguments[2]->GetFormulaToken()->GetString().
+ getString().toAsciiUpperCase().hashCode();
+
+ // Check if the from and to units are those combinations that the
+ // code below supports.
+ if( !((arg1==5584&&arg2==108)||
+ (arg1==108&&arg2==5584)||
+ (arg1==5665&&arg2==268206)||
+ (arg1==268206&&arg2==5665)) )
+ throw Unhandled(__FILE__, __LINE__);
+
+ FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur);
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n{\n";
+ ss <<" int gid0=get_global_id(0);\n";
+ ss << " double arg0 = " << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " double arg1 = " << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " double arg2 = " << vSubArguments[2]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss<< " if(isnan(arg0)||(gid0>=";
+ ss<<tmpCurDVR->GetArrayLength();
+ ss<<"))\n";
+ ss<<" arg0 = 0;\n";
+ ss<< " if(isnan(arg1)||(gid0>=";
+ ss<<tmpCurDVR->GetArrayLength();
+ ss<<"))\n";
+ ss<<" arg1 = 0;\n";
+ ss<< " if(isnan(arg2)||(gid0>=";
+ ss<<tmpCurDVR->GetArrayLength();
+ ss<<"))\n";
+ ss<<" arg2 = 0;\n";
+ ss<<" if(arg1==5584U&&arg2==108U)\n";
+ ss<<" return arg0*1000.0;\n";
+ ss<<" else if(arg1==108U&&arg2==3385U)\n";
+ ss<<" return arg0/1000.0;\n";
+ ss<<" else if(arg1==5665U&&arg2==268206U)\n";
+ ss<<" return arg0*60.0;\n";
+ ss<<" else if(arg1==268206U&&arg2==5665U)\n";
+ ss<<" return arg0/60.0;\n";
+ ss<<" else\n";
+ ss<<" return -9999999999;\n";
+ ss << "}\n";
+
+}
+
+void OpProduct::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"( ";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " int i = 0;\n";
+ ss << " double product=1.0;\n";
+ ss << " int count = 0;\n\n";
+ for (DynamicKernelArgumentRef & rArg : vSubArguments)
+ {
+ FormulaToken *pCur = rArg->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+
+ ss << "0; i < " << pDVR->GetArrayLength() << "; i++)\n";
+ ss << " {\n";
+ }
+ ss << " if(!isnan("<<rArg->GenSlidingWindowDeclRef()<<"))\n";
+ ss << " {\n";
+ ss << " product = product*";
+ ss << rArg->GenSlidingWindowDeclRef()<<";\n";
+ ss << " ++count;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ ss << " if(!isnan("<<rArg->GenSlidingWindowDeclRef()<<"))\n";
+ ss << " {\n";
+ ss << " product = product*";
+ ss << rArg->GenSlidingWindowDeclRef()<<";\n";
+ ss << " ++count;\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " if(!isnan("<<rArg->GenSlidingWindowDeclRef()<<"))\n";
+ ss << " {\n";
+ ss << " product = product*";
+ ss << rArg->GenSlidingWindowDeclRef()<<";\n";
+ ss << " ++count;\n";
+ ss << " }\n";
+ }
+ }
+ ss << " if(count == 0)\n";
+ ss << " return 0;\n";
+ ss << " return product;\n";
+ ss << "}";
+}
+void OpAverageIf::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i) ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double tmp =0;\n";
+ ss << " double count=0;\n";
+ ss << " int singleIndex =gid0;\n";
+ ss << " int doubleIndex;\n";
+ ss << " int i ;\n";
+ ss << " int j ;\n";
+ GenTmpVariables(ss,vSubArguments);
+
+ unsigned paraOneIsDoubleVector = 0;
+ unsigned paraOneWidth = 1;
+ unsigned paraTwoWidth = 1;
+ unsigned loopTimes = 0;
+
+ if(vSubArguments[0]->GetFormulaToken()->GetType() == formula::svDoubleVectorRef)
+ {
+ paraOneIsDoubleVector = 1;
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR0= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur0);
+ paraOneWidth = pCurDVR0->GetArrays().size();
+ loopTimes = paraOneWidth;
+ if(paraOneWidth > 1)
+ {
+ throw Unhandled(__FILE__, __LINE__);
+ }
+ }
+
+ if(vSubArguments[paraOneWidth]->GetFormulaToken()->GetType() ==
+ formula::svDoubleVectorRef)
+
+ {
+ FormulaToken *tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR1= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur1);
+ paraTwoWidth = pCurDVR1->GetArrays().size();
+ if(paraTwoWidth > 1)
+ {
+ throw Unhandled(__FILE__, __LINE__);
+ }
+ ss << " i = ";
+ if (!pCurDVR1->IsStartFixed() && pCurDVR1->IsEndFixed()) {
+ ss << "gid0;\n";
+ } else {
+ ss << "0;\n";
+ }
+ if(!pCurDVR1->IsStartFixed() && !pCurDVR1->IsEndFixed())
+ {
+ ss << " doubleIndex =i+gid0;\n";
+ }else
+ {
+ ss << " doubleIndex =i;\n";
+ }
+ }
+
+ CheckSubArgumentIsNan(ss,vSubArguments,paraOneWidth);
+
+ unsigned paraThreeIndex = paraOneWidth + paraTwoWidth;
+ if(vSubArguments.size() > paraThreeIndex)
+ {
+ if(vSubArguments[paraThreeIndex]->GetFormulaToken()->GetType() ==
+ formula::svDoubleVectorRef)
+ {
+ FormulaToken *tmpCur2 =
+ vSubArguments[paraThreeIndex]->GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR2= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur2);
+ unsigned paraThreeWidth = pCurDVR2->GetArrays().size();
+ if(paraThreeWidth > 1)
+ {
+ throw Unhandled(__FILE__, __LINE__);
+ }
+ }
+ }
+
+ if(paraOneIsDoubleVector)
+ {
+ unsigned loopIndex = 0;
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR0= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur0);
+ size_t nCurWindowSize = pCurDVR0->GetArrayLength() <
+ pCurDVR0->GetRefRowSize() ? pCurDVR0->GetArrayLength():
+ pCurDVR0->GetRefRowSize() ;
+
+ for(loopIndex =0; loopIndex < loopTimes; loopIndex++)
+ {
+ ss << " for (i = ";
+ if (!pCurDVR0->IsStartFixed() && pCurDVR0->IsEndFixed()) {
+ ss << "gid0; i < "<< nCurWindowSize <<"; i++)\n";
+ } else if (pCurDVR0->IsStartFixed() && !pCurDVR0->IsEndFixed()) {
+ ss << "0; i < gid0+"<< nCurWindowSize <<"; i++)\n";
+ } else {
+ ss << "0; i < "<< nCurWindowSize <<"; i++)\n";
+ }
+ ss << " {\n";
+ if(!pCurDVR0->IsStartFixed() && !pCurDVR0->IsEndFixed())
+ {
+ ss << " doubleIndex =i+gid0;\n";
+ }else
+ {
+ ss << " doubleIndex =i;\n";
+ }
+
+ CheckSubArgumentIsNan(ss,vSubArguments, loopIndex);
+
+ ss << " if ( isequal( tmp";
+ ss << loopIndex<<" , tmp"<<paraOneWidth<<") ) \n";
+ ss << " {\n";
+ if(vSubArguments.size() == paraThreeIndex)
+ ss << " tmp += tmp"<<loopIndex<<";\n";
+ else
+ {
+ CheckSubArgumentIsNan(ss,vSubArguments,
+ paraThreeIndex+loopIndex);
+ ss << " tmp += tmp";
+ ss << paraThreeIndex+loopIndex<<";\n";
+ }
+ ss << " count+=1.0;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ }
+ else
+ {
+ CheckSubArgumentIsNan(ss,vSubArguments, 0);
+ ss << " if ( isequal( tmp0 , tmp1 ) ) \n";
+ ss << " {\n";
+ if(vSubArguments.size() == 2)
+ ss << " tmp += tmp0;\n";
+ else
+ {
+ CheckSubArgumentIsNan(ss,vSubArguments,2);
+ ss << " tmp += tmp2;\n";
+ }
+ ss << " count+=1.0;\n";
+ ss << " }\n";
+ }
+
+ ss << " if(count!=0)\n";
+ ss << " tmp=tmp/count;\n";
+ ss << " else\n";
+ ss << " tmp= 0 ;\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+void OpDeg::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double arg0 = 0.0f;\n";
+ FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ assert(tmpCur);
+ if(ocPush == vSubArguments[0]->GetFormulaToken()->GetOpCode())
+ {
+ if(tmpCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR=
+ static_cast
+ <const formula::SingleVectorRefToken *>(tmpCur);
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ")||(gid0>=";
+ ss << tmpCurDVR->GetArrayLength();
+ ss << "))\n";
+ ss << " { arg0 = 0.0f; }\n";
+ }
+ else if(tmpCur->GetType() == formula::svDouble)
+ {
+ ss << " arg0=";
+ ss << tmpCur->GetDouble() << ";\n";
+ }
+ }
+ else
+ {
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ ss << " return arg0 * pown(M_PI, -1) * 180;;\n";
+ ss << "}";
+}
+
+void OpFact::GenSlidingWindowFunction(std::stringstream& ss,
+ const std::string &sSymName, SubArguments& vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_" << BinFuncName() << "(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " double tmp = " << GetBottom() << ";\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double arg0 = " << GetBottom() << ";\n";
+ FormulaToken* pCur = vSubArguments[0]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << " {\n";
+ }
+ if(ocPush==vSubArguments[0]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " arg0 = 0;\n";
+ ss << " else\n";
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " arg0 = floor(arg0);\n";
+ ss << " if (arg0 < 0.0)\n";
+ ss << " return 0.0;\n";
+ ss << " else if (arg0 == 0.0)\n";
+ ss << " return 1.0;\n";
+ ss << " else if (arg0 <= 170.0)\n";
+ ss << " {\n";
+ ss << " double fTemp = arg0;\n";
+ ss << " while (fTemp > 2.0)\n";
+ ss << " {\n";
+ ss << " fTemp = fTemp - 1;\n";
+ ss << " arg0 = arg0 * fTemp;\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << " else\n";
+ ss << " return -DBL_MAX;\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ }
+ ss << " return arg0;\n";
+ ss << "}";
+}
+void OpQuotient::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i) ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double num1 = 1.0;\n";
+ ss << " double num2 = 1.0;\n";
+ ss << " if(isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << "))\n";
+ ss << " num1 = 1.0;\n";
+ ss << " else \n ";
+ ss << " num1 = " << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if(isnan(";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << "))\n";
+ ss << " num2 = 1.0;\n";
+ ss << " else \n ";
+ ss << " num2 = " << vSubArguments[1]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " return trunc(num1/num2);\n";
+ ss << "}";
+}
+void OpSeriesSum::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ CHECK_PARAMETER_COUNT(4,4);
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double var[3], coeff, res = 0.0f;\n";
+ FormulaToken *tmpCur;
+ for(int i = 0; i < 3; ++i)
+ {
+ tmpCur = vSubArguments[i]->GetFormulaToken();
+ assert(tmpCur);
+ if(ocPush == vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ if(tmpCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* tmpCurDVR=
+ static_cast<
+ const formula::SingleVectorRefToken *>(tmpCur);
+ ss << " var["<<i<<"] = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(isnan(var["<<i<<"])||(gid0>=";
+ ss << tmpCurDVR->GetArrayLength();
+ ss << "))\n";
+ ss << " var["<<i<<"] = 0;\n";
+ }
+ else if(tmpCur->GetType() == formula::svDouble)
+ {
+ ss << " var["<<i<<"] = ";
+ ss << tmpCur->GetDouble() << ";\n";
+ }
+ }
+ else
+ {
+ ss << " var["<<i<<"] = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ }
+ tmpCur = vSubArguments[3]->GetFormulaToken();
+ assert(tmpCur);
+ if(ocPush == vSubArguments[3]->GetFormulaToken()->GetOpCode())
+ {
+ //TODO DoubleVector
+ if (tmpCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(tmpCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " int j = 0;\n";
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; ++i)\n";
+ ss << " {\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+"<< nCurWindowSize << "; ++i)\n";
+ ss << " {\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < "<< nCurWindowSize << "; ++i)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < "<< nCurWindowSize << "; ++i)\n";
+ ss << " {\n";
+ }
+ ss << " coeff = ";
+ ss << vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (isnan(coeff))\n";
+ ss << " continue;\n";
+ ss << " res = res + coeff * pow(var[0],";
+ ss << " var[1] + j * var[2]);\n";
+ ss << " ++j;\n";
+ ss << " }\n";
+ }
+ else if(tmpCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* tmpCurDVR=
+ static_cast<
+ const formula::SingleVectorRefToken *>(tmpCur);
+ ss << " coeff = ";
+ ss << vSubArguments[3]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(isnan(coeff)||(gid0>=";
+ ss << tmpCurDVR->GetArrayLength();
+ ss << "))\n";
+ ss << " return 0;\n";
+ }
+ else
+ throw Unhandled(__FILE__, __LINE__);
+ }
+ ss << " return res;\n";
+ ss << "}";
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/opencl/op_math.hxx b/sc/source/core/opencl/op_math.hxx
new file mode 100644
index 000000000..f001d1b78
--- /dev/null
+++ b/sc/source/core/opencl/op_math.hxx
@@ -0,0 +1,499 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include "opbase.hxx"
+
+namespace sc::opencl {
+
+class OpCos: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+
+ virtual std::string BinFuncName() const override { return "Cos"; }
+};
+class OpSec: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+
+ virtual std::string BinFuncName() const override { return "Sec"; }
+};
+class OpSecH: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+
+ virtual std::string BinFuncName() const override { return "SecH"; }
+};
+class OpMROUND: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+
+ virtual std::string BinFuncName() const override { return "MROUND"; }
+};
+
+class OpCsc: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Csc"; }
+};
+class OpSumIfs final : public CheckVariables
+{
+public:
+ OpSumIfs(): CheckVariables(), mNeedReductionKernel(false) {}
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "SumIfs"; }
+ bool NeedReductionKernel() const { return mNeedReductionKernel; }
+private:
+ bool mNeedReductionKernel;
+};
+class OpCosh: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>& ) override;
+ virtual std::string BinFuncName() const override { return "Cosh"; }
+};
+class OpSinh: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Sinh"; }
+};
+class OpSin: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Sin"; }
+};
+class OpAbs:public Normal{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string GetBottom() override { return "0.0"; }
+ virtual std::string BinFuncName() const override { return "ScAbs"; }
+};
+class OpArcCos:public Normal{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string GetBottom() override { return "0.0"; }
+ virtual std::string BinFuncName() const override { return "ScACos"; }
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>&) override;
+};
+class OpArcCosHyp:public Normal{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string GetBottom() override { return "1.0"; }
+ virtual std::string BinFuncName() const override { return "ScACosH"; }
+};
+class OpTan: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+
+ virtual std::string BinFuncName() const override { return "Tan"; }
+};
+class OpTanH: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+
+ virtual std::string BinFuncName() const override { return "TanH"; }
+};
+class OpSqrt: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+
+ virtual std::string BinFuncName() const override { return "Sqrt"; }
+};
+class OpArcCot:public Normal{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string GetBottom() override { return "0.0"; }
+ virtual std::string BinFuncName() const override { return "ScACot"; }
+};
+class OpArcCotHyp:public Normal{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string GetBottom() override { return "2.0"; }
+ virtual std::string BinFuncName() const override { return "ScACotH"; }
+};
+class OpArcSin:public Normal{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string GetBottom() override { return "0.0"; }
+ virtual std::string BinFuncName() const override { return "ScASin"; }
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>&) override;
+};
+class OpArcSinHyp:public Normal{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string GetBottom() override { return "0.0"; }
+ virtual std::string BinFuncName() const override { return "ScASinH"; }
+};
+class OpTrunc: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+
+ virtual std::string BinFuncName() const override { return "Trunc"; }
+};
+class OpArcTan2:public Normal{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string GetBottom() override { return "0.0"; }
+ virtual std::string BinFuncName() const override { return "ScATan2"; }
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>&) override;
+};
+class OpArcTan:public Normal{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string GetBottom() override { return "0.0"; }
+ virtual std::string BinFuncName() const override { return "ScATan"; }
+};
+class OpArcTanH:public Normal{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string GetBottom() override { return "0.0"; }
+ virtual std::string BinFuncName() const override { return "ScATanH"; }
+};
+class OpBitAnd:public Normal{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string GetBottom() override { return "0.0"; }
+ virtual std::string BinFuncName() const override { return "ScBitAnd"; }
+};
+class OpBitOr:public Normal{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string GetBottom() override { return "0.0"; }
+ virtual std::string BinFuncName() const override { return "ScBitOr"; }
+};
+class OpBitXor:public Normal{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string GetBottom() override { return "0.0"; }
+ virtual std::string BinFuncName() const override { return "ScBitXor"; }
+};
+class OpBitLshift:public Normal{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string GetBottom() override { return "0.0"; }
+ virtual std::string BinFuncName() const override { return "ScBitLshift"; }
+};
+class OpBitRshift:public Normal{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string GetBottom() override { return "0.0"; }
+ virtual std::string BinFuncName() const override { return "ScBitRshift"; }
+};
+class OpLn: public CheckVariables
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Ln"; }
+};
+class OpRound: public CheckVariables
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Round"; }
+};
+class OpRoundUp: public CheckVariables
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "RoundUp"; }
+};
+class OpRoundDown: public CheckVariables
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "RoundDown"; }
+};
+class OpInt: public CheckVariables
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Int"; }
+};
+class OpRadians: public CheckVariables
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Radians"; }
+};
+class OpIsEven: public CheckVariables
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "IsEven"; }
+};
+class OpIsOdd: public CheckVariables
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "IsOdd"; }
+};
+class OpCot: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+
+ virtual std::string BinFuncName() const override { return "Cot"; }
+};
+class OpSumSQ: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+
+ virtual std::string BinFuncName() const override { return "SumSQ"; }
+};
+class OpCoth: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,
+ std::set<std::string>& ) override;
+ virtual std::string BinFuncName() const override { return "Coth"; }
+};
+class OpPower: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Power"; }
+};
+class OpOdd: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Odd"; }
+};
+class OpFloor: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Floor"; }
+};
+class OpCscH: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "CscH"; }
+};
+class OpCeil:public Normal{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string GetBottom() override { return "0.0"; }
+ virtual std::string BinFuncName() const override { return "ScCeil"; }
+};
+class OpExp: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Exp"; }
+};
+class OpLog10: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Log10"; }
+};
+class OpConvert: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Convert"; }
+ virtual bool takeString() const override { return true; }
+
+};
+class OpEven: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+
+ virtual std::string BinFuncName() const override { return "Even"; }
+};
+class OpAverageIfs: public CheckVariables
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "AverageIfs"; }
+};
+class OpCountIfs: public CheckVariables
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "CountIfs"; }
+};
+class OpMod: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+
+ virtual std::string BinFuncName() const override { return "Mod"; }
+};
+class OpProduct: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Product"; }
+ virtual bool canHandleMultiVector() const override { return true; }
+};
+class OpSqrtPi: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+
+ virtual std::string BinFuncName() const override { return "SqrtPi"; }
+};
+
+class OpCombinA: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+
+ virtual std::string BinFuncName() const override { return "Combina"; }
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>& ) override;
+};
+class OpLog: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+
+ virtual std::string BinFuncName() const override { return "Log"; }
+};
+class OpCombin: public Normal{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string GetBottom() override { return "0.0"; }
+ virtual std::string BinFuncName() const override { return "ScCombin"; }
+};
+class OpAverageIf: public CheckVariables
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+
+ virtual std::string BinFuncName() const override { return "AverageIf"; }
+};
+class OpDeg: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Degrees"; }
+};
+class OpCountIf: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Countif"; }
+};
+class OpFact: public Normal{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream& ss,
+ const std::string &sSymName, SubArguments& vSubArguments) override;
+ virtual std::string GetBottom() override { return "0.0"; }
+ virtual std::string BinFuncName() const override { return "Fact"; }
+};
+class OpSeriesSum: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "SeriesSum"; }
+};
+class OpSumIf: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "SumIf"; }
+};
+class OpQuotient: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Quotient"; }
+};
+class OpNegSub: public CheckVariables
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "NegSub"; }
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/opencl/op_spreadsheet.cxx b/sc/source/core/opencl/op_spreadsheet.cxx
new file mode 100644
index 000000000..48789d582
--- /dev/null
+++ b/sc/source/core/opencl/op_spreadsheet.cxx
@@ -0,0 +1,286 @@
+/* -*- 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 "op_spreadsheet.hxx"
+
+#include <rtl/math.hxx>
+#include <formula/vectortoken.hxx>
+
+#include <algorithm>
+#include <sstream>
+
+using namespace formula;
+
+namespace sc::opencl {
+
+void OpVLookup::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n {\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double tmp = CreateDoubleError(NOTAVAILABLE);\n";
+ ss << " double intermediate = DBL_MAX;\n";
+ ss << " int singleIndex = gid0;\n";
+ ss << " int rowNum = -1;\n";
+
+ GenTmpVariables(ss,vSubArguments);
+ int arg=0;
+ CheckSubArgumentIsNan(ss,vSubArguments,arg++);
+ int secondParaWidth = 1;
+
+ // tdf#99512 - for now only allow non-dynamic indices (the
+ // common-case) to validate consistent return types vs. the input.
+ int index = 0;
+ int indexArg = vSubArguments.size() - 2;
+ if (vSubArguments[indexArg]->GetFormulaToken()->GetType() == formula::svDouble)
+ {
+ const formula::FormulaDoubleToken *dblToken = static_cast<const FormulaDoubleToken *>(vSubArguments[indexArg]->GetFormulaToken());
+ index = ::rtl::math::approxFloor(dblToken->GetDouble());
+ }
+
+ if (vSubArguments[1]->GetFormulaToken()->GetType() != formula::svDoubleVectorRef)
+ throw Unhandled(__FILE__, __LINE__); // unusual vlookup.
+
+ FormulaToken *tmpCur = vSubArguments[1]->GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR = static_cast<const formula::DoubleVectorRefToken *>(tmpCur);
+ const std::vector<VectorRefArray> items = pCurDVR->GetArrays();
+
+ secondParaWidth = items.size();
+
+ if (index < 1 || index > secondParaWidth)
+ throw Unhandled(__FILE__, __LINE__); // oob index.
+
+ if (items[index - 1].mpStringArray)
+ {
+ rtl_uString **pStrings = items[index - 1].mpStringArray;
+ for (size_t i = 0; i < pCurDVR->GetArrayLength(); ++i)
+ {
+ if (pStrings[i] != nullptr)
+ { // TODO: the GroupTokenConverter should do better.
+ throw Unhandled(__FILE__, __LINE__); // mixed arguments.
+ }
+ }
+ }
+
+
+ arg += secondParaWidth;
+ CheckSubArgumentIsNan(ss,vSubArguments,arg++);
+
+ if (vSubArguments.size() == static_cast<unsigned int>(3+(secondParaWidth-1)))
+ {
+ ss << " double tmp";
+ ss << 3+(secondParaWidth-1);
+ ss << "= 1;\n";
+ }
+ else
+ {
+ CheckSubArgumentIsNan(ss,vSubArguments,arg++);
+ }
+
+ if (vSubArguments[1]->GetFormulaToken()->GetType() == formula::svDoubleVectorRef)
+ {
+ tmpCur = vSubArguments[1]->GetFormulaToken();
+ pCurDVR = static_cast<const formula::DoubleVectorRefToken *>(tmpCur);
+ size_t nCurWindowSize = std::min(pCurDVR->GetArrayLength(), pCurDVR->GetRefRowSize());
+ int unrollSize = 8;
+ ss << " int loop;\n";
+ if (!pCurDVR->IsStartFixed() && pCurDVR->IsEndFixed())
+ {
+ ss << " loop = ("<<nCurWindowSize<<" - gid0)/";
+ ss << unrollSize<<";\n";
+
+ }
+ else if (pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed())
+ {
+ ss << " loop = ("<<nCurWindowSize<<" + gid0)/";
+ ss << unrollSize<<";\n";
+
+ }
+ else
+ {
+ ss << " loop = "<<nCurWindowSize<<"/"<< unrollSize<<";\n";
+ }
+
+ for (int i = 0; i < secondParaWidth; i++)
+ {
+ ss << " for ( int j = 0;j< loop; j++)\n";
+ ss << " {\n";
+ ss << " int i = ";
+ if (!pCurDVR->IsStartFixed()&& pCurDVR->IsEndFixed())
+ {
+ ss << "gid0 + j * "<< unrollSize <<";\n";
+ }
+ else
+ {
+ ss << "j * "<< unrollSize <<";\n";
+ }
+ if (!pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed())
+ {
+ ss << " int doubleIndex = i+gid0;\n";
+ }
+ else
+ {
+ ss << " int doubleIndex = i;\n";
+ }
+ ss << " if(tmp";
+ ss << 3+(secondParaWidth-1);
+ ss << " == 1)\n";
+ ss << " {\n";
+
+ for (int j = 0;j < unrollSize; j++)
+ {
+ CheckSubArgumentIsNan(ss,vSubArguments,1+i);
+
+ ss << " if((tmp0 - tmp";
+ ss << 1+i;
+ ss << ")>=0 && intermediate > ( tmp0 -tmp";
+ ss << 1+i;
+ ss << "))\n";
+ ss << " {\n";
+ ss << " rowNum = doubleIndex;\n";
+ ss << " intermediate = tmp0 - tmp";
+ ss << 1+i;
+ ss << ";\n";
+ ss << " }\n";
+ ss << " i++;\n";
+ ss << " doubleIndex++;\n";
+ }
+
+ ss << " }else\n";
+ ss << " {\n";
+ for (int j = 0; j < unrollSize; j++)
+ {
+ CheckSubArgumentIsNan(ss,vSubArguments,1+i);
+
+ ss << " if(tmp0 == tmp";
+ ss << 1+i;
+ ss << " && rowNum == -1)\n";
+ ss << " {\n";
+ ss << " rowNum = doubleIndex;\n";
+ ss << " }\n";
+ ss << " i++;\n";
+ ss << " doubleIndex++;\n";
+ }
+ ss << " }\n\n";
+
+ ss << " }\n";
+ ss << " if(rowNum!=-1)\n";
+ ss << " {\n";
+ for (int j = 0; j < secondParaWidth; j++)
+ {
+ ss << " if(tmp";
+ ss << 2+(secondParaWidth-1);
+ ss << " == ";
+ ss << j+1;
+ ss << ")\n";
+ ss << " tmp = ";
+ vSubArguments[1+j]->GenDeclRef(ss);
+ ss << "[rowNum];\n";
+ }
+ ss << " return tmp;\n";
+ ss << " }\n";
+ ss << " for (int i = ";
+ if (!pCurDVR->IsStartFixed() && pCurDVR->IsEndFixed())
+ {
+ ss << "gid0 + loop *"<<unrollSize<<"; i < ";
+ ss << nCurWindowSize <<"; i++)\n";
+ }
+ else if (pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed())
+ {
+ ss << "0 + loop *"<<unrollSize<<"; i < gid0+";
+ ss << nCurWindowSize <<"; i++)\n";
+ }
+ else
+ {
+ ss << "0 + loop *"<<unrollSize<<"; i < ";
+ ss << nCurWindowSize <<"; i++)\n";
+ }
+ ss << " {\n";
+ if (!pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed())
+ {
+ ss << " int doubleIndex = i+gid0;\n";
+ }
+ else
+ {
+ ss << " int doubleIndex = i;\n";
+ }
+ CheckSubArgumentIsNan(ss,vSubArguments,1+i);
+ ss << " if(tmp";
+ ss << 3+(secondParaWidth-1);
+ ss << " == 1)\n";
+ ss << " {\n";
+ ss << " if((tmp0 - tmp";
+ ss << 1+i;
+ ss << ")>=0 && intermediate > ( tmp0 -tmp";
+ ss << 1+i;
+ ss << "))\n";
+ ss << " {\n";
+ ss << " rowNum = doubleIndex;\n";
+ ss << " intermediate = tmp0 - tmp";
+ ss << 1+i;
+ ss << ";\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << " else\n";
+ ss << " {\n";
+ ss << " if(tmp0 == tmp";
+ ss << 1+i;
+ ss << " && rowNum == -1)\n";
+ ss << " {\n";
+ ss << " rowNum = doubleIndex;\n";
+ ss << " }\n";
+ ss << " }\n";
+
+ ss << " }\n\n";
+ ss << " if(rowNum!=-1)\n";
+ ss << " {\n";
+
+ for (int j = 0; j < secondParaWidth; j++)
+ {
+ ss << " if(tmp";
+ ss << 2+(secondParaWidth-1);
+ ss << " == ";
+ ss << j+1;
+ ss << ")\n";
+ ss << " tmp = ";
+ vSubArguments[1+j]->GenDeclRef(ss);
+ ss << "[rowNum];\n";
+ }
+ ss << " return tmp;\n";
+ ss << " }\n";
+
+ }
+ }
+ else
+ {
+ CheckSubArgumentIsNan(ss,vSubArguments,1);
+ ss << " if(tmp3 == 1)\n";
+ ss << " {\n";
+ ss << " tmp = tmp1;\n";
+ ss << " }else\n";
+ ss << " {\n";
+ ss << " if(tmp0 == tmp1)\n";
+ ss << " tmp = tmp1;\n";
+ ss << " }\n";
+ }
+ ss << " return tmp;\n";
+ ss << "}";
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/opencl/op_spreadsheet.hxx b/sc/source/core/opencl/op_spreadsheet.hxx
new file mode 100644
index 000000000..9045b0e70
--- /dev/null
+++ b/sc/source/core/opencl/op_spreadsheet.hxx
@@ -0,0 +1,27 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include "opbase.hxx"
+
+namespace sc::opencl {
+
+class OpVLookup: public CheckVariables
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "VLookup"; }
+ virtual bool canHandleMultiVector() const override { return true; }
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/opencl/op_statistical.cxx b/sc/source/core/opencl/op_statistical.cxx
new file mode 100644
index 000000000..3de4417fb
--- /dev/null
+++ b/sc/source/core/opencl/op_statistical.cxx
@@ -0,0 +1,9828 @@
+/* -*- 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 "op_statistical.hxx"
+
+#include <formula/vectortoken.hxx>
+#include <sstream>
+#include "opinlinefun_statistical.cxx"
+
+using namespace formula;
+
+namespace sc::opencl {
+void OpVar::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_" << BinFuncName() << "(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << "){\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double fSum = 0.0;\n";
+ ss << " double fMean = 0.0;\n";
+ ss << " double vSum = 0.0;\n";
+ ss << " double fCount = 0.0;\n";
+ ss << " double arg = 0.0;\n";
+ unsigned i = vSubArguments.size();
+ while (i--)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if(ocPush == vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < " << pDVR->GetArrayLength() << "; i++)\n";
+ ss << " {\n";
+ }
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (isnan(arg))\n";
+ ss << " continue;\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount += 1.0;\n";
+ ss << " }\n";
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken*>(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (!isnan(arg))\n";
+ ss << " {\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount += 1.0;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " arg = " << pCur->GetDouble() << ";\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount += 1.0;\n";
+ }
+ }
+ else
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount += 1.0;\n";
+ }
+ if (i == 0)
+ {
+ ss << " fMean = fSum * pow(fCount,-1.0);\n";
+ }
+ }
+ i = vSubArguments.size();
+ while (i--)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if(ocPush == vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < " << pDVR->GetArrayLength() << "; i++)\n";
+ ss << " {\n";
+ }
+
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (isnan(arg))\n";
+ ss << " continue;\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ ss << " }\n";
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken*>(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " if (!isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " {\n";
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " arg=" << pCur->GetDouble() << ";\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ }
+ }
+ else
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ }
+ }
+ ss << " if (fCount <= 1.0)\n";
+ ss << " return CreateDoubleError(DivisionByZero);\n";
+ ss << " else\n";
+ ss << " return vSum * pow(fCount - 1.0,-1.0);\n";
+ ss << "}\n";
+}
+void OpZTest::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(phiDecl);
+ funs.insert(phi);
+ decls.insert(taylorDecl);
+ funs.insert(taylor);
+ decls.insert(gaussDecl);
+ funs.insert(gauss);
+}
+void OpZTest::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_" << BinFuncName() << "(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << "){\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double fSum = 0.0;\n";
+ ss << " double fSumSqr = 0.0;\n";
+ ss << " double mue = 0.0;\n";
+ ss << " double fCount = 0.0;\n";
+ ss << " double arg = 0.0;\n";
+ ss << " double sigma = 0.0;\n";
+ ss << " double mu = 0.0;\n";
+ if(vSubArguments.size() == 1 || vSubArguments.empty())
+ {
+ ss << " return DBL_MAX;\n";
+ ss << "}\n";
+ return ;
+ }
+ else if(vSubArguments.size() == 2)
+ {
+ FormulaToken *pCur = vSubArguments[0]->GetFormulaToken();
+ FormulaToken *pCur1 = vSubArguments[1]->GetFormulaToken();
+ assert(pCur);
+ assert(pCur1);
+ if(pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < " << pDVR->GetArrayLength() << "; i++)\n";
+ ss << " {\n";
+ }
+
+ ss << " arg = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (isnan(arg))\n";
+ ss << " continue;\n";
+ ss << " fSum += arg;\n";
+ ss << " fSumSqr += arg * arg;\n";
+ ss << " fCount += 1.0;\n";
+ ss << " }\n";
+ ss << " if(fCount <= 1.0)\n";
+ ss << " return DBL_MAX;\n";
+ ss << " mue = fSum *pow(fCount,-1.0);\n";
+ ss << " sigma = (fSumSqr-fSum*fSum*";
+ ss << "pow(fCount,-1.0))*pow(fCount-1.0,-1.0);\n";
+ }
+ else
+ {
+ ss << " return DBL_MAX;\n";
+ ss << "}\n";
+ return ;
+ }
+ if(ocPush == vSubArguments[1]->GetFormulaToken()->GetOpCode())
+ {
+ if(pCur1->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast<const formula::SingleVectorRefToken* >(pCur1);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " mu = " ;
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (isnan(mu))\n";
+ ss << " mu = 0.0;\n";
+ ss << " }\n";
+
+ }
+ else if(pCur1->GetType() == formula::svDouble)
+ {
+ ss << " mu = " << pCur1->GetDouble() << ";\n";
+ }
+ else
+ {
+ ss << " return DBL_MAX;\n";
+ ss << "}\n";
+ return ;
+ }
+ }
+ else
+ {
+ ss << " mu = " ;
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ";\n";
+ }
+ ss << " return 0.5 - gauss((mue-mu)/sqrt(sigma/fCount));\n";
+ ss << "}\n";
+ return ;
+ }
+ else
+ {
+ FormulaToken *pCur = vSubArguments[0]->GetFormulaToken();
+ FormulaToken *pCur1 = vSubArguments[1]->GetFormulaToken();
+ FormulaToken *pCur2 = vSubArguments[2]->GetFormulaToken();
+ assert(pCur);
+ assert(pCur1);
+ assert(pCur2);
+ if(pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < " << pDVR->GetArrayLength() << "; i++)\n";
+ ss << " {\n";
+ }
+ ss << " arg = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (isnan(arg))\n";
+ ss << " continue;\n";
+ ss << " fSum += arg;\n";
+ ss << " fSumSqr += arg * arg;\n";
+ ss << " fCount += 1.0;\n";
+ ss << " }\n";
+ ss << " if(fCount <= 1.0)\n";
+ ss << " return DBL_MAX;\n";
+ ss << " mue = fSum * pow(fCount,-1.0);\n";
+ }
+ else
+ {
+ ss << " return DBL_MAX;\n";
+ ss << "}\n";
+ return ;
+ }
+ if(ocPush == vSubArguments[1]->GetFormulaToken()->GetOpCode())
+ {
+ if(pCur1->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR1 =
+ static_cast<const formula::SingleVectorRefToken* >(pCur1);
+ ss << " if (gid0 < " << pSVR1->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " mu = " ;
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (isnan(mu))\n";
+ ss << " mu = 0.0;\n";
+ ss << " }\n";
+ }
+ else if(pCur1->GetType() == formula::svDouble)
+ {
+ ss << " mu = " << pCur1->GetDouble() << ";\n";
+ }
+ else
+ {
+ ss << " return DBL_MAX;\n";
+ ss << "}\n";
+ return ;
+ }
+ }
+ else
+ {
+ ss << " mu=" ;
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ";\n";
+ }
+ if(ocPush == vSubArguments[2]->GetFormulaToken()->GetOpCode())
+ {
+ if(pCur2->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR2 =
+ static_cast<const formula::SingleVectorRefToken* >(pCur2);
+ ss << " if (gid0 < " << pSVR2->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " sigma = " ;
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (isnan(sigma))\n";
+ ss << " sigma = 0.0;\n";
+ ss << " }\n";
+ }
+ else if(pCur2->GetType() == formula::svDouble)
+ {
+ ss << " sigma = " << pCur2->GetDouble() << ";\n";
+ }
+ else
+ {
+ ss << " return DBL_MAX;\n";
+ ss << "}\n";
+ return ;
+ }
+ }
+ else
+ {
+ ss << " sigma = " ;
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef() << ";\n";
+ }
+ ss << " return 0.5 - gauss((mue-mu)*sqrt(fCount)/sigma);\n";
+ ss << "}\n";
+ }
+}
+
+void OpTTest::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(fMachEpsDecl);
+ funs.insert("");
+ decls.insert(fMaxGammaArgumentDecl);
+ funs.insert("");
+ decls.insert(lcl_getLanczosSumDecl);
+ funs.insert(lcl_getLanczosSum);
+ decls.insert(GetBetaDecl);
+ funs.insert(GetBeta);
+ decls.insert(GetLogBetaDecl);
+ funs.insert(GetLogBeta);
+ decls.insert(GetBetaDistPDFDecl);
+ funs.insert(GetBetaDistPDF);
+ decls.insert(lcl_GetBetaHelperContFracDecl);
+ funs.insert(lcl_GetBetaHelperContFrac);
+ decls.insert(GetBetaDistDecl);
+ funs.insert(GetBetaDist);
+ decls.insert(GetTDistDecl);
+ funs.insert(GetTDist);
+}
+
+void OpTTest::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_" << BinFuncName() << "(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << "){\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double fSum1 = 0.0;\n";
+ ss << " double fSum2 = 0.0;\n";
+ ss << " double fSumSqr1 = 0.0;\n";
+ ss << " double fSumSqr2 = 0.0;\n";
+ ss << " double fCount1 = 0.0;\n";
+ ss << " double fCount2 = 0.0;\n";
+ ss << " double arg1 = 0.0;\n";
+ ss << " double arg2 = 0.0;\n";
+ ss << " double mode = 0.0;\n";
+ ss << " double type = 0.0;\n";
+ ss << " double fT = 0.0;\n";
+ ss << " double fF = 0.0;\n";
+ if(vSubArguments.size() != 4)
+ {
+ ss << " return DBL_MAX;\n";
+ ss << "}\n";
+ return ;
+ }
+ if(vSubArguments.size() != 4)
+ return;
+
+ FormulaToken *pCur = vSubArguments[0]->GetFormulaToken();
+ FormulaToken *pCur1 = vSubArguments[1]->GetFormulaToken();
+ FormulaToken *pCur2 = vSubArguments[2]->GetFormulaToken();
+ FormulaToken *pCur3 = vSubArguments[3]->GetFormulaToken();
+ assert(pCur);
+ assert(pCur1);
+ assert(pCur2);
+ assert(pCur3);
+ if(ocPush == vSubArguments[2]->GetFormulaToken()->GetOpCode())
+ {
+ if(pCur2->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken*>(pCur2);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " mode = " ;
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (isnan(mode))\n";
+ ss << " mode = 0.0;\n";
+ ss << " else\n";
+ ss << " mode = floor(mode);\n";
+ ss << " }\n";
+ }
+ else if(pCur2->GetType() == formula::svDouble)
+ {
+ ss << " mode = floor(convert_double(";
+ ss << pCur2->GetDouble() << "));\n";
+ }
+ else
+ {
+ ss << " return DBL_MAX;\n";
+ ss << "}\n";
+ return ;
+ }
+ }
+ else
+ {
+ ss << " mode = floor(" ;
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef() << ");\n";
+ }
+ ss << " if(!(mode == 1.0 || mode == 2.0))\n";
+ ss << " return DBL_MAX;\n";
+ if(ocPush==vSubArguments[3]->GetFormulaToken()->GetOpCode())
+ {
+ if(pCur3->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken*>(pCur3);
+ assert(pSVR);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " if (isnan(";
+ ss << vSubArguments[3]->GenSlidingWindowDeclRef() << "))\n";
+ ss << " type=0.0;\n";
+ ss << " else\n";
+ ss << " type=floor(";
+ ss << vSubArguments[3]->GenSlidingWindowDeclRef() << ");\n";
+ ss << " }\n";
+ }
+ else if(pCur3->GetType() == formula::svDouble)
+ {
+ ss << " type = floor(convert_double(" << pCur3->GetDouble() <<
+ "));\n";
+ }
+ else
+ {
+ ss << " return DBL_MAX;\n";
+ ss << "}\n";
+ return ;
+ }
+ }
+ else
+ {
+ ss << " type=floor(";
+ ss << vSubArguments[3]->GenSlidingWindowDeclRef() << ");\n";
+ }
+ ss << " if(!(type == 1.0||type == 2.0||type == 3.0))\n";
+ ss << " return DBL_MAX;\n";
+
+ if(pCur->GetType() == formula::svDoubleVectorRef &&
+ pCur1->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ const formula::DoubleVectorRefToken* pDVR1 =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur1);
+
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ size_t nCurWindowSize1 = pDVR1->GetRefRowSize();
+
+ if(nCurWindowSize == nCurWindowSize1)
+ {
+ ss << " if(type == 1.0)\n";
+ ss << " {\n";
+ ss << " for (int i = ";
+ if ((!pDVR->IsStartFixed() && pDVR->IsEndFixed()) &&
+ (!pDVR1->IsStartFixed() && pDVR1->IsEndFixed()))
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if ((pDVR->IsStartFixed() && !pDVR->IsEndFixed()) &&
+ (pDVR1->IsStartFixed() && !pDVR1->IsEndFixed()))
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+"<< nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if ((!pDVR->IsStartFixed() && !pDVR->IsEndFixed()) &&
+ (!pDVR1->IsStartFixed() && !pDVR1->IsEndFixed()))
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if ((pDVR->IsStartFixed() && pDVR->IsEndFixed()) &&
+ (pDVR1->IsStartFixed() && pDVR1->IsEndFixed()))
+ {
+ ss << "0; i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ ss << " break;\n";
+ ss << " }";
+ ss << " return DBL_MAX;\n";
+ ss << " }\n";
+ ss << "}\n";
+ return ;
+ }
+
+ ss << " arg1 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef(true) << ";\n";
+ ss << " arg2 = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef(true) << ";\n";
+ ss << " if (isnan(arg1)||isnan(arg2))\n";
+ ss << " continue;\n";
+ ss << " fSum1 += arg1;\n";
+ ss << " fSum2 += arg2;\n";
+ ss << " fSumSqr1 += (arg1 - arg2)*(arg1 - arg2);\n";
+ ss << " fCount1 += 1;\n";
+ ss << " }\n";
+ ss << " if(fCount1 < 1.0)\n";
+ ss << " return DBL_MAX;\n";
+ ss << " fT = sqrt(fCount1-1.0) * fabs(fSum1 - fSum2)\n";
+ ss << " /sqrt(fCount1 * fSumSqr1 - (fSum1-fSum2)\n";
+ ss << " *(fSum1-fSum2));\n";
+ ss << " fF = fCount1 - 1.0;\n";
+ }
+ else
+ {
+ ss << " return DBL_MAX;\n";
+ ss << "}\n";
+ return ;
+ }
+ }
+ else
+ {
+ ss << " return DBL_MAX;\n";
+ ss << "}\n";
+ return ;
+ }
+ ss << " }\n";
+ ss << " if(type == 2.0 || type == 3.0)\n";
+ ss << " {\n";
+
+ if(pCur->GetType() == formula::svDoubleVectorRef &&
+ pCur1->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ const formula::DoubleVectorRefToken* pDVR1 =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur1);
+
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ size_t nCurWindowSize1 = pDVR1->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+"<< nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+
+ ss << " arg1 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef(true) << ";\n";
+ ss << " if (isnan(arg1))\n";
+ ss << " continue;\n";
+ ss << " fSum1 += arg1;\n";
+ ss << " fSumSqr1 += arg1 * arg1;\n";
+ ss << " fCount1 += 1;\n";
+ ss << " }\n";
+
+ ss << " for (int i = ";
+ if (!pDVR1->IsStartFixed() && pDVR1->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR1->GetArrayLength();
+ ss << " && i < " << nCurWindowSize1 << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (pDVR1->IsStartFixed() && !pDVR1->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR1->GetArrayLength();
+ ss << " && i < gid0+"<< nCurWindowSize1 << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (!pDVR1->IsStartFixed() && !pDVR1->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR1->GetArrayLength();
+ ss << " && i < " << nCurWindowSize1 << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < " << nCurWindowSize1 << "; i++)\n";
+ ss << " {\n";
+ }
+ ss << " arg2 = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef(true) << ";\n";
+ ss << " if (isnan(arg2))\n";
+ ss << " continue;\n";
+ ss << " fSum2 += arg2;\n";
+ ss << " fSumSqr2 += arg2 * arg2;\n";
+ ss << " fCount2 += 1;\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " return DBL_MAX;\n";
+ ss << " }\n";
+ ss << "}\n";
+ return ;
+ }
+ ss << " if (fCount1 < 2.0 || fCount2 < 2.0)\n";
+ ss << " return DBL_MAX;\n";
+ ss << " }\n";
+ ss << " if(type == 3.0)\n";
+ ss << " {\n";
+ ss << " double fS1 = (fSumSqr1-fSum1*fSum1/fCount1)\n";
+ ss << " /(fCount1-1.0)/fCount1;\n";
+ ss << " double fS2 = (fSumSqr2-fSum2*fSum2/fCount2)\n";
+ ss << " /(fCount2-1.0)/fCount2;\n";
+ ss << " if (fS1 + fS2 == 0.0)\n";
+ ss << " return DBL_MAX;\n";
+ ss << " fT = fabs(fSum1/fCount1 - fSum2/fCount2)\n";
+ ss << " /sqrt(fS1+fS2);\n";
+ ss << " double c = fS1/(fS1+fS2);\n";
+ ss << " fF = 1.0/(c*c/(fCount1-1.0)+(1.0-c)*(1.0-c)\n";
+ ss << " /(fCount2-1.0));\n";
+ ss << " }\n";
+ ss << " if(type == 2.0)\n";
+ ss << " {\n";
+ ss << " double fS1 = (fSumSqr1 - fSum1*fSum1/fCount1)\n";
+ ss << " /(fCount1 - 1.0);\n";
+ ss << " double fS2 = (fSumSqr2 - fSum2*fSum2/fCount2)\n";
+ ss << " /(fCount2 - 1.0);\n";
+ ss << " fT = fabs( fSum1/fCount1 - fSum2/fCount2 )\n";
+ ss << " /sqrt( (fCount1-1.0)*fS1 + (fCount2-1.0)*fS2 )\n";
+ ss << " *sqrt( fCount1*fCount2*(fCount1+fCount2-2)\n";
+ ss << " /(fCount1+fCount2) );\n";
+ ss << " fF = fCount1 + fCount2 - 2;\n";
+ ss << " }\n";
+
+ ss << " double tdist=GetTDist(fT, fF);\n";
+ ss << " if (mode==1)\n";
+ ss << " return tdist;\n";
+ ss << " else\n";
+ ss << " return 2.0*tdist;\n";
+ ss << "}\n";
+}
+void OpVarP::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_" << BinFuncName() << "(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << "){\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double fSum = 0.0;\n";
+ ss << " double fMean = 0.0;\n";
+ ss << " double vSum = 0.0;\n";
+ ss << " double fCount = 0.0;\n";
+ ss << " double arg = 0.0;\n";
+ unsigned i = vSubArguments.size();
+ while (i--)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if(ocPush == vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < " << pDVR->GetArrayLength() << "; i++)\n";
+ ss << " {\n";
+ }
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (isnan(arg))\n";
+ ss << " continue;\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount += 1.0;\n";
+ ss << " }\n";
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken*>(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (!isnan(arg))\n";
+ ss << " {\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount += 1.0;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " arg = " << pCur->GetDouble() << ";\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount += 1.0;\n";
+ }
+ }
+ else
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount += 1.0;\n";
+ }
+ if (i == 0)
+ {
+ ss << " fMean = fSum * pow(fCount,-1.0);\n";
+ }
+ }
+ i = vSubArguments.size();
+ while (i--)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if(ocPush == vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < " << pDVR->GetArrayLength() << "; i++)\n";
+ ss << " {\n";
+ }
+
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (isnan(arg))\n";
+ ss << " continue;\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ ss << " }\n";
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken*>(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (!isnan(arg))\n";
+ ss << " {\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " arg = " << pCur->GetDouble() << ";\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ }
+ }
+ else
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ }
+ }
+ ss << " if (fCount == 0.0)\n";
+ ss << " return DBL_MAX;\n";
+ ss << " else\n";
+ ss << " return vSum * pow(fCount,-1.0);\n";
+ ss << "}\n";
+}
+
+void OpTDist::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(fMachEpsDecl);
+ funs.insert("");
+ decls.insert(fMaxGammaArgumentDecl);
+ funs.insert("");
+ decls.insert(lcl_getLanczosSumDecl);
+ funs.insert(lcl_getLanczosSum);
+ decls.insert(GetBetaDecl);
+ funs.insert(GetBeta);
+ decls.insert(GetLogBetaDecl);
+ funs.insert(GetLogBeta);
+ decls.insert(GetBetaDistPDFDecl);
+ funs.insert(GetBetaDistPDF);
+ decls.insert(lcl_GetBetaHelperContFracDecl);
+ funs.insert(lcl_GetBetaHelperContFrac);
+ decls.insert(GetBetaDistDecl);
+ funs.insert(GetBetaDist);
+ decls.insert(GetTDistDecl);
+ funs.insert(GetTDist);
+}
+void OpTDist::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_" << BinFuncName() << "(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double x = 0.0;\n";
+ ss << " double fDF = 0.0;\n";
+ ss << " double fFlag = 0.0;\n";
+ if(vSubArguments.size() != 3)
+ {
+ ss << " return DBL_MAX;\n}\n";
+ return ;
+ }
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ assert(tmpCur0);
+ if(ocPush == vSubArguments[0]->GetFormulaToken()->GetOpCode())
+ {
+ if(tmpCur0->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR0 =
+ static_cast<const formula::SingleVectorRefToken *>(tmpCur0);
+ ss << " if(gid0 < ";
+ ss << tmpCurDVR0->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " x = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if(isnan(x))\n";
+ ss << " x = 0.0;\n";
+ ss << " }\n";
+ }
+ else if(tmpCur0->GetType() == formula::svDouble)
+ {
+ ss << " x = " << tmpCur0->GetDouble() << ";\n";
+ }
+ else
+ {
+ ss << " return DBL_MAX;\n}\n";
+ return ;
+ }
+ }
+ else
+ {
+ ss << " x = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ }
+ FormulaToken *tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ assert(tmpCur1);
+ if(ocPush == vSubArguments[1]->GetFormulaToken()->GetOpCode())
+ {
+ if(tmpCur1->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR1 =
+ static_cast<const formula::SingleVectorRefToken *>(tmpCur1);
+ ss << " if(gid0 < ";
+ ss << tmpCurDVR1->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " fDF = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if(isnan(fDF))\n";
+ ss << " fDF = 0.0;\n";
+ ss << " else\n";
+ ss << " fDF = floor(fDF);\n";
+ ss << " }\n";
+ }
+ else if(tmpCur1->GetType() == formula::svDouble)
+ {
+ ss << " fDF = floor(convert_double(";
+ ss << tmpCur1->GetDouble() << "));\n";
+ }
+ else
+ {
+ ss << " return DBL_MAX;\n}\n";
+ return ;
+ }
+ }
+ else
+ {
+ ss << " fDF = floor(";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ");\n";
+ }
+
+ FormulaToken *tmpCur2 = vSubArguments[2]->GetFormulaToken();
+ assert(tmpCur2);
+ if(ocPush == vSubArguments[2]->GetFormulaToken()->GetOpCode())
+ {
+ if(tmpCur2->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR2 =
+ static_cast<const formula::SingleVectorRefToken *>(tmpCur2);
+ ss << " if(gid0 < ";
+ ss << tmpCurDVR2->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " fFlag = ";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if(isnan(fFlag))\n";
+ ss << " fFlag = 0.0;\n";
+ ss << " else\n";
+ ss << " fFlag = floor(fFlag);\n";
+ ss << " }\n";
+
+ }
+ else if(tmpCur2->GetType() == formula::svDouble)
+ {
+ ss << " fFlag = floor(convert_double(";
+ ss << tmpCur2->GetDouble() << "));\n";
+ }
+ else
+ {
+ ss << " return DBL_MAX;\n}\n";
+ return ;
+ }
+ }
+ else
+ {
+ ss << " fFlag = floor(";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef() << ");\n";
+ }
+ ss << " if(fDF < 1.0 || x < 0.0 || (fFlag != 1.0 && fFlag != 2.0))\n";
+ ss << " return DBL_MAX;\n";
+ ss << " double R = GetTDist(x, fDF);\n";
+ ss << " if (fFlag == 1.0)\n";
+ ss << " return R;\n";
+ ss << " else\n";
+ ss << " return 2.0 * R;\n";
+ ss << "}\n";
+}
+void OpExponDist::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " double tmp = 0,tmp0=0,tmp1=0,tmp2=0;\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double rx,rlambda,rkum;\n";
+ ss <<"\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << "if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << "{\n";
+ }
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " tmp"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " tmp"<<i<<"=\n";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n}\n";
+ }
+ else
+ {
+ ss << "tmp"<<i<<"="<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ }
+ }
+ ss << " rx = tmp0;\n";
+ ss << " rlambda = tmp1;\n";
+ ss << " rkum = tmp2;\n";
+ ss <<" if(rlambda <= 0.0)\n";
+ ss <<" {\n";
+ ss <<" tmp = -DBL_MAX;\n";
+ ss <<" }\n";
+ ss <<" else if(rkum == 0)\n";
+ ss <<" {\n";
+ ss <<" if(rx >= 0)\n";
+ ss <<" tmp = rlambda*exp(-rlambda*rx);\n";
+ ss <<" else\n";
+ ss <<" tmp = 0.0;\n";
+ ss <<" }\n";
+ ss <<" else\n";
+ ss <<" {\n";
+ ss <<" if(rx > 0)\n";
+ ss <<" tmp = 1.0 - exp(-rlambda*rx);\n";
+ ss <<" else\n";
+ ss <<" tmp = 0.0;\n";
+ ss <<" }\n";
+ ss <<" return tmp;\n";
+ ss <<"}";
+}
+void OpFdist::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(GetFDistDecl);decls.insert(GetBetaDistDecl);
+ decls.insert(GetBetaDecl);decls.insert(fMaxGammaArgumentDecl);
+ decls.insert(lcl_GetBetaHelperContFracDecl);
+ decls.insert(GetBetaDistPDFDecl);
+ decls.insert(GetLogBetaDecl);decls.insert(lcl_getLanczosSumDecl);
+ decls.insert(fMachEpsDecl);
+ funs.insert(GetFDist);funs.insert(GetBetaDist);
+ funs.insert(GetBeta);
+ funs.insert(lcl_GetBetaHelperContFrac);funs.insert(GetBetaDistPDF);
+ funs.insert(GetLogBeta);
+ funs.insert(lcl_getLanczosSum);
+}
+void OpFdist::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " double tmp = 0,tmp0=0,tmp1=0,tmp2=0;\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double rF1,rF2,rX;\n";
+ ss <<"\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << "if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << "{\n";
+ }
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " tmp"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " tmp"<<i<<"=\n";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n}\n";
+ }
+ else
+ {
+ ss << "tmp"<<i<<"="<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ }
+ }
+ ss << " rX = tmp0;\n";
+ ss << " rF1 = floor(tmp1);\n";
+ ss << " rF2 = floor(tmp2);\n";
+ ss <<" if (rX < 0.0 || rF1 < 1.0 || rF2 < 1.0 || rF1 >= 1.0E10 ||";
+ ss <<"rF2 >= 1.0E10)\n";
+ ss <<" {\n";
+ ss <<" tmp = -DBL_MAX;\n";
+ ss <<" }\n";
+ ss <<" tmp = GetFDist(rX, rF1, rF2);\n";
+ ss <<" return tmp;\n";
+ ss <<"}";
+}
+
+void OpStandard::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_" << BinFuncName() << "(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double x = 0.0;\n";
+ ss << " double mu = 0.0;\n";
+ ss << " double sigma = 0.0;\n";
+ if(vSubArguments.size() != 3)
+ {
+ ss << " return DBL_MAX;\n}\n";
+ return ;
+ }
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ assert(tmpCur0);
+ if(ocPush == vSubArguments[0]->GetFormulaToken()->GetOpCode())
+ {
+ if(tmpCur0->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurSVR0 =
+ static_cast<const formula::SingleVectorRefToken *>(tmpCur0);
+ ss << " if (gid0 < " << tmpCurSVR0->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " x = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (isnan(x))\n";
+ ss << " x = 0.0;\n";
+ ss << " }\n";
+ }
+ else if(tmpCur0->GetType() == formula::svDouble)
+ {
+ ss << " x = " << tmpCur0->GetDouble() << ";\n";
+ }
+ else
+ {
+ ss << " return DBL_MAX;\n}\n";
+ return ;
+ }
+ }
+ else
+ {
+ ss << " x = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ }
+
+ FormulaToken *tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ assert(tmpCur1);
+ if(ocPush == vSubArguments[1]->GetFormulaToken()->GetOpCode())
+ {
+ if(tmpCur1->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurSVR1 =
+ static_cast<const formula::SingleVectorRefToken *>(tmpCur1);
+ ss << " if (gid0 < " << tmpCurSVR1->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " mu = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (isnan(mu))\n";
+ ss << " mu = 0.0;\n";
+ ss << " }\n";
+ }
+ else if(tmpCur1->GetType() == formula::svDouble)
+ {
+ ss << " mu = " << tmpCur1->GetDouble() << ";\n";
+ }
+ else
+ {
+ ss << " return DBL_MAX;\n}\n";
+ return ;
+ }
+ }
+ else
+ {
+ ss << " mu = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ";\n";
+ }
+
+ FormulaToken *tmpCur2 = vSubArguments[2]->GetFormulaToken();
+ assert(tmpCur2);
+ if(ocPush == vSubArguments[2]->GetFormulaToken()->GetOpCode())
+ {
+ if(tmpCur2->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurSVR2 =
+ static_cast<const formula::SingleVectorRefToken *>(tmpCur2);
+ ss << " if (gid0 < " << tmpCurSVR2->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " sigma = ";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (isnan(sigma))\n";
+ ss << " sigma = 0.0;\n";
+ ss << " }\n";
+ }
+ else if(tmpCur2->GetType() == formula::svDouble)
+ {
+ ss << " sigma = " << tmpCur2->GetDouble() << ";\n";
+ }
+ else
+ {
+ ss << " return DBL_MAX;\n";
+ ss << "}\n";
+ return ;
+ }
+ }
+ else
+ {
+ ss << " sigma = ";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef() << ";\n";
+ }
+
+ ss << " if(sigma <= 0.0)\n";
+ ss << " return DBL_MAX;\n";
+ ss << " else\n";
+ ss << " return (x - mu)*pow(sigma,-1.0);\n";
+ ss << "}";
+}
+
+void OpWeibull::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_" << BinFuncName() << "(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double x = 0.0;\n";
+ ss << " double alpha = 0.0;\n";
+ ss << " double beta = 0.0;\n";
+ ss << " double kum = 0.0;\n";
+ if(vSubArguments.size() != 4)
+ {
+ ss << " return DBL_MAX;\n}\n";
+ return ;
+ }
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ assert(tmpCur0);
+ if(ocPush == vSubArguments[0]->GetFormulaToken()->GetOpCode())
+ {
+ if(tmpCur0->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurSVR0 =
+ static_cast<const formula::SingleVectorRefToken *>(tmpCur0);
+ ss << " if (gid0 < " << tmpCurSVR0->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " x = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (isnan(x))\n";
+ ss << " x = 0.0;\n";
+ ss << " }\n";
+ }
+ else if(tmpCur0->GetType() == formula::svDouble)
+ {
+ ss << " x = " << tmpCur0->GetDouble() << ";\n";
+ }
+ else
+ {
+ ss << " return DBL_MAX;\n}\n";
+ return ;
+ }
+ }
+ else
+ {
+ ss << " x = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ }
+
+ FormulaToken *tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ assert(tmpCur1);
+ if(ocPush == vSubArguments[1]->GetFormulaToken()->GetOpCode())
+ {
+ if(tmpCur1->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurSVR1 =
+ static_cast<const formula::SingleVectorRefToken *>(tmpCur1);
+ ss << " if (gid0 < " << tmpCurSVR1->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " alpha = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (isnan(alpha))\n";
+ ss << " alpha = 0.0;\n";
+ ss << " }\n";
+ }
+ else if(tmpCur1->GetType() == formula::svDouble)
+ {
+ ss << " alpha = " << tmpCur1->GetDouble() << ";\n";
+ }
+ else
+ {
+ ss << " return DBL_MAX;\n}\n";
+ return ;
+ }
+ }
+ else
+ {
+ ss << " alpha = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ";\n";
+ }
+
+ FormulaToken *tmpCur2 = vSubArguments[2]->GetFormulaToken();
+ assert(tmpCur2);
+ if(ocPush == vSubArguments[2]->GetFormulaToken()->GetOpCode())
+ {
+ if(tmpCur2->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurSVR2 =
+ static_cast<const formula::SingleVectorRefToken *>(tmpCur2);
+ ss << " if (gid0 < " << tmpCurSVR2->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " beta = ";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (isnan(beta))\n";
+ ss << " beta = 0.0;\n";
+ ss << " }\n";
+ }
+ else if(tmpCur2->GetType() == formula::svDouble)
+ {
+ ss << " beta = " << tmpCur2->GetDouble() << ";\n";
+ }
+ else
+ {
+ ss << " return DBL_MAX;\n}\n";
+ return ;
+ }
+ }
+ else
+ {
+ ss << " beta = ";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef() << ";\n";
+ }
+
+ FormulaToken *tmpCur3 = vSubArguments[3]->GetFormulaToken();
+ assert(tmpCur3);
+ if(ocPush == vSubArguments[3]->GetFormulaToken()->GetOpCode())
+ {
+ if(tmpCur3->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurSVR3 =
+ static_cast<const formula::SingleVectorRefToken *>(tmpCur3);
+ ss << " if (gid0 < " << tmpCurSVR3->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " kum = ";
+ ss << vSubArguments[3]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (isnan(kum))\n";
+ ss << " kum = 0.0;\n";
+ ss << " }\n";
+ }
+ else if(tmpCur3->GetType() == formula::svDouble)
+ {
+ ss << " kum = " << tmpCur3->GetDouble() << ";\n";
+ }
+ else
+ {
+ ss << " return DBL_MAX;\n}\n";
+ return ;
+ }
+ }
+ else
+ {
+ ss << " kum = ";
+ ss << vSubArguments[3]->GenSlidingWindowDeclRef() << ";\n";
+ }
+
+ ss << " if(alpha <= 0.0 || beta <=0.0 || kum < 0.0)\n";
+ ss << " return DBL_MAX;\n";
+ ss << " else if(kum == 0.0)\n";
+ ss << " {\n";
+ ss << " return alpha*pow(pow(beta,alpha),-1.0)*pow(x,alpha-1.0)";
+ ss << "*exp(-pow(x*pow(beta,-1.0),alpha));\n";
+ ss << " }\n";
+ ss << " else\n";
+ ss << " return 1.0-exp(-pow(x*pow(beta,-1.0),alpha));\n";
+ ss << "}\n";
+}
+
+void OpSkew::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_" << BinFuncName() << "(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << "){\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double fSum = 0.0;\n";
+ ss << " double fMean = 0.0;\n";
+ ss << " double vSum = 0.0;\n";
+ ss << " double fCount = 0.0;\n";
+ ss << " double arg = 0.0;\n";
+ unsigned i = vSubArguments.size();
+ while (i--)
+ {
+ FormulaToken* pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if(ocPush == vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < " << pDVR->GetArrayLength() << "; i++)\n";
+ ss << " {\n";
+ }
+
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (isnan(arg))\n";
+ ss << " continue;\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount += 1.0;\n";
+ ss << " }\n";
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (!isnan(arg))\n";
+ ss << " {\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount += 1.0;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " arg = " << pCur->GetDouble() << ";\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount += 1.0;\n";
+ }
+ }
+ else
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount += 1.0;\n";
+ }
+
+ if(i == 0)
+ {
+ ss << " if(fCount <= 2.0)\n";
+ ss << " return DBL_MAX;\n";
+ ss << " else\n";
+ ss << " fMean = fSum * pow(fCount,-1.0);\n";
+ }
+ }
+ i = vSubArguments.size();
+ while (i--)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if(ocPush == vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < " << pDVR->GetArrayLength() << "; i++)\n";
+ ss << " {\n";
+ }
+
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (isnan(arg))\n";
+ ss << " continue;\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ ss << " }\n";
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (!isnan(arg))\n";
+ ss << " {\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " arg = " << pCur->GetDouble() << ";\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ }
+ }
+ else
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ }
+ }
+ ss << " double fStdDev = sqrt(vSum * pow(fCount - 1.0,-1.0));\n";
+ ss << " double dx = 0.0;\n";
+ ss << " double xcube = 0.0;\n";
+ ss << " if(fStdDev == 0.0)\n";
+ ss << " return DBL_MAX;\n";
+ i = vSubArguments.size();
+ while (i--)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if(ocPush == vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < " << pDVR->GetArrayLength() << "; i++)\n";
+ ss << " {\n";
+ }
+
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (isnan(arg))\n";
+ ss << " continue;\n";
+ ss << " dx = (arg - fMean) * pow(fStdDev,-1.0);\n";
+ ss << " xcube = xcube + dx * dx * dx;\n";
+ ss << " }\n";
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (!isnan(arg))\n";
+ ss << " {\n";
+ ss << " dx = (arg - fMean) * pow(fStdDev,-1.0);\n";
+ ss << " xcube = xcube + dx * dx * dx;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " arg = " << pCur->GetDouble() << ";\n";
+ ss << " dx = (arg - fMean) * pow(fStdDev,-1.0);\n";
+ ss << " xcube = xcube + dx * dx * dx;\n";
+ }
+ }
+ else
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " dx = (arg - fMean) * pow(fStdDev,-1.0);\n";
+ ss << " xcube = xcube + dx * dx * dx;\n";
+ }
+ }
+ ss << " return ((xcube * fCount) * pow(fCount - 1.0,-1.0))";
+ ss << " * pow(fCount - 2.0,-1.0);\n";
+ ss << "}\n";
+}
+
+void OpSkewp::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_" << BinFuncName() << "(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << "){\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double fSum = 0.0;\n";
+ ss << " double fMean = 0.0;\n";
+ ss << " double vSum = 0.0;\n";
+ ss << " double fCount = 0.0;\n";
+ ss << " double arg = 0.0;\n";
+ unsigned i = vSubArguments.size();
+ while (i--)
+ {
+ FormulaToken* pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if(ocPush == vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < " << pDVR->GetArrayLength() << "; i++)\n";
+ ss << " {\n";
+ }
+
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (isnan(arg))\n";
+ ss << " continue;\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount += 1.0;\n";
+ ss << " }\n";
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (!isnan(arg))\n";
+ ss << " {\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount += 1.0;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " arg = " << pCur->GetDouble() << ";\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount += 1.0;\n";
+ }
+ }
+ else
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount += 1.0;\n";
+ }
+
+ if(i == 0)
+ {
+ ss << " if(fCount <= 2.0)\n";
+ ss << " return DBL_MAX;\n";
+ ss << " else\n";
+ ss << " fMean = fSum * pow(fCount,-1.0);\n";
+ }
+ }
+ i = vSubArguments.size();
+ while (i--)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if(ocPush == vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < " << pDVR->GetArrayLength() << "; i++)\n";
+ ss << " {\n";
+ }
+
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (isnan(arg))\n";
+ ss << " continue;\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ ss << " }\n";
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (!isnan(arg))\n";
+ ss << " {\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " arg = " << pCur->GetDouble() << ";\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ }
+ }
+ else
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ }
+ }
+ ss << " double fStdDev = sqrt(vSum * pow(fCount,-1.0));\n";
+ ss << " double dx = 0.0;\n";
+ ss << " double xcube = 0.0;\n";
+ ss << " if(fStdDev == 0.0)\n";
+ ss << " return DBL_MAX;\n";
+ i = vSubArguments.size();
+ while (i--)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if(ocPush == vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < " << pDVR->GetArrayLength() << "; i++)\n";
+ ss << " {\n";
+ }
+
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (isnan(arg))\n";
+ ss << " continue;\n";
+ ss << " dx = (arg - fMean) * pow(fStdDev,-1.0);\n";
+ ss << " xcube = xcube + dx * dx * dx;\n";
+ ss << " }\n";
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (!isnan(arg))\n";
+ ss << " {\n";
+ ss << " dx = (arg - fMean) * pow(fStdDev,-1.0);\n";
+ ss << " xcube = xcube + dx * dx * dx;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " arg = " << pCur->GetDouble() << ";\n";
+ ss << " dx = (arg - fMean) * pow(fStdDev,-1.0);\n";
+ ss << " xcube = xcube + dx * dx * dx;\n";
+ }
+ }
+ else
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " dx = (arg - fMean) * pow(fStdDev,-1.0);\n";
+ ss << " xcube = xcube + dx * dx * dx;\n";
+ }
+ }
+ ss << " return xcube * pow(fCount,-1.0);\n";
+ ss << "}\n";
+}
+
+void OpTInv::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(fMachEpsDecl);
+ funs.insert("");
+ decls.insert(fMaxGammaArgumentDecl);
+ funs.insert("");
+ decls.insert(lcl_getLanczosSumDecl);
+ funs.insert(lcl_getLanczosSum);
+ decls.insert(GetBetaDecl);
+ funs.insert(GetBeta);
+ decls.insert(GetLogBetaDecl);
+ funs.insert(GetLogBeta);
+ decls.insert(GetBetaDistPDFDecl);
+ funs.insert(GetBetaDistPDF);
+ decls.insert(lcl_GetBetaHelperContFracDecl);
+ funs.insert(lcl_GetBetaHelperContFrac);
+ decls.insert(GetBetaDistDecl);
+ funs.insert(GetBetaDist);
+ decls.insert(GetTDistDecl);
+ funs.insert(GetTDist);
+ decls.insert(GetValueDecl);
+ funs.insert(GetValue);
+ decls.insert(lcl_HasChangeOfSignDecl);
+ funs.insert(lcl_HasChangeOfSign);
+ decls.insert(lcl_IterateInverseDecl);
+ funs.insert(lcl_IterateInverse);
+}
+
+void OpTInv::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_" << BinFuncName() << "(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double x = 0.0;\n";
+ ss << " double fDF = 0.0;\n";
+ if(vSubArguments.size() != 2)
+ {
+ ss << " return DBL_MAX;\n}\n";
+ return ;
+ }
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ assert(tmpCur0);
+ if(ocPush == vSubArguments[0]->GetFormulaToken()->GetOpCode())
+ {
+ if(tmpCur0->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR0 =
+ static_cast<const formula::SingleVectorRefToken *>(tmpCur0);
+ ss << " if(gid0 < ";
+ ss << tmpCurDVR0->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " x = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if(isnan(x))\n";
+ ss << " x = 0.0;\n";
+ ss << " }\n";
+ }
+ else if(tmpCur0->GetType() == formula::svDouble)
+ {
+ ss << " x = " << tmpCur0->GetDouble() << ";\n";
+ }
+ else
+ {
+ ss << " return DBL_MAX;\n}\n";
+ return ;
+ }
+ }
+ else
+ {
+ ss << " x = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ }
+ FormulaToken *tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ assert(tmpCur1);
+ if(ocPush == vSubArguments[1]->GetFormulaToken()->GetOpCode())
+ {
+ if(tmpCur1->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken*tmpCurDVR1 =
+ static_cast<const formula::SingleVectorRefToken *>(tmpCur1);
+ ss << " if(gid0 < ";
+ ss << tmpCurDVR1->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " fDF = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if(isnan(fDF))\n";
+ ss << " fDF = 0.0;\n";
+ ss << " else\n";
+ ss << " fDF = floor(fDF);\n";
+ ss << " }\n";
+ }
+ else if(tmpCur1->GetType() == formula::svDouble)
+ {
+ ss << " fDF = floor(convert_double(";
+ ss << tmpCur1->GetDouble() << "));\n";
+ }
+ else
+ {
+ ss << " return DBL_MAX;\n}\n";
+ return ;
+ }
+ }
+ else
+ {
+ ss << " fDF = floor(";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ");\n";
+ }
+ ss << " if (x > 1.0||fDF < 1.0 || fDF > 1.0E10 || x <= 0.0)\n";
+ ss << " return DBL_MAX;\n";
+ ss << " bool bConvError;\n";
+ ss << " double fVal = lcl_IterateInverse(\n";
+ ss << " fDF*0.5, fDF, &bConvError,x,fDF );\n";
+ ss << " if (bConvError)\n";
+ ss << " return DBL_MAX;\n";
+ ss << " return fVal;\n";
+ ss << "}\n";
+}
+
+void OpStDev::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_" << BinFuncName() << "(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << "){\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double fSum = 0.0;\n";
+ ss << " double vSum = 0.0;\n";
+ ss << " double fMean = 0.0;\n";
+ ss << " double fCount = 0.0;\n";
+ ss << " double arg = 0.0;\n";
+ unsigned i = vSubArguments.size();
+ while (i--)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if(ocPush == vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < " << pDVR->GetArrayLength() << "; i++)\n";
+ ss << " {\n";
+ }
+
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (isnan(arg))\n";
+ ss << " continue;\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount += 1.0;\n";
+ ss << " }\n";
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (!isnan(arg))\n";
+ ss << " {\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount += 1.0;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " arg = " << pCur->GetDouble() << ";\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount += 1.0;\n";
+ }
+ }
+ else
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount += 1.0;\n";
+ }
+ if (i == 0)
+ {
+ ss << " fMean = fSum * pow(fCount,-1.0);\n";
+ }
+ }
+ i = vSubArguments.size();
+ while (i--)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if(ocPush == vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < " << pDVR->GetArrayLength() << "; i++)\n";
+ ss << " {\n";
+ }
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (isnan(arg))\n";
+ ss << " continue;\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ ss << " }\n";
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (!isnan(arg))\n";
+ ss << " {\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " arg = " << pCur->GetDouble() << ";\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ }
+ }
+ else
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ }
+ }
+ ss << " if (fCount <= 1.0)\n";
+ ss << " return DBL_MAX;\n";
+ ss << " else\n";
+ ss << " return sqrt(vSum * pow(fCount - 1.0,-1.0));\n";
+ ss << "}\n";
+}
+
+void OpStDevP::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_" << BinFuncName() << "(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << "){\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double fSum = 0.0;\n";
+ ss << " double fMean = 0.0;\n";
+ ss << " double vSum = 0.0;\n";
+ ss << " double fCount = 0.0;\n";
+ ss << " double arg = 0.0;\n";
+ unsigned i = vSubArguments.size();
+ while (i--)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if(ocPush == vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < " << pDVR->GetArrayLength() << "; i++)\n";
+ ss << " {\n";
+ }
+
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (isnan(arg))\n";
+ ss << " continue;\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount += 1.0;\n";
+ ss << " }\n";
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (!isnan(arg))\n";
+ ss << " {\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount += 1.0;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " arg = " << pCur->GetDouble() << ";\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount += 1.0;\n";
+ }
+ }
+ else
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount += 1.0;\n";
+
+ }
+ if (i == 0)
+ {
+ ss << " fMean = fSum * pow(fCount,-1.0);\n";
+ }
+ }
+ i = vSubArguments.size();
+ while (i--)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if(ocPush == vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < " << pDVR->GetArrayLength() << "; i++)\n";
+ ss << " {\n";
+ }
+
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (isnan(arg))\n";
+ ss << " continue;\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ ss << " }\n";
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (!isnan(arg))\n";
+ ss << " {\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " arg = " << pCur->GetDouble() << ";\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ }
+ }
+ else
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ }
+ }
+ ss << " if (fCount == 0.0)\n";
+ ss << " return DBL_MAX;\n";
+ ss << " else\n";
+ ss << " return sqrt(vSum * pow(fCount,-1.0));\n";
+ ss << "}\n";
+}
+
+void OpSlope::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ CHECK_PARAMETER_COUNT(2,2);
+ ss << "\ndouble " << sSymName;
+ ss << "_" << BinFuncName() << "(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << "){\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double fSumX = 0.0;\n";
+ ss << " double fSumY = 0.0;\n";
+ ss << " double fMeanX = 0.0;\n";
+ ss << " double fMeanY = 0.0;\n";
+ ss << " double fSumDeltaXDeltaY = 0.0;\n";
+ ss << " double fSumSqrDeltaX = 0.0;\n";
+ ss << " double fCount = 0.0;\n";
+ ss << " double argX = 0.0;\n";
+ ss << " double argY = 0.0;\n";
+ FormulaToken *pCur = vSubArguments[1]->GetFormulaToken();
+ FormulaToken *pCur1 = vSubArguments[0]->GetFormulaToken();
+ assert(pCur);
+ assert(pCur1);
+ if (pCur->GetType() != formula::svDoubleVectorRef ||
+ pCur1->GetType() != formula::svDoubleVectorRef)
+ {
+ throw Unhandled(__FILE__, __LINE__);
+ }
+
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ const formula::DoubleVectorRefToken* pDVR1 =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur1);
+
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ size_t nCurWindowSize1 = pDVR1->GetRefRowSize();
+ size_t arrayLength = pDVR->GetArrayLength()<
+ pDVR1->GetArrayLength() ? pDVR->GetArrayLength():
+ pDVR1->GetArrayLength();
+ if(nCurWindowSize != nCurWindowSize1)
+ throw Unhandled(__FILE__, __LINE__);
+ ss << " for (int i = ";
+ if ((!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ &&(!pDVR1->IsStartFixed() && pDVR1->IsEndFixed()))
+ {
+ ss << "gid0; i < " << arrayLength;
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if ((pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ &&(pDVR1->IsStartFixed() && !pDVR1->IsEndFixed()))
+ {
+ ss << "0; i < " << arrayLength ;
+ ss << " && i < gid0+" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if ((!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ &&(!pDVR1->IsStartFixed() && !pDVR1->IsEndFixed()))
+ {
+ ss << "0; i + gid0 < " << arrayLength;
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if ((pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ &&(pDVR1->IsStartFixed() && pDVR1->IsEndFixed()))
+ {
+ ss << "0; i < " << arrayLength << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ throw Unhandled(__FILE__, __LINE__);
+ }
+
+ ss << " argX = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " argY = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (isnan(argX) || isnan(argY))\n";
+ ss << " continue;\n";
+ ss << " fSumX += argX;\n";
+ ss << " fSumY += argY;\n";
+ ss << " fCount += 1.0;\n";
+ ss << " }\n";
+
+ ss << " if (fCount < 1.0)\n";
+ ss << " return CreateDoubleError(NoValue);\n";
+ ss << " else\n";
+ ss << " {\n";
+ ss << " fMeanX = fSumX * pow(fCount,-1.0);\n";
+ ss << " fMeanY = fSumY * pow(fCount,-1.0);\n";
+
+ ss << " for (int i = ";
+ if ((!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ &&(!pDVR1->IsStartFixed() && pDVR1->IsEndFixed()))
+ {
+ ss << "gid0; i < " << arrayLength;
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if ((pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ &&(pDVR1->IsStartFixed() && !pDVR1->IsEndFixed()))
+ {
+ ss << "0; i < " << arrayLength ;
+ ss << " && i < gid0+" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if ((!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ &&(!pDVR1->IsStartFixed() && !pDVR1->IsEndFixed()))
+ {
+ ss << "0; i + gid0 < " << arrayLength;
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < " << arrayLength << "; i++)\n";
+ ss << " {\n";
+ }
+
+ ss << " argX = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " argY = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (isnan(argX) || isnan(argY))\n";
+ ss << " continue;\n";
+ ss << " fSumDeltaXDeltaY += (argX-fMeanX)*(argY-fMeanY);\n";
+ ss << " fSumSqrDeltaX += (argX-fMeanX) * (argX-fMeanX);\n";
+ ss << " }\n";
+ ss << " if(fSumSqrDeltaX == 0.0)\n";
+ ss << " return CreateDoubleError(DivisionByZero);\n";
+ ss << " else\n";
+ ss << " {\n";
+ ss << " return fSumDeltaXDeltaY*pow(fSumSqrDeltaX,-1.0);\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << "}\n";
+
+}
+void OpSTEYX::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_" << BinFuncName() << "(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << "){\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double fSumX = 0.0;\n";
+ ss << " double fSumY = 0.0;\n";
+ ss << " double fMeanX = 0.0;\n";
+ ss << " double fMeanY = 0.0;\n";
+ ss << " double fSumDeltaXDeltaY = 0.0;\n";
+ ss << " double fSumSqrDeltaX = 0.0;\n";
+ ss << " double fSumSqrDeltaY = 0.0;\n";
+ ss << " double fCount = 0.0;\n";
+ ss << " double argX = 0.0;\n";
+ ss << " double argY = 0.0;\n";
+ FormulaToken *pCur = vSubArguments[1]->GetFormulaToken();
+ FormulaToken *pCur1 = vSubArguments[0]->GetFormulaToken();
+ assert(pCur);
+ assert(pCur1);
+ if (pCur->GetType() == formula::svDoubleVectorRef&&
+ pCur1->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ const formula::DoubleVectorRefToken* pDVR1 =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur1);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ size_t nCurWindowSize1 = pDVR1->GetRefRowSize();
+ size_t arrayLength = pDVR->GetArrayLength()<
+ pDVR1->GetArrayLength() ? pDVR->GetArrayLength():
+ pDVR1->GetArrayLength();
+ if(nCurWindowSize != nCurWindowSize1)
+ {
+ ss << " return DBL_MAX;\n";
+ ss << "}\n";
+ return ;
+ }
+ ss << " for (int i = ";
+ if ((!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ &&(!pDVR1->IsStartFixed() && pDVR1->IsEndFixed()))
+ {
+ ss << "gid0; i < " << arrayLength;
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if ((pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ &&(pDVR1->IsStartFixed() && !pDVR1->IsEndFixed()))
+ {
+ ss << "0; i < " << arrayLength;
+ ss << " && i < gid0+" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if ((!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ &&(!pDVR1->IsStartFixed() && !pDVR1->IsEndFixed()))
+ {
+ ss << "0; i + gid0 < " << arrayLength;
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if ((pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ &&(pDVR1->IsStartFixed() && pDVR1->IsEndFixed()))
+ {
+ ss << "0; i < " << arrayLength << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ ss << " break;\n";
+ ss << " }";
+ ss << " return DBL_MAX;\n";
+ ss << "}\n";
+ return ;
+ }
+
+ ss << " argX = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " argY = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (isnan(argX) || isnan(argY))\n";
+ ss << " continue;\n";
+ ss << " fSumX += argX;\n";
+ ss << " fSumY += argY;\n";
+ ss << " fCount += 1.0;\n";
+ ss << " }\n";
+
+ ss << " if (fCount < 3.0)\n";
+ ss << " return DBL_MAX;\n";
+ ss << " else\n";
+ ss << " {\n";
+ ss << " fMeanX = fSumX * pow(fCount,-1.0);\n";
+ ss << " fMeanY = fSumY * pow(fCount,-1.0);\n";
+
+ ss << " for (int i = ";
+ if ((!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ &&(!pDVR1->IsStartFixed() && pDVR1->IsEndFixed()))
+ {
+ ss << "gid0; i < " << arrayLength;
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if ((pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ &&(pDVR1->IsStartFixed() && !pDVR1->IsEndFixed()))
+ {
+ ss << "0; i < " << arrayLength ;
+ ss << " && i < gid0+" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if ((!pDVR->IsStartFixed() && !pDVR->IsEndFixed())&&
+ (!pDVR1->IsStartFixed() && !pDVR1->IsEndFixed()))
+ {
+ ss << "0; i + gid0 < " << arrayLength;
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < " << arrayLength << "; i++)\n";
+ ss << " {\n";
+ }
+
+ ss << " argX = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " argY = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (isnan(argX)||isnan(argY))\n";
+ ss << " continue;\n";
+ ss << " fSumDeltaXDeltaY +=(argX-fMeanX)*(argY-fMeanY);\n";
+ ss << " fSumSqrDeltaX += (argX-fMeanX)*(argX-fMeanX);\n";
+ ss << " fSumSqrDeltaY += (argY-fMeanY)*(argY-fMeanY);\n";
+ ss << " }\n";
+ ss << " if(fSumSqrDeltaX == 0.0)\n";
+ ss << " return DBL_MAX;\n";
+ ss << " else\n";
+ ss << " {\n";
+ ss << " return sqrt((fSumSqrDeltaY - fSumDeltaXDeltaY * \n";
+ ss << " fSumDeltaXDeltaY*pow(fSumSqrDeltaX,-1.0))\n";
+ ss << " *pow(fCount - 2.0,-1.0));\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << "}\n";
+ }
+ else
+ {
+ ss << " return DBL_MAX;\n";
+ ss << "}\n";
+ }
+}
+void OpFisher::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &
+vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss <<" int gid0=get_global_id(0);\n";
+ ss <<" double arg0;\n";
+ if(vSubArguments.size() != 1)
+ {
+ ss << " return DBL_MAX;\n";
+ return ;
+ }
+ FormulaToken *pCur = vSubArguments[0]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ ss << " return DBL_MAX;\n";
+ return ;
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " arg0 = " << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss<< " if(isnan(arg0)||(gid0>=";
+ ss<<pSVR->GetArrayLength();
+ ss<<"))\n";
+ ss<<" arg0 = 0;\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << " arg0 = " << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(isnan(arg0))\n";
+ ss << " return DBL_MAX;\n";
+ }
+ ss << " if (fabs(arg0) >= 1.0)\n";
+ ss << " return DBL_MAX;\n";
+ ss << " double tmp=0.5*log((1+arg0)*pow((1-arg0),-1));\n";
+ ss << " return tmp;\n";
+ ss << "}\n";
+}
+
+void OpFisherInv::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &vSubArguments)
+{
+ FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR = static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur);
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n\t";
+ ss <<"int gid0=get_global_id(0);\n\t";
+ ss << "double arg0 = " << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n\t";
+ ss<< "if(isnan(arg0)||(gid0>=";
+ ss<<tmpCurDVR->GetArrayLength();
+ ss<<"))\n\t\t";
+ ss<<"arg0 = 0;\n\t";
+ ss << "double tmp=tanh(arg0);\n\t";
+ ss << "return tmp;\n";
+ ss << "}\n";
+}
+
+void OpGamma::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n\t";
+ ss <<"int gid0=get_global_id(0);\n\t";
+ ss << "double arg0 = " << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n\t";
+ ss << "double tmp=tgamma(arg0);\n\t";
+ ss << "return tmp;\n";
+ ss << "}\n";
+}
+
+void OpCorrel::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &vSubArguments)
+{
+ if( vSubArguments.size() !=2 ||vSubArguments[0]->GetFormulaToken()
+ ->GetType() != formula::svDoubleVectorRef||vSubArguments[1]
+ ->GetFormulaToken()->GetType() != formula::svDoubleVectorRef )
+ ///only support DoubleVector in OpCorrelfor GPU calculating.
+ throw Unhandled(__FILE__, __LINE__);
+ const formula::DoubleVectorRefToken* pCurDVRX =
+ static_cast<const formula::DoubleVectorRefToken *>(
+ vSubArguments[0]->GetFormulaToken());
+ const formula::DoubleVectorRefToken* pCurDVRY =
+ static_cast<const formula::DoubleVectorRefToken *>(
+ vSubArguments[1]->GetFormulaToken());
+ if( pCurDVRX->GetRefRowSize() != pCurDVRY->GetRefRowSize() )
+ throw Unhandled(__FILE__, __LINE__);
+
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n\t";
+ ss << "double vSum = 0.0;\n\t";
+ ss << "double vXSum = 0.0;\n\t";
+ ss << "double vYSum = 0.0;\n\t";
+ ss << "double vXMean = 0.0;\n\t";
+ ss << "double vYMean = 0.0;\n\t";
+
+ ss << "int gid0 = get_global_id(0);\n\t";
+ ss << "double arg0 = 0.0;\n\t";
+ ss << "double arg1 = 0.0;\n\t";
+ ss << "int cnt = 0;\n\t";
+
+ size_t nCurWindowSizeX = pCurDVRY->GetRefRowSize();
+
+ ss << "for (int i = ";
+ if (!pCurDVRX->IsStartFixed() && pCurDVRX->IsEndFixed()) {
+ ss << "gid0; i < " << nCurWindowSizeX << "; i++) {\n\t\t";
+ ss << "arg0 = " << vSubArguments[0]
+ ->GenSlidingWindowDeclRef(true) << ";\n\t\t";
+ ss << "arg1 = " << vSubArguments[1]
+ ->GenSlidingWindowDeclRef(true) << ";\n\t\t";
+ ss << "if(isnan(arg0) || isnan(arg1) || (i >= ";
+ ss << pCurDVRX->GetArrayLength() << ") || (i >=";
+ ss << pCurDVRY->GetArrayLength() << ")) {\n\t\t\t";
+ ss << "arg0 = 0.0;\n\t\t\t";
+ ss << "arg1 = 0.0;\n\t\t\t";
+ ss << "--cnt;\n\t\t";
+ ss << "}\n\t\t";
+ ss << "++cnt;\n\t\t";
+ ss << "vXSum += arg0;\n\t\t";
+ ss << "vYSum += arg1;\n\t";
+ ss << "}\n\t";
+ } else if (pCurDVRX->IsStartFixed() && !pCurDVRX->IsEndFixed()) {
+ ss << "0; i < gid0 + " << nCurWindowSizeX << "; i++) {\n\t\t";
+ ss << "arg0 = " << vSubArguments[0]
+ ->GenSlidingWindowDeclRef(true) << ";\n\t\t";
+ ss << "arg1 = " << vSubArguments[1]
+ ->GenSlidingWindowDeclRef(true) << ";\n\t\t";
+ ss << "if(isnan(arg0) || isnan(arg1) || (i >= ";
+ ss << pCurDVRX->GetArrayLength() << ") || (i >=";
+ ss << pCurDVRY->GetArrayLength() << ")) {\n\t\t\t";
+ ss << "arg0 = 0.0;\n\t\t\t";
+ ss << "arg1 = 0.0;\n\t\t\t";
+ ss << "--cnt;\n\t\t";
+ ss << "}\n\t\t";
+ ss << "++cnt;\n\t\t";
+ ss << "vXSum += arg0;\n\t\t";
+ ss << "vYSum += arg1;\n\t";
+ ss << "}\n\t";
+ }
+ else if (pCurDVRX->IsStartFixed() && pCurDVRX->IsEndFixed()) {
+ ss << "0; i < " << nCurWindowSizeX << "; i++) {\n\t\t";
+ ss << "arg0 = " << vSubArguments[0]
+ ->GenSlidingWindowDeclRef(true) << ";\n\t\t";
+ ss << "arg1 = " << vSubArguments[1]
+ ->GenSlidingWindowDeclRef(true) << ";\n\t\t";
+ ss << "if(isnan(arg0) || isnan(arg1) || (i >= ";
+ ss << pCurDVRX->GetArrayLength() << ") || (i >=";
+ ss << pCurDVRY->GetArrayLength() << ")) {\n\t\t\t";
+ ss << "arg0 = 0.0;\n\t\t\t";
+ ss << "arg1 = 0.0;\n\t\t\t";
+ ss << "--cnt;\n\t\t";
+ ss << "}\n\t\t";
+ ss << "++cnt;\n\t\t";
+ ss << "vXSum += arg0;\n\t\t";
+ ss << "vYSum += arg1;\n\t";
+ ss << "}\n\t";
+ } else {
+ ss << "0; i < " << nCurWindowSizeX << "; i++) {\n\t\t";
+ ss << "arg0 = " << vSubArguments[0]
+ ->GenSlidingWindowDeclRef(true) << ";\n\t\t";
+ ss << "arg1 = " << vSubArguments[1]
+ ->GenSlidingWindowDeclRef(true) << ";\n\t\t";
+ ss << "if(isnan(arg0) || isnan(arg1) || (i + gid0 >= ";
+ ss << pCurDVRX->GetArrayLength() << ") || (i + gid0 >=";
+ ss << pCurDVRY->GetArrayLength() << ")) {\n\t\t\t";
+ ss << "arg0 = 0.0;\n\t\t\t";
+ ss << "arg1 = 0.0;\n\t\t\t";
+ ss << "--cnt;\n\t\t";
+ ss << "}\n\t\t";
+ ss << "++cnt;\n\t\t";
+ ss << "vXSum += arg0;\n\t\t";
+ ss << "vYSum += arg1;\n\t";
+ ss << "}\n\t";
+ }
+
+ ss << "if(cnt < 1) {\n\t\t";
+ ss << "return DBL_MIN;\n\t";
+ ss << "}\n\t";
+ ss << "else {\n\t\t";
+ ss << "vXMean = vXSum/cnt;\n\t\t";
+ ss << "vYMean = vYSum/cnt;\n\t\t";
+ ss << "vXSum = 0.0;\n\t\t";
+ ss << "vYSum = 0.0;\n\t\t";
+
+ ss << "for (int i = ";
+ if (!pCurDVRX->IsStartFixed() && pCurDVRX->IsEndFixed()) {
+ ss << "gid0; i < " << nCurWindowSizeX << "; i++) {\n\t\t\t";
+ ss << "arg0 = " << vSubArguments[0]
+ ->GenSlidingWindowDeclRef(true) << ";\n\t\t\t";
+ ss << "arg1 = " << vSubArguments[1]
+ ->GenSlidingWindowDeclRef(true) << ";\n\t\t\t";
+ ss << "if(isnan(arg0) || isnan(arg1) || (i >= ";
+ ss << pCurDVRX->GetArrayLength() << ") || (i >=";
+ ss << pCurDVRY->GetArrayLength() << ")) {\n\t\t\t\t";
+ ss << "arg0 = vXMean;\n\t\t\t\t";
+ ss << "arg1 = vYMean;\n\t\t\t";
+ ss << "}\n\t\t\t";
+ ss << "vXSum += pow(arg0 - vXMean, 2);\n\t\t\t";
+ ss << "vYSum += pow(arg1 - vYMean, 2);\n\t\t\t";
+ ss << "vSum += (arg0 - vXMean)*(arg1 - vYMean);\n\t\t";
+ ss << "}\n\t\t";
+ } else if (pCurDVRX->IsStartFixed() && !pCurDVRX->IsEndFixed()) {
+ ss << "0; i < gid0 + " << nCurWindowSizeX << "; i++) {\n\t\t\t";
+ ss << "arg0 = " << vSubArguments[0]
+ ->GenSlidingWindowDeclRef(true) << ";\n\t\t\t";
+ ss << "arg1 = " << vSubArguments[1]
+ ->GenSlidingWindowDeclRef(true) << ";\n\t\t\t";
+ ss << "if(isnan(arg0) || isnan(arg1) || (i >= ";
+ ss << pCurDVRX->GetArrayLength() << ") || (i >=";
+ ss << pCurDVRY->GetArrayLength() << ")) {\n\t\t\t\t";
+ ss << "arg0 = vXMean;\n\t\t\t\t";
+ ss << "arg1 = vYMean;\n\t\t\t";
+ ss << "}\n\t\t\t";
+ ss << "vXSum += pow(arg0 - vXMean, 2);\n\t\t\t";
+ ss << "vYSum += pow(arg1 - vYMean, 2);\n\t\t\t";
+ ss << "vSum += (arg0 - vXMean)*(arg1 - vYMean);\n\t\t";
+ ss << "}\n\t\t";
+ } else if (pCurDVRX->IsStartFixed() && pCurDVRX->IsEndFixed()) {
+ ss << "0; i < " << nCurWindowSizeX << "; i++) {\n\t\t\t";
+ ss << "arg0 = " << vSubArguments[0]
+ ->GenSlidingWindowDeclRef(true) << ";\n\t\t\t";
+ ss << "arg1 = " << vSubArguments[1]
+ ->GenSlidingWindowDeclRef(true) << ";\n\t\t\t";
+ ss << "if(isnan(arg0) || isnan(arg1) || (i >= ";
+ ss << pCurDVRX->GetArrayLength() << ") || (i >=";
+ ss << pCurDVRY->GetArrayLength() << ")) {\n\t\t\t\t";
+ ss << "arg0 = vXMean;\n\t\t\t\t";
+ ss << "arg1 = vYMean;\n\t\t\t";
+ ss << "}\n\t\t\t";
+ ss << "vXSum += pow(arg0 - vXMean, 2);\n\t\t\t";
+ ss << "vYSum += pow(arg1 - vYMean, 2);\n\t\t\t";
+ ss << "vSum += (arg0 - vXMean)*(arg1 - vYMean);\n\t\t";
+ ss << "}\n\t\t";
+ } else {
+ ss << "0; i < " << nCurWindowSizeX << "; i++) {\n\t\t\t";
+ ss << "arg0 = " << vSubArguments[0]
+ ->GenSlidingWindowDeclRef(true) << ";\n\t\t\t";
+ ss << "arg1 = " << vSubArguments[1]
+ ->GenSlidingWindowDeclRef(true) << ";\n\t\t\t";
+ ss << "if(isnan(arg0) || isnan(arg1) || (i + gid0 >= ";
+ ss << pCurDVRX->GetArrayLength() << ") || (i + gid0 >=";
+ ss << pCurDVRY->GetArrayLength() << ")) {\n\t\t\t\t";
+ ss << "arg0 = vXMean;\n\t\t\t\t";
+ ss << "arg1 = vYMean;\n\t\t\t";
+ ss << "}\n\t\t\t";
+ ss << "vXSum += ((arg0 - vXMean)*(arg0 - vXMean));\n\t\t\t";
+ ss << "vYSum += ((arg1 - vYMean)*(arg1 - vYMean));\n\t\t\t";
+ ss << "vSum += (arg0 - vXMean)*(arg1 - vYMean);\n\t\t";
+ ss << "}\n\t\t";
+ }
+
+ ss << "if(vXSum == 0.0 || vYSum == 0.0) {\n\t\t\t";
+ ss << "return NAN;\n\t\t";
+ ss << "}\n\t\t";
+ ss << "else {\n\t\t\t";
+ ss << "return vSum/pow(vXSum*vYSum, 0.5);\n\t\t";
+ ss << "}\n\t";
+ ss << "}\n";
+ ss << "}";
+}
+
+void OpNegbinomdist::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n\t";
+ ss << "double f,s,p,tmp0,tmp1,tmp2;\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss <<"\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << "if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << "{\n";
+ }
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " tmp"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " tmp"<<i<<"=\n";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n}\n";
+ }
+ else
+ {
+ ss << "tmp"<<i<<"="<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ }
+ }
+ ss << " p = tmp2;\n";
+ ss << " s = tmp1;\n";
+ ss << " f = tmp0;\n";
+ ss << " double q = 1.0 - p;\n\t";
+ ss << " double fFactor = pow(p,s);\n\t";
+ ss << " for(int i=0; i<f; i++)\n\t";
+ ss << " {\n\t";
+ ss << " fFactor *= ((double)i+s)*pow(((double)i+1.0),-1.0)/pow(q,-1);\n";
+ ss << " }\n\t";
+ ss << " double temp=fFactor;\n\t";
+ ss << " return temp;\n";
+ ss << "}\n";
+}
+
+void OpPearson::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &vSubArguments)
+{
+ if( vSubArguments.size() !=2 ||vSubArguments[0]->GetFormulaToken()
+ ->GetType() != formula::svDoubleVectorRef||vSubArguments[1]
+ ->GetFormulaToken()->GetType() != formula::svDoubleVectorRef )
+ ///only support DoubleVector in OpPearson for GPU calculating.
+ throw Unhandled(__FILE__, __LINE__);
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(
+ vSubArguments[0]->GetFormulaToken());
+ const formula::DoubleVectorRefToken* pCurDVRY =
+ static_cast<const formula::DoubleVectorRefToken *>(
+ vSubArguments[1]->GetFormulaToken());
+ if( pDVR->GetRefRowSize() != pCurDVRY->GetRefRowSize() )
+ throw Unhandled(__FILE__, __LINE__);
+
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double fCount = 0.0;\n";
+ ss << " double fSumX = 0.0;\n";
+ ss << " double fSumY = 0.0;\n";
+ ss << " double fSumDeltaXDeltaY = 0.0;\n";
+ ss << " double fInx;\n";
+ ss << " double fIny;\n";
+ ss << "for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed()) {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++){\n";
+ } else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed()) {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+"<< nCurWindowSize << "; i++){\n";
+ } else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed()){
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < "<< nCurWindowSize << "; i++){\n";
+ }
+ else {
+ ss << "0; i < "<< nCurWindowSize << "; i++){\n";
+ }
+ ss << " fInx = "<<vSubArguments[0]->GenSlidingWindowDeclRef(true);
+ ss << ";\n";
+ ss << " fIny = "<<vSubArguments[1]->GenSlidingWindowDeclRef(true);
+ ss << " ;\n";
+ ss << " if(!isnan(fInx)&&!isnan(fIny)){\n";
+ ss << " fSumX += fInx;\n";
+ ss << " fSumY += fIny;\n";
+ ss << " fCount = fCount + 1;\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << " if(fCount < 1)\n";
+ ss << " return CreateDoubleError(NoValue);\n";
+ ss << " double fMeanX = fSumX / fCount;\n";
+ ss << " double fMeanY = fSumY / fCount;\n";
+ ss << " fSumX = 0.0;\n";
+ ss << " fSumY = 0.0;\n";
+ ss << "for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed()) {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++){\n";
+ } else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed()) {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+"<< nCurWindowSize << "; i++){\n";
+ } else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed()){
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < "<< nCurWindowSize << "; i++){\n";
+ }
+ else {
+ ss << "0; i < "<< nCurWindowSize << "; i++){\n";
+ }
+ ss << " fInx = "<<vSubArguments[0]->GenSlidingWindowDeclRef(true);
+ ss << " ;\n";
+ ss << " fIny = "<<vSubArguments[1]->GenSlidingWindowDeclRef(true);
+ ss << " ;\n";
+ ss << " if(!isnan(fInx)&&!isnan(fIny)){\n";
+ ss << " fSumDeltaXDeltaY += (fInx - fMeanX) * (fIny - fMeanY);\n";
+ ss << " fSumX += (fInx - fMeanX) * (fInx - fMeanX);\n";
+ ss << " fSumY += (fIny - fMeanY) * (fIny - fMeanY);\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << " if (fSumX == 0 || fSumY == 0)\n";
+ ss << " return CreateDoubleError(DivisionByZero);\n";
+ ss << " double tmp = ( fSumDeltaXDeltaY / sqrt( fSumX * fSumY));\n";
+ ss << " return tmp;\n";
+ ss << "}\n";
+}
+
+void OpGammaLn::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &vSubArguments)
+{
+ FormulaToken *tmpCur = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR= static_cast<const
+formula::SingleVectorRefToken *>(tmpCur);
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n\t";
+ ss <<"int gid0=get_global_id(0);\n\t";
+ ss << "double arg0 = " << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n\t";
+ ss<< "if(isnan(arg0)||(gid0>=";
+ ss<<tmpCurDVR->GetArrayLength();
+ ss<<"))\n\t\t";
+ ss<<"arg0 = 0;\n\t";
+ ss << "double tmp=lgamma(arg0);\n\t";
+ ss << "return tmp;\n";
+ ss << "}\n";
+}
+void OpGauss::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(taylorDecl);decls.insert(phiDecl);
+ decls.insert(gaussDecl);
+ funs.insert(taylor);funs.insert(phi);
+ funs.insert(gauss);
+}
+
+void OpGauss::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &
+vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss <<" int gid0=get_global_id(0);\n";
+ ss <<" double arg0;\n";
+ if(vSubArguments.size() != 1)
+ {
+ ss << " return DBL_MAX;\n";
+ return ;
+ }
+ FormulaToken *pCur = vSubArguments[0]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ ss << " return DBL_MAX;\n";
+ return ;
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " arg0 = " << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss<< " if(isnan(arg0)||(gid0>=";
+ ss<<pSVR->GetArrayLength();
+ ss<<"))\n";
+ ss<<" arg0 = 0;\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << " arg0 = " << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(isnan(arg0))\n";
+ ss << " return DBL_MAX;\n";
+ }
+ ss << " double tmp=gauss(arg0);\n";
+ ss << " return tmp;\n";
+ ss << "}\n";
+}
+
+void OpGeoMean::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "__kernel void ";
+ ss << "GeoMean_reduction( ";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ", __global double *result)\n";
+ ss << "{\n";
+ ss << " double tmp =0;\n";
+ ss << " int count = 0;\n";
+ ss << " int i ;\n";
+ GenTmpVariables(ss,vSubArguments);
+ ss << " double current_sum = 0.0;\n";
+ ss << " int windowSize;\n";
+ ss << " int arrayLength;\n";
+ ss << " int current_count = 0;\n";
+ ss << " int writePos = get_group_id(1);\n";
+ ss << " int lidx = get_local_id(0);\n";
+ ss << " __local double shm_buf[256];\n";
+ ss << " __local int count_buf[256];\n";
+ ss << " int loop;\n";
+ ss << " int offset;\n";
+ ss << " barrier(CLK_LOCAL_MEM_FENCE);\n";
+
+ for(const DynamicKernelArgumentRef & rArg : vSubArguments)
+ {
+ assert(rArg->GetFormulaToken());
+
+ if(rArg->GetFormulaToken()->GetType() ==
+ formula::svDoubleVectorRef)
+ {
+ FormulaToken *tmpCur = rArg->GetFormulaToken();
+ const formula::DoubleVectorRefToken*pCurDVR= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur);
+ size_t nCurWindowSize = pCurDVR->GetArrayLength() <
+ pCurDVR->GetRefRowSize() ? pCurDVR->GetArrayLength():
+ pCurDVR->GetRefRowSize() ;
+
+ if (pCurDVR->IsStartFixed() && pCurDVR->IsEndFixed())
+ ss << " offset = 0;\n";
+ else if (!pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed())
+ ss << " offset = get_group_id(1);\n";
+ else
+ throw Unhandled(__FILE__, __LINE__);
+ ss << " windowSize = ";
+ ss << nCurWindowSize;
+ ss << ";\n";
+ ss << " arrayLength = ";
+ ss << pCurDVR->GetArrayLength();
+ ss << ";\n";
+ ss << " loop = arrayLength/512 + 1;\n";
+ ss << " for (int l=0; l<loop; l++){\n";
+ ss << " tmp = 0.0;\n";
+ ss << " count = 0;\n";
+ ss << " int loopOffset = l*512;\n";
+ ss << " int p1 = loopOffset + lidx + offset, p2 = p1 + 256;\n";
+ ss << " if (p2 < min(offset + windowSize, arrayLength)) {\n";
+ ss << " tmp0 = 0.0;\n";
+ std::string p1 = "p1";
+ std::string p2 = "p2";
+
+ ss << " tmp0 =";
+ rArg->GenDeclRef(ss);
+ ss << "["<<p1.c_str()<<"];\n";
+ ss << " if(!isnan(tmp0))\n";
+ ss << " {\n";
+ ss << " tmp += log(tmp0);\n";
+ ss << " count++;\n";
+ ss << " }\n";
+
+ ss << " tmp0 =";
+ rArg->GenDeclRef(ss);
+ ss << "["<<p2.c_str()<<"];\n";
+ ss << " if(!isnan(tmp0))\n";
+ ss << " {\n";
+ ss << " tmp += log(tmp0);\n";
+ ss << " count++;\n";
+ ss << " }\n";
+
+ ss << " }\n";
+ ss << " else if (p1 < min(arrayLength, offset + windowSize)) {\n";
+
+ ss << " tmp0 =";
+ rArg->GenDeclRef(ss);
+ ss << "["<<p1.c_str()<<"];\n";
+ ss << " if(!isnan(tmp0))\n";
+ ss << " {\n";
+ ss << " tmp += log(tmp0);\n";
+ ss << " count++;\n";
+ ss << " }\n";
+
+ ss << " }\n";
+ ss << " shm_buf[lidx] = tmp;\n";
+ ss << " count_buf[lidx] = count;\n";
+ ss << " barrier(CLK_LOCAL_MEM_FENCE);\n";
+
+ ss << " for (int i = 128; i >0; i/=2) {\n";
+ ss << " if (lidx < i)\n";
+ ss << " {\n";
+ ss << " shm_buf[lidx] += shm_buf[lidx + i];\n";
+ ss << " count_buf[lidx] += count_buf[lidx + i];\n";
+ ss << " }\n";
+ ss << " barrier(CLK_LOCAL_MEM_FENCE);\n";
+ ss << " }\n";
+ ss << " if (lidx == 0)\n";
+ ss << " {\n";
+ ss << " current_sum += shm_buf[0];\n";
+ ss << " current_count += count_buf[0];\n";
+ ss << " }\n";
+ // ss << "if(writePos == 14 && lidx ==0)\n";
+ //ss <<"printf(\"\\n********************sum is %f, count is%d\",current_sum,current_count);\n";
+ ss << " barrier(CLK_LOCAL_MEM_FENCE);\n";
+ ss << " }\n";
+ }else
+ {
+ ss << " if (lidx == 0)\n";
+ ss << " {\n";
+ ss << " tmp0 =";
+ if(rArg->GetFormulaToken()->GetType() == formula::svSingleVectorRef)
+ {
+ rArg->GenDeclRef(ss);
+ ss << "[writePos];\n";
+ }
+ else
+ {
+ rArg->GenDeclRef(ss);
+ ss <<";\n";
+ //ss <<"printf(\"\\n********************tmp0 is %f\",tmp0);\n";
+ }
+ ss << " if(!isnan(tmp0))\n";
+ ss << " {\n";
+ ss << " current_sum += log(tmp0);\n";
+ ss << " current_count++;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ }
+
+ ss << " if (lidx == 0)\n";
+ ss << " result[writePos] = exp(current_sum/current_count);\n";
+ ss << "}\n";
+
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n {\n";
+ ss <<" int gid0=get_global_id(0);\n";
+ ss << " double tmp =0;\n";
+ ss << " tmp =";
+ vSubArguments[0]->GenDeclRef(ss);
+ ss << "[gid0];\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+
+void OpHarMean::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &
+vSubArguments)
+{
+
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"( ";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss <<"{\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double nVal=0.0;\n";
+ ss << " double tmp = 0;\n";
+ ss << " int length;\n";
+ ss << " int totallength=0;\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " length="<<nCurWindowSize;
+ ss << ";\n";
+ ss << " for (int i = ";
+ ss << "0; i < "<< nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ ss << " double arg"<<i<<" = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(isnan(arg"<<i<<")||((gid0+i)>=";
+ ss << pDVR->GetArrayLength();
+ ss << "))\n";
+ ss << " {\n";
+ ss << " length--;\n";
+ ss << " continue;\n";
+ ss << " }\n";
+ ss << " nVal += (1.0 *pow(";
+ ss << " arg"<<i<<",-1));\n";
+ ss << " }\n";
+ ss << " totallength +=length;\n";
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ ss << " tmp = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(!isnan(tmp))\n";
+ ss << " {\n";
+ ss << " nVal += (1.0 * pow( tmp,-1));\n";
+ ss << " totallength +=1;\n";
+ ss << " }\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << " tmp = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " nVal += (1.0 *pow( tmp,-1));\n";
+ ss << " totallength +=1;\n";
+ }
+ else
+ {
+ ss << " return DBL_MIN;\n";
+ }
+ }
+ ss << " tmp = totallength*pow(nVal,-1);\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+
+void OpConfidence::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(gaussinvDecl);
+ funs.insert(gaussinv);
+}
+
+void OpConfidence::GenSlidingWindowFunction(std::stringstream& ss,
+ const std::string &sSymName, SubArguments& vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " double tmp = " << GetBottom() <<";\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double alpha = " << GetBottom() <<";\n";
+ ss << " double sigma = " << GetBottom() <<";\n";
+ ss << " double size = " << GetBottom() <<";\n";
+ ss << " double tmp0,tmp1,tmp2;\n";
+ ss <<"\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << "if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << "{\n";
+ }
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " tmp"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " tmp"<<i<<"=\n";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n}\n";
+ }
+ else
+ {
+ ss << "tmp"<<i<<"="<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ }
+ }
+ ss << " alpha = tmp0;\n";
+ ss << " sigma = tmp1;\n";
+ ss << " size = tmp2;\n";
+ ss << " double rn = floor(size);\n";
+ ss << " if(sigma <= 0.0 || alpha <= 0.0 || alpha >= 1.0";
+ ss << "|| rn < 1.0)\n";
+ ss << " tmp = -DBL_MAX;\n";
+ ss << " else\n";
+ ss << " tmp = gaussinv(1.0 - alpha * pow(2.0,-1.0)) * sigma ";
+ ss << "* pow(sqrt( rn ),-1);\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+
+void OpCritBinom::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(MinDecl);
+ funs.insert("");
+}
+
+void OpCritBinom::GenSlidingWindowFunction(std::stringstream& ss,
+ const std::string &sSymName, SubArguments& vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " double tmp = " << GetBottom() <<";\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double n = " << GetBottom() <<";\n";
+ ss << " double p = " << GetBottom() <<";\n";
+ ss << " double alpha = " << GetBottom() <<";\n";
+ ss << " double tmp0 = 0.0,tmp1 = 0.0,tmp2 = 0.0;\n";
+ ss <<"\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << "if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << "{\n";
+ }
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " tmp"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " tmp"<<i<<"=\n";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n}\n";
+ }
+ else
+ {
+ ss << "tmp"<<i<<"="<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ }
+ }
+ ss << " n = tmp0;\n";
+ ss << " p = tmp1;\n";
+ ss << " alpha = tmp2;\n";
+ ss << " double rn = floor(n);\n";
+ ss << " if (rn < 0.0 || alpha <= 0.0 || alpha >= 1.0 || p < 0.0";
+ ss << " || p > 1.0)\n";
+ ss << " tmp = -DBL_MIN;\n";
+ ss << " else\n";
+ ss << " {\n";
+ ss << " double rq = (0.5 - p) + 0.5;\n";
+ ss << " double fFactor = pow(rq, rn);\n";
+ ss << " if (fFactor <= Min)\n";
+ ss << " {\n";
+ ss << " fFactor = pow(p, rn);\n";
+ ss << " if (fFactor <= Min)\n";
+ ss << " tmp = -DBL_MAX;\n";
+ ss << " else\n";
+ ss << " {\n";
+ ss << " double fSum = 1.0 - fFactor;\n";
+ ss << " uint max =(uint)(rn), i;\n";
+ ss << " for (i = 0; i < max && fSum >= alpha; i++)\n";
+ ss << " {\n";
+ ss << " fFactor *= (rn - i) * pow((double)(i + 1),-1.0) *";
+ ss << " rq * pow(p, -1.0);\n";
+ ss << " fSum -= fFactor;\n";
+ ss << " }\n";
+ ss << " tmp = (rn - i);\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << " else\n";
+ ss << " {\n";
+ ss << " double fSum = fFactor;\n";
+ ss << " uint max = (uint)(rn), i;\n";
+ ss << " for (i = 0; i < max && fSum < alpha; i++)\n";
+ ss << " {\n";
+ ss << " fFactor *= (rn - i) * pow((double)(i + 1), -1.0) *";
+ ss << " p * pow(rq, -1.0);\n";
+ ss << " fSum += fFactor;\n";
+ ss << " }\n";
+ ss << " tmp = (i);\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+
+void OpRsq::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &vSubArguments)
+{
+ if( vSubArguments.size() !=2 ||vSubArguments[0]->GetFormulaToken()
+ ->GetType() != formula::svDoubleVectorRef||vSubArguments[1]
+ ->GetFormulaToken()->GetType() != formula::svDoubleVectorRef )
+ ///only support DoubleVector in OpRsq for GPU calculating.
+ throw Unhandled(__FILE__, __LINE__);
+ const formula::DoubleVectorRefToken* pCurDVR1 =
+ static_cast<const formula::DoubleVectorRefToken *>(
+ vSubArguments[0]->GetFormulaToken());
+ const formula::DoubleVectorRefToken* pCurDVR2 =
+ static_cast<const formula::DoubleVectorRefToken *>(
+ vSubArguments[1]->GetFormulaToken());
+ if( pCurDVR1->GetRefRowSize() != pCurDVR2->GetRefRowSize() )
+ throw Unhandled(__FILE__, __LINE__);
+
+ size_t nCurWindowSize = pCurDVR1->GetRefRowSize();
+
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double fCount = 0.0;\n";
+ ss << " double fSumX = 0.0;\n";
+ ss << " double fSumY = 0.0;\n";
+ ss << " double fSumDeltaXDeltaY = 0.0;\n";
+ ss << " double fInx;\n";
+ ss << " double fIny;\n";
+ ss << " double tmp0,tmp1;\n";
+
+ ss <<"\n";
+
+ ss << " for(int i=0; i<"<<nCurWindowSize<<"; i++)\n";
+ ss << " {\n";
+ ss << " if(isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef(true);
+ ss << "))\n";
+ ss << " fInx = 0;\n";
+ ss << " else\n";
+ ss << " fInx = "<<vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(isnan(";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef(true);
+ ss << "))\n";
+ ss << " fIny = 0;\n";
+ ss << " else\n";
+ ss << " fIny = "<<vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss << " ;\n";
+ ss << " fSumX += fInx;\n";
+ ss << " fSumY += fIny;\n";
+ ss << " fCount = fCount + 1;\n";
+ ss << " }\n";
+ ss << " double fMeanX = fSumX / fCount;\n";
+ ss << " double fMeanY = fSumY / fCount;\n";
+ ss << " fSumX = 0.0;\n";
+ ss << " fSumY = 0.0;\n";
+ ss << " for(int i=0; i<"<<nCurWindowSize<<"; i++)\n";
+ ss << " {\n";
+ ss << " if(isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef(true);
+ ss << "))\n";
+ ss << " fInx = 0;\n";
+ ss << " else\n";
+ ss << " fInx = "<<vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(isnan(";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " fIny = 0;\n";
+ ss << " else\n";
+ ss << " fIny = "<<vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss << " ;\n";
+ ss << " fSumDeltaXDeltaY += (fInx - fMeanX) * (fIny - fMeanY);\n";
+ ss << " fSumX += pow(fInx - fMeanX,2);\n";
+ ss << " fSumY += pow(fIny - fMeanY,2);\n";
+ ss << " }\n";
+ ss << " double tmp = pow( fSumDeltaXDeltaY,2) / (fSumX * fSumY);\n";
+ ss << " return tmp ;\n";
+ ss << "}\n";
+}
+
+void OpChiInv::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(fMachEpsDecl);
+ funs.insert("");
+ decls.insert(fBigInvDecl);
+ funs.insert("");
+ decls.insert(fHalfMachEpsDecl);
+ funs.insert("");
+ decls.insert(lcl_IterateInverseChiInvDecl);
+ funs.insert(lcl_IterateInverseChiInv);
+ decls.insert(GetChiDistDecl);
+ funs.insert(GetChiDist);
+ decls.insert(lcl_HasChangeOfSignDecl);
+ funs.insert(lcl_HasChangeOfSign);
+ decls.insert(GetUpRegIGammaDecl);
+ funs.insert(GetUpRegIGamma);
+ decls.insert(GetGammaContFractionDecl);
+ funs.insert(GetGammaContFraction);
+ decls.insert(GetGammaSeriesDecl);
+ funs.insert(GetGammaSeries);
+}
+void OpChiInv::GenSlidingWindowFunction(
+ std::stringstream &ss,const std::string &sSymName,
+ SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " double tmp0,tmp1,tmp;\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss <<"\n ";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << "for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed()) {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++){\n";
+ } else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed()) {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+"<< nCurWindowSize << "; i++){\n";
+ } else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed()){
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < "<< nCurWindowSize << "; i++){\n";
+ } else {
+ ss << "0; i < "<< nCurWindowSize << "; i++){\n";
+ }
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << "if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << "{\n";
+ }
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << "if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " tmp"<<i<<"= 0;\n";
+ ss << "else\n";
+ ss <<"tmp"<<i<<"="<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n}\n";
+ }
+ else
+ {
+ ss << "tmp"<<i<<"="<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ }
+ ss << " tmp1 = floor(tmp1);";
+ ss << " if (tmp1 < 1.0 || tmp0 <= 0.0 || tmp0 > 1.0 )\n";
+ ss << " {\n";
+ ss << " return DBL_MIN;\n";
+ ss << " }\n";
+ ss << " bool bConvError;\n";
+ ss << " double fVal = lcl_IterateInverseChiInv";
+ ss << "(tmp0, tmp1, tmp1*0.5, tmp1, &bConvError);\n";
+ ss << " if(bConvError)\n";
+ ss << " return DBL_MIN;\n";
+ ss << " return fVal;\n";
+ ss << "}\n";
+}
+void OpNormdist::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName,
+ SubArguments &vSubArguments)
+{
+ CHECK_PARAMETER_COUNT(3,4);
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " double x,mue,sigma,c;\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double tmp0,tmp1,tmp2;\n";
+ ss << " double tmp3 = 0;\n"; // optional argument
+ ss <<"\n ";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << "if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << "{\n";
+ }
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " tmp"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " tmp"<<i<<"=\n";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n}\n";
+ }
+ else
+ {
+ ss << "tmp"<<i<<"="<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ }
+ }
+ ss << "x = tmp0;\n";
+ ss << "mue = tmp1;\n";
+ ss << "sigma = tmp2;\n";
+ ss << "c = tmp3;\n";
+ ss << "if(sigma <= 0)\n";
+ ss << " return CreateDoubleError(IllegalArgument);\n";
+ ss << "double mid,tmp;\n";
+ ss << "mid = (x - mue)/sigma;\n";
+ ss << "if(c)\n";
+ ss << " tmp = 0.5 *erfc(-mid * 0.7071067811865475);\n";
+ ss << "else \n";
+ ss <<" tmp=(0.39894228040143268*exp(-pow(mid,2)/2.0))/sigma;\n";
+ ss << "return tmp;\n";
+ ss << "}\n";
+}
+void OpNormsdist::GenSlidingWindowFunction(
+ std::stringstream &ss,const std::string &sSymName,
+ SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " double x = 0,tmp0 = 0;\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss <<"\n ";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << "if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << "{\n";
+ }
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " tmp"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " tmp"<<i<<"=\n";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n}\n";
+ }
+ else
+ {
+ ss << "tmp"<<i<<"="<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ }
+ }
+ ss << " x = tmp0;\n";
+ ss << " double tmp = 0.5 * erfc((-1)*x * 0.7071067811865475);\n";
+ ss << " return tmp;\n";
+ ss << "}\n";
+}
+
+void OpPermut::GenSlidingWindowFunction(
+ std::stringstream &ss,const std::string &sSymName,
+ SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss <<" int gid0=get_global_id(0);\n";
+ ss <<" double inA;\n";
+ ss <<" double inB;\n";
+ ss <<" double tmp0,tmp1;\n";
+ ss <<" double tmp = 1 ;\n";
+ ss <<"\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << "if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << "{\n";
+ }
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " tmp"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " tmp"<<i<<"=\n";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n}\n";
+ }
+ else
+ {
+ ss << "tmp"<<i<<"="<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ }
+ }
+ ss << " inA = tmp0;\n";
+ ss << " inB = tmp1;\n";
+ ss << " for( int i =0; i<inB; i++)\n";
+ ss << " {\n";
+ ss << " tmp *= inA ;\n";
+ ss << " inA = inA - 1.0;\n";
+ ss << " }\n";
+ ss << " return tmp;\n";
+ ss << "}\n";
+}
+void OpPermutationA::GenSlidingWindowFunction(
+ std::stringstream &ss,const std::string &sSymName,
+ SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss <<" int gid0=get_global_id(0);\n";
+ ss <<" double inA;\n";
+ ss <<" double inB;\n";
+ ss <<" double tmp = 1.0;\n";
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR0= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+ FormulaToken *tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur1);
+ ss << "int buffer_fIna_len = ";
+ ss << tmpCurDVR0->GetArrayLength();
+ ss << ";\n";
+ ss << " int buffer_fInb_len = ";
+ ss << tmpCurDVR1->GetArrayLength();
+ ss << ";\n";
+ ss << " if((gid0)>=buffer_fIna_len || isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " inA = 0;\nelse \n";
+ ss << " inA = "<<vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << "if((gid0)>=buffer_fInb_len || isnan(";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << "inB = 0;\nelse \n";
+ ss << " inB = "<<vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " for(int i=0; i<inB; i++)\n";
+ ss << " {\n";
+ ss << " tmp *= inA;\n";
+ ss << " }\n";
+ ss << " return tmp;\n";
+ ss << "}\n";
+}
+
+void OpPhi::GenSlidingWindowFunction(
+ std::stringstream &ss,const std::string &sSymName,
+ SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " double x,tmp0;\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss <<"\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << "if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << "{\n";
+ }
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " tmp"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " tmp"<<i<<"=\n";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n}\n";
+ }
+ else
+ {
+ ss << "tmp"<<i<<"="<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ }
+ }
+ ss << " x = tmp0;\n";
+ ss << " double tmp = 0.39894228040143268 * exp((-1)*pow(x,2) / 2.0);\n";
+ ss << " return tmp;\n";
+ ss << "}\n";
+}
+
+void OpNorminv::GenSlidingWindowFunction(
+ std::stringstream &ss,const std::string &sSymName,
+ SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss <<" double q,t,z;\n";
+ ss <<" double x,mue,sigma;\n";
+ ss <<" double tmp0,tmp1,tmp2;\n";
+ ss <<" int gid0=get_global_id(0);\n";
+ ss <<"\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << "if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << "{\n";
+ }
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " tmp"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " tmp"<<i<<"=\n";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n}\n";
+ }
+ else
+ {
+ ss << "tmp"<<i<<"="<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ }
+ }
+ ss <<" x = tmp0;\n";
+ ss <<" mue = tmp1;\n";
+ ss <<" sigma = tmp2;\n";
+ ss <<" q = x -0.5;\n";
+ ss <<" if(fabs(q)<=.425)\n";
+ ss <<" {\n";
+ ss <<" t=0.180625-pow(q,2);\n";
+ ss <<" z=\n"
+ "q*\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "t*2509.0809287301226727";
+ ss <<"+33430.575583588128105\n"
+ ")\n"
+ "*t+67265.770927008700853\n"
+ ")\n"
+ "*t+45921.953931549871457\n"
+ ")\n"
+ "*t+13731.693765509461125\n"
+ ")\n"
+ "*t+1971.5909503065514427\n"
+ ")\n"
+ "*t+133.14166789178437745\n"
+ ")\n"
+ "*t+3.387132872796366608\n"
+ ")\n"
+ "/\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "t*5226.495278852854561";
+ ss <<"+28729.085735721942674\n"
+ ")\n"
+ "*t+39307.89580009271061\n"
+ ")\n"
+ "*t+21213.794301586595867\n"
+ ")\n"
+ "*t+5394.1960214247511077\n"
+ ")\n"
+ "*t+687.1870074920579083\n"
+ ")\n"
+ "*t+42.313330701600911252\n"
+ ")\n"
+ "*t+1.0\n"
+ ");\n";
+ ss <<"}\nelse\n{\n";
+ ss <<" if(q>0)\nt=1-x;\n";
+ ss <<"else\nt=x;\n";
+ ss <<"t=sqrt(-log(t));\n";
+ ss <<"if(t<=5.0)\n{\n";
+ ss <<"t+=-1.6;\n";
+ ss <<"z=\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "t*7.7454501427834140764e-4";
+ ss <<"+0.0227238449892691845833\n"
+ ")\n"
+ "*t+0.24178072517745061177\n"
+ ")\n"
+ "*t+1.27045825245236838258\n"
+ ")\n"
+ "*t+3.64784832476320460504\n"
+ ")\n"
+ "*t+5.7694972214606914055\n"
+ ")\n"
+ "*t+4.6303378461565452959\n"
+ ")\n"
+ "*t+1.42343711074968357734\n"
+ ")\n"
+ "/\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "t*1.05075007164441684324e-9";
+ ss <<"+5.475938084995344946e-4\n"
+ ")\n"
+ "*t+0.0151986665636164571966\n"
+ ")\n"
+ "*t+0.14810397642748007459\n"
+ ")\n"
+ "*t+0.68976733498510000455\n"
+ ")\n"
+ "*t+1.6763848301838038494\n"
+ ")\n"
+ "*t+2.05319162663775882187\n"
+ ")\n"
+ "*t+1.0\n"
+ ");\n}\n";
+ ss <<"else\n{\n";
+ ss <<"t+=-5.0;\n";
+ ss <<"z=\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "t*2.01033439929228813265e-7";
+ ss<<"+2.71155556874348757815e-5\n"
+ ")\n"
+ "*t+0.0012426609473880784386\n"
+ ")\n"
+ "*t+0.026532189526576123093\n"
+ ")\n"
+ "*t+0.29656057182850489123\n"
+ ")\n"
+ "*t+1.7848265399172913358\n"
+ ")\n"
+ "*t+5.4637849111641143699\n"
+ ")\n"
+ "*t+6.6579046435011037772\n"
+ ")\n"
+ "/\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "t*2.04426310338993978564e-15"
+ "+1.4215117583164458887e-7\n"
+ ")\n"
+ "*t+1.8463183175100546818e-5\n"
+ ")\n"
+ "*t+7.868691311456132591e-4\n"
+ ")\n"
+ "*t+0.0148753612908506148525\n"
+ ")\n"
+ "*t+0.13692988092273580531\n"
+ ")\n"
+ "*t+0.59983220655588793769\n"
+ ")\n"
+ "*t+1.0\n"
+ ");\n";
+ ss<<"}\n";
+ ss << "z = q < 0.0 ? (-1)*z : z;\n";
+ ss<<"}\n";
+ ss<<"double tmp = z*sigma + mue;\n";
+ ss<<"return tmp;\n";
+ ss<<"}\n";
+}
+void OpNormsinv:: GenSlidingWindowFunction
+ (std::stringstream &ss,const std::string &sSymName,
+ SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " double q,t,z,x,tmp0;\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss <<"\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << "if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << "{\n";
+ }
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " tmp"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " tmp"<<i<<"=\n";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n}\n";
+ }
+ else
+ {
+ ss << "tmp"<<i<<"="<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ }
+ }
+ ss <<" x = tmp0;\n";
+ ss <<" q = x -0.5;\n";
+ ss <<" if(fabs(q)<=.425)\n";
+ ss <<" {\n";
+ ss <<" t=0.180625-pow(q,2);\n";
+ ss <<" z=\n"
+ "q*\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "t*2509.0809287301226727";
+ ss <<"+33430.575583588128105\n"
+ ")\n"
+ "*t+67265.770927008700853\n"
+ ")\n"
+ "*t+45921.953931549871457\n"
+ ")\n"
+ "*t+13731.693765509461125\n"
+ ")\n"
+ "*t+1971.5909503065514427\n"
+ ")\n"
+ "*t+133.14166789178437745\n"
+ ")\n"
+ "*t+3.387132872796366608\n"
+ ")\n"
+ "/\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "t*5226.495278852854561";
+ ss <<"+28729.085735721942674\n"
+ ")\n"
+ "*t+39307.89580009271061\n"
+ ")\n"
+ "*t+21213.794301586595867\n"
+ ")\n"
+ "*t+5394.1960214247511077\n"
+ ")\n"
+ "*t+687.1870074920579083\n"
+ ")\n"
+ "*t+42.313330701600911252\n"
+ ")\n"
+ "*t+1.0\n"
+ ");\n";
+ ss <<"}\nelse\n{\n";
+ ss <<" if(q>0)\nt=1-x;\n";
+ ss <<"else\nt=x;\n";
+ ss <<"t=sqrt(-log(t));\n";
+ ss <<"if(t<=5.0)\n{\n";
+ ss <<"t+=-1.6;\n";
+ ss <<"z=\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "t*7.7454501427834140764e-4";
+ ss <<"+0.0227238449892691845833\n"
+ ")\n"
+ "*t+0.24178072517745061177\n"
+ ")\n"
+ "*t+1.27045825245236838258\n"
+ ")\n"
+ "*t+3.64784832476320460504\n"
+ ")\n"
+ "*t+5.7694972214606914055\n"
+ ")\n"
+ "*t+4.6303378461565452959\n"
+ ")\n"
+ "*t+1.42343711074968357734\n"
+ ")\n"
+ "/\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "t*1.05075007164441684324e-9";
+ ss <<"+5.475938084995344946e-4\n"
+ ")\n"
+ "*t+0.0151986665636164571966\n"
+ ")\n"
+ "*t+0.14810397642748007459\n"
+ ")\n"
+ "*t+0.68976733498510000455\n"
+ ")\n"
+ "*t+1.6763848301838038494\n"
+ ")\n"
+ "*t+2.05319162663775882187\n"
+ ")\n"
+ "*t+1.0\n"
+ ");\n}\n";
+ ss <<"else\n{\n";
+ ss <<"t+=-5.0;\n";
+ ss <<"z=\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "t*2.01033439929228813265e-7";
+ ss <<"+2.71155556874348757815e-5\n"
+ ")\n"
+ "*t+0.0012426609473880784386\n"
+ ")\n"
+ "*t+0.026532189526576123093\n"
+ ")\n"
+ "*t+0.29656057182850489123\n"
+ ")\n"
+ "*t+1.7848265399172913358\n"
+ ")\n"
+ "*t+5.4637849111641143699\n"
+ ")\n"
+ "*t+6.6579046435011037772\n"
+ ")\n"
+ "/\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "(\n"
+ "t*2.04426310338993978564e-15"
+ "+1.4215117583164458887e-7\n"
+ ")\n"
+ "*t+1.8463183175100546818e-5\n"
+ ")\n"
+ "*t+7.868691311456132591e-4\n"
+ ")\n"
+ "*t+0.0148753612908506148525\n"
+ ")\n"
+ "*t+0.13692988092273580531\n"
+ ")\n"
+ "*t+0.59983220655588793769\n"
+ ")\n"
+ "*t+1.0\n"
+ ");\n";
+ ss <<"}\n";
+ ss << "z = q < 0.0 ? (-1)*z : z;\n";
+ ss <<"}\n";
+ ss <<"if (isnan(z))\n";
+ ss <<" return CreateDoubleError(NoValue);\n";
+ ss <<"return z;\n";
+ ss <<"}\n";
+}
+void OpMedian::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName,
+ SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double tmp = 0;\n";
+ ss << " int i;\n";
+ ss << " unsigned int startFlag = 0;\n";
+ ss << " unsigned int endFlag = 0;\n";
+ ss << " double dataIna;\n";
+ for (const DynamicKernelArgumentRef & rArg : vSubArguments)
+ {
+ FormulaToken *pCur = rArg->GetFormulaToken();
+ assert(pCur);
+ if (const formula::DoubleVectorRefToken* pCurDVR =
+ dynamic_cast<const formula::DoubleVectorRefToken *>(pCur))
+ {
+ size_t nCurWindowSize = pCurDVR->GetRefRowSize();
+ ss << "startFlag = ";
+ if (!pCurDVR->IsStartFixed() && pCurDVR->IsEndFixed())
+ {
+ ss << "gid0; endFlag = "<< nCurWindowSize <<"-gid0;\n";
+ }
+ ss << "gid0; endFlag = gid0+"<< nCurWindowSize <<";\n";
+ }
+ else
+ {
+ ss<<"startFlag=gid0;endFlag=gid0;\n";
+ }
+ }
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ const formula::DoubleVectorRefToken*tmpCurDVR0= static_cast<const
+ formula::DoubleVectorRefToken *>(tmpCur0);
+ ss << "int buffer_fIna_len = ";
+ ss << tmpCurDVR0->GetArrayLength();
+ ss << ";\n";
+ ss<<"if((i+gid0)>=buffer_fIna_len || isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss<<"))\n";
+ ss<<" dataIna = 0;\n";
+ ss << " int nSize =endFlag- startFlag ;\n";
+ ss << " if (nSize & 1)\n";
+ ss << " {\n";
+ ss << " tmp = "<<vSubArguments[0]->GetName();
+ ss << " [startFlag+nSize/2];\n";
+ ss << " }\n";
+ ss << " else\n";
+ ss << " {\n";
+ ss << " tmp =("<<vSubArguments[0]->GetName();
+ ss << " [startFlag+nSize/2]+";
+ ss << vSubArguments[0]->GetName();
+ ss << " [startFlag+nSize/2-1])/2;\n";
+ ss << " }\n";
+ ss <<" return tmp;\n";
+ ss << "}\n";
+}
+void OpKurt:: GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"( ";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss <<"{\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double fSum = 0.0;\n";
+ ss << " double vSum = 0.0;\n";
+ ss << " double length;\n";
+ ss << " double totallength=0;\n";
+ ss << " double tmp = 0;\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " length="<<nCurWindowSize;
+ ss << ";\n";
+ ss << " for (int i = ";
+ ss << "0; i < "<< nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ ss << " double arg"<<i<<" = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef(true);
+ ss << ";\n";
+ ss << " if(isnan(arg"<<i<<")||((gid0+i)>=";
+ ss << pDVR->GetArrayLength();
+ ss << "))\n";
+ ss << " {\n";
+ ss << " length-=1.0;\n";
+ ss << " continue;\n";
+ ss << " }\n";
+ ss << " fSum += arg"<<i<<";\n";
+ ss << " }\n";
+ ss << " totallength +=length;\n";
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ ss << " tmp = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(!isnan(tmp))\n";
+ ss << " {\n";
+ ss << " fSum += tmp;\n";
+ ss << " totallength +=1;\n";
+ ss << " }\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << " tmp = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " fSum += tmp;\n";
+ ss << " totallength +=1;\n";
+ }
+ else
+ {
+ ss << " return DBL_MIN;\n";
+ }
+ }
+ ss << " double fMean = fSum * pow(totallength,-1);\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ ss << "0; i < "<< nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ ss << " double arg"<<i<<" = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef(true);
+ ss << ";\n";
+ ss << " if(isnan(arg"<<i<<")||((gid0+i)>=";
+ ss << pDVR->GetArrayLength();
+ ss << "))\n";
+ ss << " {\n";
+ ss << " continue;\n";
+ ss << " }\n";
+ ss << " vSum += (arg"<<i<<"-fMean)*(arg"<<i<<"-fMean);\n";
+ ss << " }\n";
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ ss << " tmp = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(!isnan(tmp))\n";
+ ss << " {\n";
+ ss << " vSum += (tmp-fMean)*(tmp-fMean);\n";
+ ss << " }\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << " tmp = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " vSum += (tmp-fMean)*(tmp-fMean);\n";
+ }
+ }
+ ss << " double fStdDev = sqrt(vSum / (totallength - 1.0));\n";
+ ss << " double dx = 0.0;\n";
+ ss << " double xpower4 = 0.0;\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ ss << "0; i < "<< nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ ss << " double arg"<<i<<" = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef(true);
+ ss << ";\n";
+ ss << " if(isnan(arg"<<i<<")||((gid0+i)>=";
+ ss << pDVR->GetArrayLength();
+ ss << "))\n";
+ ss << " {\n";
+ ss << " continue;\n";
+ ss << " }\n";
+ ss<< " dx = (arg"<<i<<" -fMean) / fStdDev;\n";
+ ss<< " xpower4 = xpower4 + (dx * dx * dx * dx);\n";
+ ss << " }\n";
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ ss << " tmp = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(!isnan(tmp))\n";
+ ss << " {\n";
+ ss<< " dx = (tmp -fMean) / fStdDev;\n";
+ ss<< " xpower4 = xpower4 + (dx * dx * dx * dx);\n";
+ ss << " }\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << " tmp = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss<< " dx = (tmp -fMean) / fStdDev;\n";
+ ss<< " xpower4 = xpower4 + (dx * dx * dx * dx);\n";
+ }
+ }
+ ss<< " double k_d = (totallength - 2.0) * (totallength - 3.0);\n";
+ ss<< " double k_l = totallength * (totallength + 1.0) /";
+ ss<< "((totallength - 1.0) * k_d);\n";
+ ss<< " double k_t = 3.0 * (totallength - 1.0) * ";
+ ss<< "(totallength - 1.0) / k_d;\n";
+ ss<< " tmp = xpower4 * k_l - k_t;\n";
+ ss<< " return tmp;\n";
+ ss << "}";
+}
+
+void OpIntercept::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_" << BinFuncName() << "(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << "){\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double fSumX = 0.0;\n";
+ ss << " double fSumY = 0.0;\n";
+ ss << " double fMeanX = 0.0;\n";
+ ss << " double fMeanY = 0.0;\n";
+ ss << " double fSumDeltaXDeltaY = 0.0;\n";
+ ss << " double fSumSqrDeltaX = 0.0;\n";
+ ss << " double fCount = 0.0;\n";
+ ss << " double argX = 0.0;\n";
+ ss << " double argY = 0.0;\n";
+ if(vSubArguments.size() != 2)
+ {
+ ss << " return NAN;\n";
+ ss << "}\n";
+ return ;
+ }
+ FormulaToken *pCur = vSubArguments[1]->GetFormulaToken();
+ FormulaToken *pCur1 = vSubArguments[0]->GetFormulaToken();
+ assert(pCur);
+ assert(pCur1);
+ if (pCur->GetType() == formula::svDoubleVectorRef&&
+ pCur1->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ const formula::DoubleVectorRefToken* pDVR1 =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur1);
+
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ size_t nCurWindowSize1 = pDVR1->GetRefRowSize();
+ size_t arrayLength = pDVR->GetArrayLength()<
+ pDVR1->GetArrayLength() ? pDVR->GetArrayLength():
+ pDVR1->GetArrayLength();
+ if(nCurWindowSize != nCurWindowSize1)
+ {
+ ss << " return NAN;\n";
+ ss << "}\n";
+ return ;
+ }
+ ss << " for (int i = ";
+ if ((!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ &&(!pDVR1->IsStartFixed() && pDVR1->IsEndFixed()))
+ {
+ ss << "gid0; i < " << arrayLength;
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if ((pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ &&(pDVR1->IsStartFixed() && !pDVR1->IsEndFixed()))
+ {
+ ss << "0; i < " << arrayLength ;
+ ss << " && i < gid0+" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if ((!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ &&(!pDVR1->IsStartFixed() && !pDVR1->IsEndFixed()))
+ {
+ ss << "0; i + gid0 < " << arrayLength;
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if ((pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ &&(pDVR1->IsStartFixed() && pDVR1->IsEndFixed()))
+ {
+ ss << "0; i < " << arrayLength << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ ss << " break;\n";
+ ss << " }";
+ ss << " return NAN;\n";
+ ss << "}\n";
+ return ;
+ }
+
+ ss << " argX = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " argY = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (isnan(argX) || isnan(argY))\n";
+ ss << " continue;\n";
+ ss << " fSumX += argX;\n";
+ ss << " fSumY += argY;\n";
+ ss << " fCount += 1.0;\n";
+ ss << " }\n";
+
+ ss << " if (fCount < 1.0)\n";
+ ss << " return NAN;\n";
+ ss << " else\n";
+ ss << " {\n";
+ ss << " fMeanX = fSumX * pow(fCount,-1.0);\n";
+ ss << " fMeanY = fSumY * pow(fCount,-1.0);\n";
+
+ ss << " for (int i = ";
+ if ((!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ &&(!pDVR1->IsStartFixed() && pDVR1->IsEndFixed()))
+ {
+ ss << "gid0; i < " << arrayLength;
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if ((pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ &&(pDVR1->IsStartFixed() && !pDVR1->IsEndFixed()))
+ {
+ ss << "0; i < " << arrayLength ;
+ ss << " && i < gid0+" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if ((!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ &&(!pDVR1->IsStartFixed() && !pDVR1->IsEndFixed()))
+ {
+ ss << "0; i + gid0 < " << arrayLength;
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < " << arrayLength << "; i++)\n";
+ ss << " {\n";
+ }
+
+ ss << " argX = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " argY = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (isnan(argX) || isnan(argY))\n";
+ ss << " continue;\n";
+ ss << " fSumDeltaXDeltaY += (argX-fMeanX)*(argY-fMeanY);\n";
+ ss << " fSumSqrDeltaX += (argX-fMeanX) * (argX-fMeanX);\n";
+ ss << " }\n";
+ ss << " if(fSumSqrDeltaX == 0.0)\n";
+ ss << " return NAN;\n";
+ ss << " else\n";
+ ss << " {\n";
+ ss << " return fMeanY -";
+ ss << " (fSumDeltaXDeltaY*pow(fSumSqrDeltaX,-1.0))*fMeanX;\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << "}\n";
+ }
+ else
+ {
+ ss << " return NAN;\n";
+ ss << "}\n";
+ }
+}
+void OpLogInv:: GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double tmp;\n";
+ ss << " double arg0,arg1,arg2,arg3;\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << "for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed()) {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++){\n";
+ } else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed()) {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+"<< nCurWindowSize << "; i++){\n";
+ } else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed()){
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < "<< nCurWindowSize << "; i++){\n ";
+ }
+ else {
+ ss << "0; i < "<< nCurWindowSize << "; i++){\n";
+ }
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " arg"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " arg"<<i<<"=";
+ ss<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " }\n";
+ ss << " else\n";
+ ss << " arg"<<i<<"= 0;\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " arg"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " arg"<<i<<"=";
+ ss<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ }
+ ss<< " double q,t,z;\n";
+ ss<< " q = arg0 -0.5;\n";
+ ss<< " if(fabs(q)<=.425)\n";
+ ss<< " {\n";
+ ss<< " t=0.180625-pow(q, 2);\n";
+ ss<< " z=\n"
+ " q*\n"
+ " (\n"
+ " (\n"
+ " (\n"
+ " (\n"
+ " (\n"
+ " (\n"
+ " (\n"
+ " t*2509.0809287301226727";
+ ss<<"+33430.575583588128105\n"
+ " )\n"
+ " *t+67265.770927008700853\n"
+ " )\n"
+ " *t+45921.953931549871457\n"
+ " )\n"
+ " *t+13731.693765509461125\n"
+ " )\n"
+ " *t+1971.5909503065514427\n"
+ " )\n"
+ " *t+133.14166789178437745\n"
+ " )\n"
+ " *t+3.387132872796366608\n"
+ " )\n"
+ " /\n"
+ " (\n"
+ " (\n"
+ " (\n"
+ " (\n"
+ " (\n"
+ " (\n"
+ " (\n"
+ " t*5226.495278852854561";
+ ss<<"+28729.085735721942674\n"
+ " )\n"
+ " *t+39307.89580009271061\n"
+ " )\n"
+ " *t+21213.794301586595867\n"
+ " )\n"
+ " *t+5394.1960214247511077\n"
+ " )\n"
+ " *t+687.1870074920579083\n"
+ " )\n"
+ " *t+42.313330701600911252\n"
+ " )\n"
+ " *t+1.0\n"
+ " );\n";
+ ss<<" }\n";
+ ss<<" else\n";
+ ss<<" {\n";
+ ss<<" t = q > 0 ? 1 - arg0 : arg0;\n";
+ ss<<" t=sqrt(-log(t));\n";
+ ss<<" if(t<=5.0)\n";
+ ss<<" {\n";
+ ss<<" t+=-1.6;\n";
+ ss<<" z=\n"
+ " (\n"
+ " (\n"
+ " (\n"
+ " (\n"
+ " (\n"
+ " (\n"
+ " (\n"
+ " t*7.7454501427834140764e-4";
+ ss<<"+0.0227238449892691845833\n"
+ " )\n"
+ " *t+0.24178072517745061177\n"
+ " )\n"
+ " *t+1.27045825245236838258\n"
+ " )\n"
+ " *t+3.64784832476320460504\n"
+ " )\n"
+ " *t+5.7694972214606914055\n"
+ " )\n"
+ " *t+4.6303378461565452959\n"
+ " )\n"
+ " *t+1.42343711074968357734\n"
+ " )\n"
+ " /\n"
+ " (\n"
+ " (\n"
+ " (\n"
+ " (\n"
+ " (\n"
+ " (\n"
+ " (\n"
+ " t*1.05075007164441684324e-9";
+ ss<<"+5.475938084995344946e-4\n"
+ " )\n"
+ " *t+0.0151986665636164571966\n"
+ " )\n"
+ " *t+0.14810397642748007459\n"
+ " )\n"
+ " *t+0.68976733498510000455\n"
+ " )\n"
+ " *t+1.6763848301838038494\n"
+ " )\n"
+ " *t+2.05319162663775882187\n"
+ " )\n"
+ " *t+1.0\n"
+ " );\n";
+ ss<<" }\n";
+ ss<<" else\n";
+ ss<<" {\n";
+ ss<<" t+=-5.0;\n";
+ ss<<" z=\n"
+ " (\n"
+ " (\n"
+ " (\n"
+ " (\n"
+ " (\n"
+ " (\n"
+ " (\n"
+ " t*2.01033439929228813265e-7";
+ ss<<"+2.71155556874348757815e-5\n"
+ " )\n"
+ " *t+0.0012426609473880784386\n"
+ " )\n"
+ " *t+0.026532189526576123093\n"
+ " )\n"
+ " *t+0.29656057182850489123\n"
+ " )\n"
+ " *t+1.7848265399172913358\n"
+ " )\n"
+ " *t+5.4637849111641143699\n"
+ " )\n"
+ " *t+6.6579046435011037772\n"
+ " )\n"
+ " /\n"
+ " (\n"
+ " (\n"
+ " (\n"
+ " (\n"
+ " (\n"
+ " (\n"
+ " (\n"
+ " t*2.04426310338993978564e-15"
+ " +1.4215117583164458887e-7\n"
+ " )\n"
+ " *t+1.8463183175100546818e-5\n"
+ " )\n"
+ " *t+7.868691311456132591e-4\n"
+ " )\n"
+ " *t+0.0148753612908506148525\n"
+ " )\n"
+ " *t+0.13692988092273580531\n"
+ " )\n"
+ " *t+0.59983220655588793769\n"
+ " )\n"
+ " *t+1.0\n"
+ " );\n";
+ ss << " }\n";
+ ss << " z = q < 0.0 ? (-1)*z : z;\n";
+ ss << " }\n";
+ ss << " tmp = exp(arg1+arg2*z);\n";
+ ss << " return tmp;\n";
+ ss << "}\n";
+}
+
+void OpForecast::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ FormulaToken *pCur0 = vSubArguments[0]->GetFormulaToken();
+ assert(pCur0);
+ const formula::SingleVectorRefToken*pCurDVR0= static_cast<const
+ formula::SingleVectorRefToken *>(pCur0);
+ FormulaToken *pCur1 = vSubArguments[1]->GetFormulaToken();
+ assert(pCur1);
+ const formula::DoubleVectorRefToken* pCurDVR1 =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur1);
+ size_t nCurWindowSize = pCurDVR1->GetRefRowSize();
+ FormulaToken *pCur2 = vSubArguments[2]->GetFormulaToken();
+ assert(pCur2);
+ const formula::DoubleVectorRefToken* pCurDVR2 =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur2);
+ size_t nCurWindowSize1 = pCurDVR2->GetRefRowSize();
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"( ";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double fSumX = 0.0;\n";
+ ss << " double fSumY = 0.0;\n";
+ ss << " double fSumDeltaXDeltaY = 0.0;\n";
+ ss << " double fSumSqrDeltaX = 0.0;\n";
+ if(pCur0->GetType()== formula::svDouble ||
+ pCur0->GetType() == formula::svSingleVectorRef)
+ {
+ ss << " double arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ else
+ ss << "return HUGE_VAL";
+ if(pCur1->GetType() != formula::svDoubleVectorRef ||
+ pCur2->GetType() != formula::svDoubleVectorRef)
+ ss << "return HUGE_VAL";
+ else
+ {
+ ss<< " if(isnan(arg0)||(gid0>=";
+ ss<<pCurDVR0->GetArrayLength();
+ ss<<"))\n";
+ ss<<" arg0 = 0;\n";
+ ss << " int length="<<nCurWindowSize;
+ ss << ";\n";
+ ss << " int length1= "<<nCurWindowSize1;
+ ss << ";\n";
+ ss << " if(length!=length1)\n";
+ ss << " return 0;\n";
+ ss << " double tmp = 0;\n";
+ ss << " for (int i = 0; i <" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ ss << " double arg1 = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef(true);
+ ss << ";\n";
+ ss << " double arg2 = ";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef(true);
+ ss << ";\n";
+ ss << " if(isnan(arg1)||((gid0+i)>=";
+ ss << pCurDVR1->GetArrayLength();
+ ss << "))\n";
+ ss << " {\n";
+ ss << " length--;\n";
+ ss << " continue;\n";
+ ss << " }\n";
+ ss << " if(isnan(arg2)||((gid0+i)>=";
+ ss << pCurDVR2->GetArrayLength();
+ ss << "))\n";
+ ss << " {\n";
+ ss << " length--;\n";
+ ss << " continue;\n";
+ ss << " }\n";
+ ss << " fSumY+=arg1;\n";
+ ss << " fSumX+=arg2;\n";
+ ss << " }\n";
+ ss << " double fMeanX = fSumX / length;\n";
+ ss << " double fMeanY = fSumY / length;\n";
+ ss << " for (int i = 0; i <" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ ss << " double arg1 = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef(true);
+ ss << ";\n";
+ ss << " double arg2 = ";
+ ss << vSubArguments[2]->GenSlidingWindowDeclRef(true);
+ ss << ";\n";
+ ss << " if(isnan(arg1)||((gid0+i)>=";
+ ss <<pCurDVR1->GetArrayLength();
+ ss <<"))\n";
+ ss <<" {\n";
+ ss <<" continue;\n";
+ ss <<" }\n";
+ ss << " if(isnan(arg2)||((gid0+i)>=";
+ ss <<pCurDVR2->GetArrayLength();
+ ss <<"))\n";
+ ss <<" {\n";
+ ss <<" continue;\n";
+ ss <<" }\n";
+ ss <<" fSumDeltaXDeltaY+=(arg2 - fMeanX) * (arg1 - fMeanY);\n";
+ ss <<" fSumSqrDeltaX+=pow(arg2 - fMeanX, 2);\n";
+ ss <<" }\n";
+ ss <<" tmp =fMeanY + fSumDeltaXDeltaY / fSumSqrDeltaX *";
+ ss <<" (arg0 - fMeanX);\n";
+ ss <<" return tmp;\n";
+ ss << "}";
+ }
+}
+void OpLogNormDist::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ FormulaToken *tmpCur0 = vSubArguments[0]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR0= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur0);
+ FormulaToken *tmpCur1 = vSubArguments[1]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR1= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur1);
+ FormulaToken *tmpCur2 = vSubArguments[2]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR2= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur2);
+ FormulaToken *tmpCur3 = vSubArguments[3]->GetFormulaToken();
+ const formula::SingleVectorRefToken*tmpCurDVR3= static_cast<const
+ formula::SingleVectorRefToken *>(tmpCur3);
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double arg0,arg1,arg2,arg3;\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << "for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed()) {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++){\n";
+ } else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed()) {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+"<< nCurWindowSize << "; i++){\n";
+ } else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed()){
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < "<< nCurWindowSize << "; i++){\n ";
+ }
+ else {
+ ss << "0; i < "<< nCurWindowSize << "; i++){\n";
+ }
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " arg"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " arg"<<i<<"=";
+ ss<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " }\n";
+ ss << " else\n";
+ ss << " arg"<<i<<"= 0;\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " arg"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " arg"<<i<<"=";
+ ss <<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ }
+ ss << " double tmp;\n";
+ ss << " if(isnan(arg0)||(gid0>=";
+ ss << tmpCurDVR0->GetArrayLength();
+ ss << "))\n";
+ ss << " arg0 = 0;\n";
+ ss << " if(isnan(arg1)||(gid0>=";
+ ss << tmpCurDVR1->GetArrayLength();
+ ss << "))\n";
+ ss << " arg1 = 0;\n";
+ ss << " if(isnan(arg2)||(gid0>=";
+ ss << tmpCurDVR2->GetArrayLength();
+ ss << "))\n";
+ ss << " arg2 = 0;\n";
+ ss << " if(isnan(arg3)||(gid0>=";
+ ss << tmpCurDVR3->GetArrayLength();
+ ss << "))\n";
+ ss << " arg3 = 0;\n";
+ ss << " double temp = (log(arg0)-arg1)/arg2;\n";
+ ss << " if(arg3)\n";
+ ss << " {\n";
+ ss << " if(arg0<=0)\n";
+ ss << " tmp = 0.0;\n";
+ ss << " else\n";
+ ss << " tmp = 0.5 * erfc(-temp * 0.7071067811865475);\n";
+ ss << " }\n";
+ ss << " else\n";
+ ss << " tmp = (0.39894228040143268 * exp((-1)*pow(temp, 2)";
+ ss << " / 2.0))/(arg2*arg0);\n";
+ ss << " return tmp;\n";
+ ss << "}\n";
+}
+
+void OpGammaDist::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(fBigInvDecl);decls.insert(fLogDblMaxDecl);
+ decls.insert(fHalfMachEpsDecl);decls.insert(fMaxGammaArgumentDecl);
+ decls.insert(GetGammaSeriesDecl);decls.insert(GetGammaContFractionDecl);
+ decls.insert(GetLowRegIGammaDecl);decls.insert(GetGammaDistDecl);
+ decls.insert(GetGammaDistPDFDecl);
+ funs.insert(GetGammaSeries);funs.insert(GetGammaContFraction);
+ funs.insert(GetLowRegIGamma);funs.insert(GetGammaDist);
+ funs.insert(GetGammaDistPDF);
+}
+
+void OpGammaDist::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double tmp;\n";
+ ss << " double arg0,arg1,arg2,arg3;\n";
+
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << "for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed()) {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++){\n";
+ } else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed()) {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+"<< nCurWindowSize << "; i++){\n";
+ } else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed()){
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < "<< nCurWindowSize << "; i++){\n ";
+ }
+ else {
+ ss << "0; i < "<< nCurWindowSize << "; i++){\n";
+ }
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " arg"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " arg"<<i<<"=";
+ ss<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " }\n";
+ ss << " else\n";
+ ss << " arg"<<i<<"= 0;\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " arg"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " arg"<<i<<"=";
+ ss <<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ }
+ ss << " if (arg3)\n";
+ ss << " tmp=GetGammaDist( arg0, arg1, arg2);\n";
+ ss << " else\n";
+ ss << " tmp=GetGammaDistPDF( arg0, arg1, arg2);\n";
+ ss << " return tmp;\n";
+ ss << "}\n";
+}
+void OpChiDist::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(fBigInvDecl);
+ funs.insert("");
+ decls.insert(fHalfMachEpsDecl);
+ funs.insert("");
+ decls.insert(GetUpRegIGammaDecl);
+ funs.insert(GetUpRegIGamma);
+ decls.insert(GetGammaSeriesDecl);
+ funs.insert(GetGammaSeries);
+ decls.insert(GetGammaContFractionDecl);
+ funs.insert(GetGammaContFraction);
+ decls.insert(GetChiDistDecl);
+ funs.insert(GetChiDist);
+}
+void OpChiDist::GenSlidingWindowFunction(
+ std::stringstream &ss,const std::string &sSymName,
+ SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " double fx,fDF,tmp=0,tmp0=0,tmp1=0;\n";
+ ss << " int gid0=get_global_id(0);\n";
+
+ ss <<"\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << "for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++){\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+"<< nCurWindowSize << "; i++){\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < "<< nCurWindowSize << "; i++){\n";
+ }
+ else
+ {
+ ss << "0; i < "<< nCurWindowSize << "; i++){\n";
+ }
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << "if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << "{\n";
+ }
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " tmp"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " tmp"<<i<<"=\n";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n}\n";
+ }
+ }
+ ss << " fx = tmp0;\n";
+ ss << " fDF = floor(tmp1);\n";
+ ss << " if(fDF < 1.0)\n";
+ ss << " {\n";
+ ss << " return DBL_MIN;\n";
+ ss << " }\n";
+ ss << " tmp = GetChiDist( fx, fDF);\n";
+ ss << " return tmp;\n";
+ ss << "}\n";
+}
+void OpBinomdist::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(fMachEpsDecl);
+ funs.insert("");
+ decls.insert(MinDecl);
+ funs.insert("");
+ decls.insert(fMaxGammaArgumentDecl);
+ funs.insert("");
+ decls.insert(GetBinomDistPMFDecl);
+ funs.insert(GetBinomDistPMF);
+ decls.insert(GetBetaDistDecl);
+ funs.insert(GetBetaDist);
+ decls.insert(lcl_GetBinomDistRangeDecl);
+ funs.insert(lcl_GetBinomDistRange);
+ decls.insert(lcl_GetBetaHelperContFracDecl);
+ funs.insert(lcl_GetBetaHelperContFrac);
+ decls.insert(GetBetaDistPDFDecl);
+ funs.insert(GetBetaDistPDF);
+ decls.insert(GetLogBetaDecl);
+ funs.insert(GetLogBeta);
+ decls.insert(GetBetaDecl);
+ funs.insert(GetBeta);
+ decls.insert(lcl_getLanczosSumDecl);
+ funs.insert(lcl_getLanczosSum);
+}
+void OpBinomdist::GenSlidingWindowFunction(
+ std::stringstream &ss,const std::string &sSymName,
+ SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " double tmp0,tmp1,tmp2,tmp3;\n";
+ ss << " int gid0=get_global_id(0);\n";
+
+ ss <<"\n ";
+ //while (i-- > 1)
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << "for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++){\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+"<< nCurWindowSize << "; i++){\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < "<< nCurWindowSize << "; i++){\n";
+ }
+ else
+ {
+ ss << "0; i < "<< nCurWindowSize << "; i++){\n";
+ }
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << "if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << "{\n";
+ }
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " tmp"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " tmp"<<i<<"=\n";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n}\n";
+ }
+ else
+ {
+ ss << "tmp"<<i<<"="<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ }
+ }
+ ss << " tmp0 = floor(tmp0);\n";
+ ss << " tmp1 = floor(tmp1);\n";
+ ss << " double rq = (0.5 - tmp2) + 0.5;\n";
+ ss << " if (tmp1 < 0.0 || tmp0 < 0.0 || tmp0 > tmp1 ||";
+ ss << "tmp2 < 0.0 || tmp2 > 1.0)\n";
+ ss << " {\n";
+ ss << " return DBL_MIN;\n";
+ ss << " }\n";
+ ss << " if(tmp2 == 0.0)\n";
+ ss << " return ( (tmp0 == 0.0 || tmp3) ? 1.0 : 0.0 );\n";
+ ss << " if(tmp2 == 1.0)\n";
+ ss << " return ( (tmp0 == tmp1) ? 1.0 : 0.0);\n";
+ ss << " if(!tmp3)\n";
+ ss << " return ( GetBinomDistPMF(tmp0, tmp1, tmp2));\n";
+ ss << " else \n";
+ ss << " {\n";
+ ss << " if(tmp0 == tmp1)\n";
+ ss << " return 1.0;\n";
+ ss << " else\n";
+ ss << " {\n";
+ ss << " double fFactor = pow(rq,tmp1);\n";
+ ss << " if(tmp0 == 0.0)\n";
+ ss << " return (fFactor);\n";
+ ss << " else if(fFactor <= Min)\n";
+ ss << " {\n";
+ ss << " fFactor = pow(tmp2,tmp1);\n";
+ ss << " if(fFactor <= Min)\n";
+ ss << " return GetBetaDist";
+ ss << "(rq, tmp1 - tmp0, tmp0 + 1.0);\n";
+ ss << " else\n";
+ ss << " {\n";
+ ss << " if(fFactor > fMachEps)\n";
+ ss << " {\n";
+ ss << " double fSum = 1.0 - fFactor;\n";
+ ss << " unsigned int max = ";
+ ss << "(unsigned int)((tmp1 - tmp0)-1);\n";
+ ss << " for (uint i = 0; i < max && fFactor > 0.0;";
+ ss << " i++)\n";
+ ss << " {\n";
+ ss << " fFactor *= (tmp1 - i)*pow((i + 1),-1.0)*";
+ ss << "rq*pow(tmp2,-1.0);\n";
+ ss << " fSum -= fFactor;\n";
+ ss << " }\n";
+ ss << " return ( (fSum < 0.0) ? 0.0 : fSum );\n";
+ ss << " }\n";
+ ss << " else \n";
+ ss << " return (lcl_GetBinomDistRange";
+ ss << "(tmp1, tmp1 - tmp0, tmp1, fFactor, rq, tmp2));\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << " else\n";
+ ss << " {\n";
+ ss << " double rtmp = ( lcl_GetBinomDistRange";
+ ss << "(tmp1, 0.0, tmp0, fFactor, tmp2, rq));\n";
+ ss << " return rtmp;\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << "}\n";
+}
+
+void OpChiSqDist::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(fMaxGammaArgumentDecl);decls.insert(GetChiSqDistCDFDecl);
+ decls.insert(GetChiSqDistPDFDecl);decls.insert(GetLowRegIGammaDecl);
+ decls.insert(GetGammaContFractionDecl);decls.insert(GetGammaSeriesDecl);
+ decls.insert(fHalfMachEpsDecl);decls.insert(F_PIDecl);
+ decls.insert(fBigInvDecl);
+
+ funs.insert(GetGammaContFraction);funs.insert(GetChiSqDistCDF);
+ funs.insert(GetChiSqDistPDF);funs.insert(GetLowRegIGamma);
+ funs.insert(GetGammaSeries);
+}
+
+void OpChiSqDist::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &
+vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " int singleIndex = gid0;\n";
+ ss << " double result = 0;\n";
+ if(vSubArguments.size()<2)
+ {
+ ss << " result = -DBL_MAX;\n";
+ ss << " return result;\n";
+ }else
+ {
+ GenTmpVariables(ss,vSubArguments);
+ CheckAllSubArgumentIsNan(ss,vSubArguments);
+ if(vSubArguments.size() == 2)
+ {
+ ss << " int tmp2 = 1;\n";
+ }
+ }
+ ss <<"\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << "if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << "{\n";
+ }
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " tmp"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " tmp"<<i<<"=\n";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n}\n";
+ }
+ else
+ {
+ ss << "tmp"<<i<<"="<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ }
+ }
+ ss << " tmp1 = floor(tmp1);\n";
+ ss << " if(tmp1 < 1.0)\n";
+ ss << " result = -DBL_MAX;\n";
+ ss << " else\n";
+ ss << " {\n";
+ ss << " if(tmp2)\n";
+ ss << " result =GetChiSqDistCDF(tmp0,tmp1);\n";
+ ss << " else\n";
+ ss << " result =GetChiSqDistPDF(tmp0,tmp1);\n";
+ ss << " }\n";
+ ss << " return result;\n";
+ ss << "}";
+}
+
+ void OpChiSqInv::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(fMaxGammaArgumentDecl);decls.insert(GetChiSqDistCDFDecl);
+ decls.insert(GetLowRegIGammaDecl);decls.insert(lcl_IterateInverseChiSQInvDecl);
+ decls.insert(GetGammaContFractionDecl);decls.insert(GetGammaSeriesDecl);
+ decls.insert(fHalfMachEpsDecl);decls.insert(F_PIDecl);
+ decls.insert(fBigInvDecl);decls.insert(lcl_HasChangeOfSignDecl);
+ decls.insert(fMachEpsDecl);
+
+ funs.insert(GetGammaContFraction);funs.insert(GetChiSqDistCDF);
+ funs.insert(GetLowRegIGamma);funs.insert(lcl_HasChangeOfSign);
+ funs.insert(GetGammaSeries);funs.insert(lcl_IterateInverseChiSQInv);
+}
+
+void OpChiSqInv::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &
+vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " int singleIndex = gid0;\n";
+ ss << " double result = 0;\n";
+ if(vSubArguments.size()!=2)
+ {
+ ss << " result = -DBL_MAX;\n";
+ ss << " return result;\n";
+ }
+ else
+ {
+ GenTmpVariables(ss,vSubArguments);
+ CheckAllSubArgumentIsNan(ss,vSubArguments);
+ ss <<"\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << "if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << "{\n";
+ }
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " tmp"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " tmp"<<i<<"=\n";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n}\n";
+ }
+ else
+ {
+ ss << "tmp"<<i<<"="<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ }
+ }
+ ss << " tmp1 = floor(tmp1);\n";
+ ss << " bool bConvError;\n";
+ ss << " if(tmp1 < 1.0 || tmp0 < 0 || tmp0>=1.0)\n";
+ ss << " result = -DBL_MAX;\n";
+ ss << " else\n";
+ ss << " {\n";
+ ss << " result =lcl_IterateInverseChiSQInv( tmp0, tmp1,";
+ ss << "tmp1*0.5, tmp1, &bConvError );\n";
+ ss << " }\n";
+ ss << " if(bConvError)\n";
+ ss << " result = -DBL_MAX;\n";
+ ss << " return result;\n";
+ ss << "}";
+ }
+
+}
+void OpGammaInv::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(fBigInvDecl);decls.insert(fHalfMachEpsDecl);
+ decls.insert(GetGammaSeriesDecl);decls.insert(GetGammaContFractionDecl);
+ decls.insert(GetGammaInvValueDecl);
+ funs.insert(GetGammaSeries);funs.insert(GetGammaContFraction);
+ funs.insert(GetGammaInvValue);
+}
+
+void OpGammaInv::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double tmp;\n";
+ ss << " double arg0,arg1,arg2;\n";
+
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << "for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed()) {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++){\n";
+ } else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed()) {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+"<< nCurWindowSize << "; i++){\n";
+ } else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed()){
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < "<< nCurWindowSize << "; i++){\n ";
+ }
+ else {
+ ss << "0; i < "<< nCurWindowSize << "; i++){\n";
+ }
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " arg"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " arg"<<i<<"=";
+ ss <<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " }\n";
+ ss << " else\n";
+ ss << " arg"<<i<<"= 0;\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " arg"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " arg"<<i<<"=";
+ ss <<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ }
+ ss << " if (arg0 == 0.0)\n"
+ " {\n"
+ " tmp=0.0;\n"
+ " return tmp;\n"
+ " }\n"
+ " else\n"
+ " {\n"
+ " bool bConvError;\n"
+ " double fStart = arg1 * arg2;\n"
+ " double fAx=fStart*0.5;\n"
+ " double fBx=fStart;\n"
+ " bConvError = false;\n"
+ " double fYEps = 1.0E-307;\n"
+ " double fXEps = 2.22045e-016;\n"
+ " double fAy = arg0-GetGammaInvValue(arg1,arg2,fAx);\n"
+ " double fBy = arg0-GetGammaInvValue(arg1,arg2,fBx);\n"
+ " double fTemp;\n"
+ " unsigned short nCount;\n"
+ " for (nCount = 0; nCount < 1000 && !((fAy < 0.0 && fBy > 0.0)"
+ " || (fAy > 0.0 && fBy < 0.0)); nCount++)\n"
+ " {\n"
+ " if (fabs(fAy) <= fabs(fBy))\n"
+ " {\n"
+ " fTemp = fAx;\n"
+ " fAx += 2.0 * (fAx - fBx);\n"
+ " if (fAx < 0.0)\n"
+ " fAx = 0.0;\n"
+ " fBx = fTemp;\n"
+ " fBy = fAy;\n"
+ " fAy = arg0-GetGammaInvValue(arg1,arg2,fAx);\n"
+ " }\n"
+ " else\n"
+ " {\n"
+ " fTemp = fBx;\n"
+ " fBx += 2.0 * (fBx - fAx);\n"
+ " fAx = fTemp;\n"
+ " fAy = fBy;\n"
+ " fBy = arg0-GetGammaInvValue(arg1,arg2,fBx);\n"
+ " }\n"
+ " }\n"
+ " if (fAy == 0.0)\n"
+ " {\n"
+ " tmp = fAx;\n"
+ " return tmp;\n"
+ " }\n"
+ " if (fBy == 0.0)\n"
+ " {\n"
+ " tmp = fBx;\n"
+ " return tmp;\n"
+ " }\n"
+ " if (!((fAy < 0.0 && fBy > 0.0) || (fAy > 0.0 && fBy < 0.0)))\n"
+ " {\n"
+ " bConvError = true;\n"
+ " tmp = 0.0;\n"
+ " return tmp;\n"
+ " }\n"
+ " double fPx = fAx;\n"
+ " double fPy = fAy;\n"
+ " double fQx = fBx;\n"
+ " double fQy = fBy;\n"
+ " double fRx = fAx;\n"
+ " double fRy = fAy;\n"
+ " double fSx = 0.5 * (fAx + fBx);\n"
+ " bool bHasToInterpolate = true;\n"
+ " nCount = 0;\n"
+ " while ( nCount < 500 && fabs(fRy) > fYEps &&"
+ "(fBx-fAx) > fmax( fabs(fAx), fabs(fBx)) * fXEps )\n"
+ " {\n"
+ " if (bHasToInterpolate)\n"
+ " {\n"
+ " if (fPy!=fQy && fQy!=fRy && fRy!=fPy)\n"
+ " {\n"
+ " fSx = fPx * fRy * fQy *pow( (fRy-fPy),-1) *pow"
+ "( (fQy-fPy),-1)"
+ "+ fRx * fQy * fPy *pow( (fQy-fRy),-1) *pow( (fPy-fRy),-1)"
+ "+ fQx * fPy * fRy *pow( (fPy-fQy),-1) *pow( (fRy-fQy),-1);\n"
+ " bHasToInterpolate = (fAx < fSx) && (fSx < fBx);\n"
+ " }\n"
+ " else\n"
+ " bHasToInterpolate = false;\n"
+ " }\n"
+ " if(!bHasToInterpolate)\n"
+ " {\n"
+ " fSx = 0.5 * (fAx + fBx);\n"
+ " fPx = fAx; fPy = fAy;\n"
+ " fQx = fBx; fQy = fBy;\n"
+ " bHasToInterpolate = true;\n"
+ " }\n"
+ " fPx = fQx; fQx = fRx; fRx = fSx;\n"
+ " fPy = fQy; fQy = fRy;\n"
+ " fRy = arg0-GetGammaInvValue(arg1,arg2,fSx);\n"
+ " if ((fAy < 0.0 && fRy > 0.0) || (fAy > 0.0 && fRy < 0.0))\n"
+ " {\n"
+ " fBx = fRx;\n"
+ " fBy = fRy;\n"
+ " }\n"
+ " else\n"
+ " {\n"
+ " fAx = fRx;\n"
+ " fAy = fRy;\n"
+ " }\n"
+ " bHasToInterpolate = bHasToInterpolate && (fabs(fRy)"
+ " * 2.0 <= fabs(fQy));\n"
+ " ++nCount;\n"
+ " }\n"
+ " tmp = fRx;\n"
+ " return tmp;\n"
+ " }\n"
+ "}\n";
+}
+void OpFInv::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(fMachEpsDecl);decls.insert(fMaxGammaArgumentDecl);
+ decls.insert(lcl_getLanczosSumDecl);decls.insert(GetBetaDecl);
+ decls.insert(GetLogBetaDecl);decls.insert(GetBetaDistPDFDecl);
+ decls.insert(lcl_GetBetaHelperContFracDecl);decls.insert(GetFInvValueDecl);
+ funs.insert(lcl_getLanczosSum);funs.insert(GetBeta);
+ funs.insert(GetLogBeta);funs.insert(GetBetaDistPDF);
+ funs.insert(lcl_GetBetaHelperContFrac);funs.insert(GetFInvValue);
+}
+
+void OpFInv::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double tmp;\n";
+ ss << " double arg0,arg1,arg2;\n";
+
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << "for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed()) {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++){\n";
+ } else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed()) {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+"<< nCurWindowSize << "; i++){\n";
+ } else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed()){
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < "<< nCurWindowSize << "; i++){\n ";
+ }
+ else {
+ ss << "0; i < "<< nCurWindowSize << "; i++){\n";
+ }
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " arg"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " arg"<<i<<"="<<vSubArguments[i]->
+ GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " }\n";
+ ss << " else\n";
+ ss << " arg"<<i<<"= 0;\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " arg"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " arg"<<i<<"="<<vSubArguments[i]->
+ GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ }
+ ss << " double fF2=floor(arg2);\n"
+ " double fF1=floor(arg1);\n"
+ " bool bConvError;\n"
+ " double fAx=fF1*0.5;\n"
+ " double fBx=fF1;\n"
+ " bConvError = false;\n"
+ " const double fYEps = 1.0E-307;\n"
+ " const double fXEps = 2.22045e-016;\n"
+ " double fAy = arg0-GetFInvValue(fF1,fF2,fAx);\n"
+ " double fBy = arg0-GetFInvValue(fF1,fF2,fBx);\n"
+ " double fTemp;\n"
+ " unsigned short nCount;\n"
+ " for (nCount = 0; nCount < 1000 && !((fAy < 0.0 && fBy > 0.0)"
+ " || (fAy > 0.0 && fBy < 0.0)); nCount++)\n"
+ " {\n"
+ " if (fabs(fAy) <= fabs(fBy))\n"
+ " {\n"
+ " fTemp = fAx;\n"
+ " fAx += 2.0 * (fAx - fBx);\n"
+ " if (fAx < 0.0)\n"
+ " fAx = 0.0;\n"
+ " fBx = fTemp;\n"
+ " fBy = fAy;\n"
+ " fAy = arg0-GetFInvValue(fF1,fF2,fAx);\n"
+ " }\n"
+ " else\n"
+ " {\n"
+ " fTemp = fBx;\n"
+ " fBx += 2.0 * (fBx - fAx);\n"
+ " fAx = fTemp;\n"
+ " fAy = fBy;\n"
+ " fBy = arg0-GetFInvValue(fF1,fF2,fBx);\n"
+ " }\n"
+ " }\n"
+ " if (fAy == 0.0)\n"
+ " {\n"
+ " tmp = fAx;\n"
+ " return tmp;\n"
+ " }\n"
+ " if (fBy == 0.0)\n"
+ " {\n"
+ " tmp = fBx;\n"
+ " return tmp;\n"
+ " }\n"
+ " if (!((fAy < 0.0 && fBy > 0.0) || (fAy > 0.0 && fBy < 0.0)))\n"
+ " {\n"
+ " bConvError = true;\n"
+ " tmp = 0.0;\n"
+ " return tmp;\n"
+ " }\n"
+ " double fPx = fAx;\n"
+ " double fPy = fAy;\n"
+ " double fQx = fBx;\n"
+ " double fQy = fBy;\n"
+ " double fRx = fAx;\n"
+ " double fRy = fAy;\n"
+ " double fSx = 0.5 * (fAx + fBx);\n"
+ " bool bHasToInterpolate = true;\n"
+ " nCount = 0;\n"
+ " while ( nCount < 500 && fabs(fRy) > fYEps &&"
+ "(fBx-fAx) > fmax( fabs(fAx), fabs(fBx)) * fXEps )\n"
+ " {\n"
+ " if (bHasToInterpolate)\n"
+ " {\n"
+ " if (fPy!=fQy && fQy!=fRy && fRy!=fPy)\n"
+ " {\n"
+ " fSx = fPx * fRy * fQy *pow( (fRy-fPy),-1)"
+ " *pow( (fQy-fPy),-1)+fRx * fQy * fPy*pow( (fQy-fRy),-1) *"
+ "pow( (fPy-fRy),-1)+ fQx * fPy * fRy *pow( (fPy-fQy),-1)"
+ " *pow((fRy-fQy),-1);\n"
+ " bHasToInterpolate = (fAx < fSx) && (fSx < fBx);\n"
+ " }\n"
+ " else\n"
+ " bHasToInterpolate = false;\n"
+ " }\n"
+ " if(!bHasToInterpolate)\n"
+ " {\n"
+ " fSx = 0.5 * (fAx + fBx);\n"
+ " fPx = fAx; fPy = fAy;\n"
+ " fQx = fBx; fQy = fBy;\n"
+ " bHasToInterpolate = true;\n"
+ " }\n"
+ " fPx = fQx; fQx = fRx; fRx = fSx;\n"
+ " fPy = fQy; fQy = fRy;\n"
+ " fRy = arg0-GetFInvValue(fF1,fF2,fSx);\n"
+ " if ((fAy < 0.0 && fRy > 0.0) || (fAy > 0.0 && fRy < 0.0))\n"
+ " {\n"
+ " fBx = fRx; fBy = fRy;\n"
+ " }\n"
+ " else\n"
+ " {\n"
+ " fAx = fRx; fAy = fRy;\n"
+ " }\n"
+ " bHasToInterpolate = bHasToInterpolate && (fabs(fRy)"
+ " * 2.0 <= fabs(fQy));\n"
+ " ++nCount;\n"
+ " }\n"
+ " tmp = fRx;\n"
+ " return tmp;"
+ "}";
+}
+void OpFTest::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(fMachEpsDecl);decls.insert(fMaxGammaArgumentDecl);
+ decls.insert(lcl_getLanczosSumDecl);decls.insert(GetBetaDecl);
+ decls.insert(GetLogBetaDecl);decls.insert(GetBetaDistPDFDecl);
+ decls.insert(lcl_GetBetaHelperContFracDecl);decls.insert(GetBetaDistDecl);
+ decls.insert(GetFDistDecl);
+ funs.insert(lcl_getLanczosSum);funs.insert(GetBeta);
+ funs.insert(GetLogBeta);funs.insert(GetBetaDistPDF);
+ funs.insert(lcl_GetBetaHelperContFrac);funs.insert(GetBetaDist);
+ funs.insert(GetFDist);
+}
+void OpFTest::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ FormulaToken *pCur = vSubArguments[0]->GetFormulaToken();
+ assert(pCur);
+ const formula::DoubleVectorRefToken* pCurDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pCurDVR->GetRefRowSize();
+ FormulaToken *pCur1 = vSubArguments[1]->GetFormulaToken();
+ assert(pCur1);
+ const formula::DoubleVectorRefToken* pCurDVR1 =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur1);
+ size_t nCurWindowSize1 = pCurDVR1->GetRefRowSize();
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"( ";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double fSum1 = 0.0;\n";
+ ss << " double fSumSqr1 = 0.0;\n";
+ ss << " double fSum2 = 0.0;\n";
+ ss << " double fSumSqr2 = 0.0;\n";
+ ss << " int length0="<<nCurWindowSize;
+ ss << ";\n";
+ ss << " int length1= "<<nCurWindowSize1;
+ ss << ";\n";
+ ss << " double tmp = 0;\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCurSub = vSubArguments[i]->GetFormulaToken();
+ assert(pCurSub);
+ if (pCurSub->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCurSub);
+ ss << " for (int i = ";
+ ss << "0; i < "<< pDVR->GetRefRowSize() << "; i++){\n";
+ ss << " double arg"<<i<<" = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef(true);
+ ss << ";\n";
+ ss << " if(isnan(arg"<<i<<")||((gid0+i)>=";
+ ss << pDVR->GetArrayLength();
+ ss << "))\n";
+ ss << " {\n";
+ ss << " length"<<i<<"--;\n";
+ ss << " continue;\n";
+ ss << " }\n";
+ ss << " fSum"<<i+1<<" += arg"<<i<<";\n";
+ ss << " fSumSqr"<<i+1<<" += arg"<<i;
+ ss << " * arg"<<i<<";\n";
+ ss << " }\n";
+ }
+ else if (pCurSub->GetType() == formula::svSingleVectorRef)
+ {
+ ss << "return HUGE_VAL";
+ }
+ else if (pCurSub->GetType() == formula::svDouble)
+ {
+ ss << "return HUGE_VAL";
+ }
+ }
+ ss << " double fS1 = (fSumSqr1-fSum1*fSum1/length0)/(length0-1.0);\n"
+ " double fS2 = (fSumSqr2-fSum2*fSum2/length1)/(length1-1.0);\n"
+ " double fF, fF1, fF2;\n"
+ " if (fS1 > fS2)\n"
+ " {\n"
+ " fF = fS1/fS2;\n"
+ " fF1 = length0-1.0;\n"
+ " fF2 = length1-1.0;\n"
+ " }\n"
+ " else\n"
+ " {\n"
+ " fF = fS2/fS1;\n"
+ " fF1 = length1-1.0;\n"
+ " fF2 = length0-1.0;\n"
+ " }\n"
+ " tmp = 2.0*GetFDist(fF, fF1, fF2);\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+void OpB::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ //decls.insert(fBigInvDecl);decls.insert(fLogDblMaxDecl);
+ decls.insert(GetBinomDistPMFDecl);decls.insert(MinDecl);
+ decls.insert(fMachEpsDecl);decls.insert(fMaxGammaArgumentDecl);
+ decls.insert(GetBetaDistDecl);decls.insert(GetBetaDistPDFDecl);
+ decls.insert(lcl_GetBetaHelperContFracDecl);decls.insert(GetLogBetaDecl);
+ decls.insert(lcl_getLanczosSumDecl); decls.insert(GetBetaDecl);
+ funs.insert(GetBinomDistPMF);funs.insert(lcl_GetBinomDistRange);
+ funs.insert(GetBetaDist);funs.insert(GetBetaDistPDF);
+ funs.insert(lcl_GetBetaHelperContFrac);funs.insert(GetLogBeta);
+ funs.insert(lcl_getLanczosSum);funs.insert(GetBeta);
+}
+
+void OpB::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double min = 2.22507e-308;\n";
+ ss << " double tmp;\n";
+ ss << " double arg0,arg1,arg2,arg3;\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << "for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed()) {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++){\n";
+ } else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed()) {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+"<< nCurWindowSize << "; i++){\n";
+ } else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed()){
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < "<< nCurWindowSize << "; i++){\n ";
+ }
+ else {
+ ss << "0; i < "<< nCurWindowSize << "; i++){\n";
+ }
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " arg"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " arg"<<i<<"="<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " }\n";
+ ss << " else\n";
+ ss << " arg"<<i<<"= 0;\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " arg"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " arg"<<i<<"="<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ }
+ ss << " double rxs = floor(arg2);\n"
+ " double rxe = floor(arg3);\n"
+ " double rn = floor(arg0);\n"
+ " double rq = (0.5 - arg1) + 0.5;\n"
+ " bool bIsValidX = (0.0 <= rxs && rxs <= rxe && rxe <= rn);\n"
+ " if (bIsValidX && 0.0 < arg1 && arg1 < 1.0)\n"
+ " {\n"
+ " if (rxs == rxe)\n"
+ " tmp = GetBinomDistPMF(rxs, rn, arg1);\n"
+ " else\n"
+ " {\n"
+ " double fFactor = pow(rq, rn);\n"
+ " if (fFactor > min)\n"
+ " tmp ="
+ " lcl_GetBinomDistRange(rn, rxs, rxe, fFactor, arg1, rq);\n"
+ " else\n"
+ " {\n"
+ " fFactor = pow(arg1, rn);\n"
+ " if (fFactor > min)\n"
+ " {\n"
+ " tmp ="
+ "lcl_GetBinomDistRange(rn, rn - rxe, rn - rxs, fFactor, rq, arg1);\n"
+ " }\n"
+ " else\n"
+ " tmp ="
+ "GetBetaDist(rq, rn - rxe, rxe + 1.0)"
+ "- GetBetaDist(rq, rn - rxs + 1, rxs);\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ " else\n"
+ " {\n"
+ " if (bIsValidX)\n"
+ " {\n"
+ " if (arg1 == 0.0)\n"
+ " tmp = (rxs == 0.0 ? 1.0 : 0.0);\n"
+ " else if (arg1 == 1.0)\n"
+ " tmp = (rxe == rn ? 1.0 : 0.0);\n"
+ " else\n"
+ " {\n"
+ " tmp = DBL_MIN;\n"
+ " }\n"
+ " }\n"
+ " else\n"
+ " {\n"
+ " tmp = DBL_MIN;\n"
+ " }\n"
+ " }\n"
+ " return tmp;"
+ "}\n";
+}
+void OpBetaDist::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(fMachEpsDecl);decls.insert(fMaxGammaArgumentDecl);
+ decls.insert(GetBetaDistDecl);decls.insert(GetBetaDistPDFDecl);
+ decls.insert(lcl_GetBetaHelperContFracDecl);decls.insert(GetLogBetaDecl);
+ decls.insert(GetBetaDecl);decls.insert(lcl_getLanczosSumDecl);
+ funs.insert(GetBetaDist);funs.insert(GetBetaDistPDF);
+ funs.insert(lcl_GetBetaHelperContFrac);funs.insert(GetLogBeta);
+ funs.insert(GetBeta);funs.insert(lcl_getLanczosSum);
+}
+void OpPoisson::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(fHalfMachEpsDecl);
+ funs.insert("");
+ decls.insert(fMaxGammaArgumentDecl);
+ funs.insert("");
+ decls.insert(fBigInvDecl);
+ funs.insert("");
+ decls.insert(GetLogGammaDecl);
+ funs.insert(GetLogGamma);
+ decls.insert(lcl_GetLogGammaHelperDecl);
+ funs.insert(lcl_GetLogGammaHelper);
+ decls.insert(lcl_GetGammaHelperDecl);
+ funs.insert(lcl_GetGammaHelper);
+ decls.insert(lcl_getLanczosSumDecl);
+ funs.insert(lcl_getLanczosSum);
+ decls.insert(GetUpRegIGammaDecl);
+ funs.insert(GetUpRegIGamma);
+ decls.insert(GetGammaContFractionDecl);
+ funs.insert(GetGammaContFraction);
+ decls.insert(GetGammaSeriesDecl);
+ funs.insert(GetGammaSeries);
+}
+void OpPoisson::GenSlidingWindowFunction(
+ std::stringstream &ss,const std::string &sSymName,
+ SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " double x,lambda,tmp,tmp0,tmp1,tmp2;\n";
+ ss << " int bCumulative;\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss <<"\n ";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << "if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << "{\n";
+ }
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " tmp"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " tmp"<<i<<"=\n";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n}\n";
+ }
+ else
+ {
+ ss << "tmp"<<i<<"="<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ }
+ }
+ ss << " x = floor(tmp0);\n";
+ ss << " lambda = tmp1;\n";
+ ss << " bCumulative = tmp2;\n ";
+ ss << " if (!bCumulative)\n";
+ ss << " {\n";
+ ss << " if(lambda == 0.0)\n";
+ ss << " {\n";
+ ss << " return 0;\n";
+ ss << " }\n";
+ ss << " else\n";
+ ss << " {\n";
+ ss << " if (lambda >712)\n";
+ ss << " {\n";
+ ss << " tmp = (exp(x*log(lambda)-lambda-GetLogGamma(x+1.0)));\n";
+ ss << " return tmp;\n";
+ ss << " }\n";
+ ss << " else\n";
+ ss << " {\n";
+ ss << " double fPoissonVar = 1.0;\n";
+ ss << " for ( int f = 0; f < x; ++f )\n";
+ ss << " fPoissonVar *= lambda * pow(( (double)f + 1.0 ),-1);\n";
+ ss << " tmp = ( fPoissonVar * exp( -lambda ) );\n";
+ ss << " return tmp;\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << " } \n";
+ ss << " else\n";
+ ss << " {\n";
+ ss << " if (lambda == 0.0)\n";
+ ss << " {\n";
+ ss << " return 1;\n";
+ ss << " }\n";
+ ss << " else\n";
+ ss << " {\n";
+ ss << " if (lambda > 712 )\n";
+ ss << " {\n";
+ ss << " tmp = (GetUpRegIGamma(x+1.0,lambda));\n";
+ ss << " return tmp;\n";
+ ss << " }\n";
+ ss << " else\n";
+ ss << " {\n";
+ ss << " if (x >= 936.0)\n";
+ ss << " {\n";
+ ss << " return 1;\n";
+ ss << " }\n";
+ ss << " else\n";
+ ss << " {\n";
+ ss << " double fSummand = exp(-lambda);\n";
+ ss << " double fSum = fSummand;\n";
+ ss << " int nEnd = (int) (x + 0.5);\n";
+ ss << " for (int i = 1; i <= nEnd; i++)\n";
+ ss << " {\n";
+ ss << " fSummand = (fSummand*lambda)*pow((double)i,-1);\n";
+ ss << " fSum += fSummand;\n";
+ ss << " }\n";
+ ss << " tmp = fSum;\n";
+ ss << " return tmp;\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << " }\n";
+ ss << "}\n";
+}
+void OpCovar::GenSlidingWindowFunction(std::stringstream& ss,
+ const std::string &sSymName, SubArguments& vSubArguments)
+{
+ CHECK_PARAMETER_COUNT(2,2);
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double vSum = 0.0;\n";
+ ss << " double vSum0 = 0.0;\n";
+ ss << " double vSum1 = 0.0;\n";
+ ss << " double vMean0 = 0.0;\n";
+ ss << " double vMean1 = 0.0;\n";
+ ss << " double arg0 = 0.0;\n";
+ ss << " double arg1 = 0.0;\n";
+ FormulaToken* pCurX = vSubArguments[0]->GetFormulaToken();
+ FormulaToken* pCurY = vSubArguments[1]->GetFormulaToken();
+ if ((pCurX->GetType() == formula::svDoubleVectorRef)&&
+ (pCurY->GetType() == formula::svDoubleVectorRef)){
+ ss << " int cnt = 0;\n";
+ const formula::DoubleVectorRefToken* pCurDVRX =
+ static_cast<const formula::DoubleVectorRefToken* >(pCurX);
+ const formula::DoubleVectorRefToken* pCurDVRY =
+ static_cast<const formula::DoubleVectorRefToken* >(pCurY);
+ size_t nCurWindowSizeX = pCurDVRX->GetRefRowSize();
+ size_t nCurWindowSizeY = pCurDVRY->GetRefRowSize();
+ if(nCurWindowSizeX != nCurWindowSizeY)
+ {
+ throw Unhandled(__FILE__, __LINE__);
+ }
+
+ ss << " for( ";
+ if (!pCurDVRX->IsStartFixed() && pCurDVRX->IsEndFixed()) {
+ ss << "int i = gid0; i < " << nCurWindowSizeX;
+ ss << " && i < " << pCurDVRX->GetArrayLength() << " && i < ";
+ ss << pCurDVRY->GetArrayLength() << "; i++){\n";
+ ss << " if(isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ") ||";
+ ss << " isnan("<< vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss << ")) {\n";
+ ss << " arg0 = 0.0;\n";
+ ss << " arg1 = 0.0;\n";
+ ss << " --cnt;\n";
+ ss << " }\n";
+ ss << "else{\n";
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " arg1 = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ";\n";
+ ss << "}\n";
+ ss << " ++cnt;\n";
+ ss << " vSum0 += arg0;\n";
+ ss << " vSum1 += arg1;\n";
+ ss << " }\n";
+ }
+ else if (pCurDVRX->IsStartFixed() && !pCurDVRX->IsEndFixed()) {
+ ss << "int i = 0; i < gid0 + " << nCurWindowSizeX << " && ";
+ ss << " i < " << pCurDVRX->GetArrayLength() << " && ";
+ ss << " i < " << pCurDVRY->GetArrayLength() << "; i++) {\n";
+ ss << " if(isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ") ||";
+ ss << " isnan(" << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss << ")) {\n";
+ ss << " arg0 = 0.0;\n";
+ ss << " arg1 = 0.0;\n";
+ ss << " --cnt;\n";
+ ss << " }\n";
+ ss << "else{\n";
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " arg1 = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ";}\n";
+ ss << " ++cnt;\n";
+ ss << " vSum0 += arg0;\n";
+ ss << " vSum1 += arg1;\n";
+ ss << " }\n";
+ }
+ else if (pCurDVRX->IsStartFixed() && pCurDVRX->IsEndFixed()) {
+ ss << "int i = 0; i < " << nCurWindowSizeX << " && i < ";
+ ss << pCurDVRX->GetArrayLength() << " && i < ";
+ ss << pCurDVRY->GetArrayLength() << "; i++) {\n";
+ ss << " if(isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ") ||";
+ ss << " isnan(" << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss << ")) {\n";
+ ss << " arg0 = 0.0;\n";
+ ss << " arg1 = 0.0;\n";
+ ss << " --cnt;\n";
+ ss << " }\n";
+ ss << "else{\n";
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " arg1 = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ";}\n";
+ ss << " ++cnt;\n";
+ ss << " vSum0 += arg0;\n";
+ ss << " vSum1 += arg1;\n";
+ ss << " }\n";
+ }
+ else {
+ ss << "int i = 0; i < " << nCurWindowSizeX << " && ";
+ ss << " i + gid0 < " << pCurDVRX->GetArrayLength();
+ ss << " && i + gid0 < " << pCurDVRY->GetArrayLength();
+ ss << "; i++) {\n";
+ ss << "if ((isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ")) || ";
+ ss << "(isnan("<< vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss << "))) {\n";
+ ss << " arg0 = 0.0;\n";
+ ss << " arg1 = 0.0;\n";
+ ss << " --cnt;\n";
+ ss << " }\n";
+ ss << " else {\n";
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " arg1 = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " }\n";
+ ss << " ++cnt;\n";
+ ss << " vSum0 += arg0;\n";
+ ss << " vSum1 += arg1;\n";
+ ss << " }\n";
+ }
+ ss << " if(cnt < 1) {\n";
+ ss << " return CreateDoubleError(NoValue);\n";
+ ss << " }\n";
+ ss << " else {\n";
+ ss << " vMean0 = vSum0 / cnt;\n";
+ ss << " vMean1 = vSum1 / cnt;\n";
+ ss << " for(";
+ if (!pCurDVRX->IsStartFixed() && pCurDVRX->IsEndFixed()) {
+ ss << "int i = gid0; i < " << nCurWindowSizeX;
+ ss << " && i < " << pCurDVRX->GetArrayLength() << " && i < ";
+ ss << pCurDVRY->GetArrayLength() << "; i++){\n";
+ ss << " if(isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ") ||";
+ ss << " isnan(" << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss << ")){\n";
+ ss << " arg0 = vMean0;\n";
+ ss << " arg1 = vMean1;\n";
+ ss << " }\n";
+ ss << " else{\n";
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " arg1 = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ";}\n";
+ ss << " vSum += (arg0 - vMean0) * (arg1 - vMean1);\n";
+ ss << " }\n";
+ }
+ else if (pCurDVRX->IsStartFixed() && !pCurDVRX->IsEndFixed()) {
+ ss << "int i = 0; i < gid0 + " << nCurWindowSizeX << " && ";
+ ss << " i < " << pCurDVRX->GetArrayLength() << " && ";
+ ss << " i < " << pCurDVRY->GetArrayLength() << "; i++) {\n";
+ ss << " if(isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ") || ";
+ ss << "isnan(" << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss << ")) {\n";
+ ss << " arg0 = vMean0;\n";
+ ss << " arg1 = vMean1;\n";
+ ss << " }\n";
+ ss << "else{\n";
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " arg1 = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ";}\n";
+ ss << " vSum += (arg0 - vMean0) * (arg1 - vMean1);\n";
+ ss << " }\n";
+ }
+ else if (pCurDVRX->IsStartFixed() && pCurDVRX->IsEndFixed()) {
+ ss << "int i = 0; i < " << nCurWindowSizeX << " && i < ";
+ ss << pCurDVRX->GetArrayLength() << " && i < ";
+ ss << pCurDVRY->GetArrayLength() << "; i++) {\n";
+ ss << " if(isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ") || ";
+ ss << "isnan(" << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss << ")) {\n";
+ ss << " arg0 = vMean0;\n";
+ ss << " arg1 = vMean1;\n";
+ ss << " }\n";
+ ss << "else{\n";
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " arg1 = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ";}\n";
+ ss << " vSum += (arg0 - vMean0) * (arg1 - vMean1);\n";
+ ss << " }\n";
+ }
+ else {
+ ss << "int i = 0; i < " << nCurWindowSizeX << " && ";
+ ss << " i + gid0 < " << pCurDVRX->GetArrayLength();
+ ss << " && i + gid0 < " << pCurDVRX->GetArrayLength();
+ ss << "; i++) {\n";
+ ss << "if((isnan(";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ")) || ";
+ ss << "(isnan(" << vSubArguments[1]->GenSlidingWindowDeclRef();
+ ss << "))) {\n";
+ ss << " arg0 = vMean0;\n";
+ ss << " arg1 = vMean1;\n";
+ ss << " }\n";
+ ss << " else{\n";
+ ss << " arg0 = ";
+ ss << vSubArguments[0]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " arg1 = ";
+ ss << vSubArguments[1]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " }\n";
+ ss << " vSum += (arg0 - vMean0) * (arg1 - vMean1);\n";
+ ss << " }\n";
+ }
+ ss << " return vSum / cnt;\n";
+ ss << " }\n";
+ ss << "}";
+
+ }
+ else {
+ ss << " int cnt0 = 0,cnt1 = 0;\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken* pCur = vSubArguments[i]->GetFormulaToken();
+ if (pCur->GetType() == formula::svSingleVectorRef){
+ const formula::SingleVectorRefToken* pTVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if(isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ") || gid0 >= " << pTVR->GetArrayLength() << ")\n";
+ ss << " arg" << i << " = 0;\n else\n";
+ ss << " arg" << i << " = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " cnt" << i << "++;\n";
+ ss << " vSum" << i << " += arg" << i << ";\n";
+ }
+ else if (pCur->GetType() == formula::svDouble){
+ ss << " if(isnan ( ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << "))\n";
+ ss << " arg" << i << " = 0;\n else\n";
+ ss << " arg" << i << " = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " cnt" << i << "++;\n";
+ ss << " vSum" << i << " += arg" << i << ";\n";
+ }
+ else {
+ ss << " arg" << i << " = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " cnt" << i << "++;\n";
+ ss << " vSum" << i << " += arg" << i << ";\n";
+ }
+ }
+ ss << " vMean0 = vSum0 / cnt0;\n";
+ ss << " vMean1 = vSum0 / cnt1;\n";
+ for(size_t i = 0; i < vSubArguments.size(); i++ ) {
+ FormulaToken* pCur = vSubArguments[i]->GetFormulaToken();
+ if (pCur->GetType() == formula::svSingleVectorRef) {
+ const formula::SingleVectorRefToken* pTVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if(isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ") || gid0 >= " << pTVR->GetArrayLength() << ")\n";
+ ss << " arg" << i << " = vMean" << i << ";\n";
+ ss << " else\n";
+ ss << " arg" << i << " = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ }
+ else if (pCur->GetType() == formula::svDouble) {
+ ss << " arg" << i << " = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if(isnan(arg" << i << "))\n";
+ ss << " arg" << i << " = vMean" << i << ";\n";
+ }
+ else {
+ ss << " arg" << i << " = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ }
+ }
+ ss << " vSum += (arg0 - vMean0) * ( arg1 - vMean1 );\n";
+ ss << " return vSum / cnt0;\n";
+ ss << "}";
+ }
+}
+void OpBetaDist::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double tmp;\n";
+ ss << " double arg0,arg1,arg2,arg3,arg4,arg5;\n";
+
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << "for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed()) {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++){\n";
+ } else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed()) {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+"<< nCurWindowSize << "; i++){\n";
+ } else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed()){
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < "<< nCurWindowSize << "; i++){\n ";
+ }
+ else {
+ ss << "0; i < "<< nCurWindowSize << "; i++){\n";
+ }
+
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " arg"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " arg"<<i<<"="<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " }\n";
+ ss << " else\n";
+ ss << " arg"<<i<<"= 0;\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " arg"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " arg"<<i<<"="<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ }
+ ss << " double fScale = arg4 - arg3;\n"
+ " if (fScale <= 0.0 || arg1 <= 0.0 || arg2 <= 0.0)\n"
+ " {\n"
+ " tmp = DBL_MIN;\n"
+ " return tmp;\n"
+ " }\n"
+ " if (arg5)\n"
+ " {\n"
+ " if (arg0< arg3)\n"
+ " {\n"
+ " tmp = 0.0;\n"
+ " return tmp;\n"
+ " }\n"
+ " if (arg0 > arg4)\n"
+ " {\n"
+ " tmp = 1.0;\n"
+ " return tmp;\n"
+ " }\n"
+ " arg0 = (arg0-arg3)*pow(fScale,-1);\n"
+ " tmp = GetBetaDist(arg0, arg1, arg2);\n"
+ " }\n"
+ " else\n"
+ " {\n"
+ " if (arg0 < arg3 || arg0 > arg4 )\n"
+ " {\n"
+ " tmp = 0.0;\n"
+ " return tmp;\n"
+ " }\n"
+ " arg0 = (arg0 - arg3)*pow(fScale,-1);\n"
+ " tmp = GetBetaDistPDF(arg0, arg1, arg2)*pow(fScale,-1);\n"
+ " }\n";
+ ss << " return tmp;\n";
+ ss << "}\n";
+}
+void OpBetainv::BinInlineFun(std::set<std::string>& decls,
+ std::set<std::string>& funs)
+{
+ decls.insert(fMachEpsDecl);
+ funs.insert("");
+ decls.insert(fMaxGammaArgumentDecl);
+ funs.insert("");
+ decls.insert(lcl_IterateInverseBetaInvDecl);
+ funs.insert(lcl_IterateInverseBetaInv);
+ decls.insert(GetBetaDistDecl);
+ funs.insert(GetBetaDist);
+ decls.insert(lcl_HasChangeOfSignDecl);
+ funs.insert(lcl_HasChangeOfSign);
+ decls.insert(lcl_HasChangeOfSignDecl);
+ funs.insert(lcl_HasChangeOfSign);
+ decls.insert(lcl_HasChangeOfSignDecl);
+ funs.insert(lcl_HasChangeOfSign);
+ decls.insert(lcl_GetBetaHelperContFracDecl);
+ funs.insert(lcl_GetBetaHelperContFrac);
+ decls.insert(GetBetaDistPDFDecl);
+ funs.insert(GetBetaDistPDF);
+ decls.insert(GetLogBetaDecl);
+ funs.insert(GetLogBeta);
+ decls.insert(GetBetaDecl);
+ funs.insert(GetBeta);
+ decls.insert(lcl_getLanczosSumDecl);
+ funs.insert(lcl_getLanczosSum);
+}
+void OpBetainv::GenSlidingWindowFunction(
+ std::stringstream &ss,const std::string &sSymName,
+ SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " double tmp0,tmp1,tmp2,tmp3,tmp4;\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss <<"\n ";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << "for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed()) {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++){\n";
+ } else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed()) {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+"<< nCurWindowSize << "; i++){\n";
+ } else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed()){
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < "<< nCurWindowSize << "; i++){\n";
+ }
+ else {
+ ss << "0; i < "<< nCurWindowSize << "; i++){\n";
+ }
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << "if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << "{\n";
+ }
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " tmp"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " tmp"<<i<<"=\n";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n}\n";
+ }
+ else
+ {
+ ss << "tmp"<<i<<"="<<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss <<";\n";
+ }
+ }
+ ss << " if (tmp0 < 0.0 || tmp0 >= 1.0 ||";
+ ss << "tmp3 == tmp4 || tmp1 <= 0.0 || tmp2 <= 0.0)\n";
+ ss << " {\n";
+ ss << " return DBL_MIN;\n";
+ ss << " }\n";
+ ss << " if (tmp0 == 0.0)\n";
+ ss << " return 0.0;\n";
+ ss << " else\n";
+ ss << " {";
+ ss << " bool bConvError;";
+ ss << " double fVal = lcl_IterateInverseBetaInv";
+ ss << "(tmp0, tmp1, tmp2, 0.0, 1.0, &bConvError);\n";
+ ss << " if(bConvError)\n";
+ ss << " return DBL_MIN;\n";
+ ss << " else\n";
+ ss << " return (tmp3 + fVal*(tmp4 - tmp3));\n";
+ ss << " }";
+ ss << "}\n";
+}
+void OpDevSq::GenSlidingWindowFunction(std::stringstream& ss,
+ const std::string &sSymName, SubArguments& vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double vSum = 0.0;\n";
+ ss << " double vMean = 0.0;\n";
+ ss << " int cnt = 0;\n";
+ for(size_t i = 0; i < vSubArguments.size(); i++ )
+ {
+ ss << " double arg" << i << " = 0.0;\n";
+ FormulaToken* pCur = vSubArguments[i]->GetFormulaToken();
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pCurDVR =
+ static_cast<const formula::DoubleVectorRefToken* >(pCur);
+ size_t nCurWindowSize = pCurDVR->GetRefRowSize();
+ ss << " for(int i = ";
+ if (!pCurDVR->IsStartFixed() && pCurDVR->IsEndFixed()) {
+ ss << "gid0; i < " << nCurWindowSize << "; i++) {\n";
+ ss << " arg" << i << " = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if(isnan(arg" << i << ") || (i >= ";
+ ss << pCurDVR->GetArrayLength() << ")) {\n";
+ ss << " arg" << i << " = 0.0;\n";
+ ss << " --cnt;\n";
+ ss << " }\n";
+ ss << " ++cnt;\n";
+ ss << " vSum += arg" << i << ";\n";
+ ss << " }\n";
+ } else if (pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed()) {
+ ss << "0; i < gid0 + " << nCurWindowSize << "; i++) {\n";
+ ss << " arg" << i << " = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if(isnan(arg" << i << ") || (i >= ";
+ ss << pCurDVR->GetArrayLength() << ")) {\n";
+ ss << " arg" << i << " = 0.0;\n";
+ ss << " --cnt;\n";
+ ss << " }\n";
+ ss << " ++cnt;\n";
+ ss << " vSum += arg" << i << ";\n";
+ ss << " }\n";
+ } else if (pCurDVR->IsStartFixed() && pCurDVR->IsEndFixed()) {
+ ss << "0; i < " << nCurWindowSize << "; i++) {\n";
+ ss << " arg" << i << " = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if(isnan(arg" << i << ") || (i >= ";
+ ss << pCurDVR->GetArrayLength() << ")) {\n";
+ ss << " arg" << i << " = 0.0;\n";
+ ss << " --cnt;\n";
+ ss << " }\n";
+ ss << " ++cnt;\n";
+ ss << " vSum += arg" << i << ";\n";
+ ss << " }\n";
+ } else {
+ ss << "0; i < " << nCurWindowSize << "; i++) {\n";
+ ss << " arg" << i << " = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if(isnan(arg" << i << ") || (i + gid0 >= ";
+ ss << pCurDVR->GetArrayLength() << ")) {\n";
+ ss << " arg" << i << " = 0.0;\n";
+ ss << " --cnt;\n";
+ ss << " }\n";
+ ss << " ++cnt;\n";
+ ss << " vSum += arg" << i << ";\n";
+ ss << " }\n";
+ }
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pTVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if(isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ") || gid0 >= " << pTVR->GetArrayLength() << ")\n";
+ ss << " arg" << i << " = 0;\n else\n";
+ ss << " arg" << i << " = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " cnt++;\n";
+ ss << " vSum += arg" << i << ";\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << " if(isnan ( ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << "))\n";
+ ss << " arg" << i << " = 0;\n else\n";
+ ss << " arg" << i << " = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " cnt++;\n";
+ ss << " vSum += arg" << i << ";\n";
+ }
+ else
+ {
+ ss << " arg" << i << " = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " cnt++;\n";
+ ss << " vSum += arg" << i << ";\n";
+ }
+ }
+ ss << " vMean = vSum / cnt;\n";
+ ss << " vSum = 0.0;\n";
+ for(size_t k = 0; k < vSubArguments.size(); k++ )
+ {
+ FormulaToken* pCur = vSubArguments[k]->GetFormulaToken();
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pCurDVR =
+ static_cast<const formula::DoubleVectorRefToken* >(pCur);
+ size_t nCurWindowSize = pCurDVR->GetRefRowSize();
+ ss << " for(int i = ";
+ if (!pCurDVR->IsStartFixed() && pCurDVR->IsEndFixed()) {
+ ss << "gid0; i < " << nCurWindowSize << "; i++) {\n";
+ ss << " arg" << k << " = ";
+ ss << vSubArguments[k]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if(isnan( arg" << k << " ) || (i >= ";
+ ss << pCurDVR->GetArrayLength() << ")) {\n";
+ ss << " arg" << k << " = vXMean;\n";
+ ss << " }\n";
+ ss << " vSum += pow( arg" << k << " - vMean, 2 );\n";
+ ss << " }\n";
+ } else if (pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed()) {
+ ss << "0; i < gid0 + " << nCurWindowSize << "; i++) {\n";
+ ss << " arg" << k << " = ";
+ ss << vSubArguments[k]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if(isnan( arg" << k << ") || (i >= ";
+ ss << pCurDVR->GetArrayLength() << ")) {\n";
+ ss << " arg" << k << " = vMean;\n";
+ ss << " }\n";
+ ss << " vSum += pow( arg" << k << " - vMean, 2 );\n";
+ ss << " }\n";
+ } else if (pCurDVR->IsStartFixed() && pCurDVR->IsEndFixed()) {
+ ss << "0; i < " << nCurWindowSize << "; i++) {\n";
+ ss << " arg" << k << " = ";
+ ss << vSubArguments[k]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if(isnan(arg" << k << ") || (i >= ";
+ ss << pCurDVR->GetArrayLength() << ")) {\n";
+ ss << " arg" << k << " = vMean;\n";
+ ss << " }\n";
+ ss << " vSum += pow( arg" << k << " - vMean, 2 );\n";
+ ss << " }\n";
+ } else {
+ ss << "0; i < " << nCurWindowSize << "; i++) {\n";
+ ss << " arg" << k << " = ";
+ ss << vSubArguments[k]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if(isnan(arg" << k << ") || (i + gid0 >= ";
+ ss << pCurDVR->GetArrayLength() << ")) {\n";
+ ss << " arg" << k << " = vMean;\n";
+ ss << " }\n";
+ ss << " vSum += pow( arg" << k << " - vMean, 2 );\n";
+ ss << " }\n";
+ }
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pTVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if(isnan(";
+ ss << vSubArguments[k]->GenSlidingWindowDeclRef();
+ ss << ") || gid0 >= " << pTVR->GetArrayLength() << ")\n";
+ ss << " arg" << k << " = vMean;\n else\n";
+ ss << " arg" << k << " = ";
+ ss << vSubArguments[k]->GenSlidingWindowDeclRef()<<";\n";
+ ss << " vSum += pow( arg" << k << " - vMean, 2 );\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << " arg" << k << " = ";
+ ss << vSubArguments[k]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if(isnan(arg" << k << "))\n";
+ ss << " arg" << k << " = vMean;\n";
+ ss << " vSum += pow( arg" << k << " - vMean, 2 );\n";
+ }
+ else
+ {
+ ss << " arg" << k << " = ";
+ ss << vSubArguments[k]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " vSum += pow( arg" << k << " - vMean, 2 );\n";
+ }
+ }
+ ss << " return vSum;\n";
+ ss << "}";
+}
+void OpHypGeomDist::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ") {\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double arg0,arg1,arg2,arg3;\n";
+
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << "for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed()) {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++){\n";
+ } else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed()) {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+"<< nCurWindowSize << "; i++){\n";
+ } else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed()){
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < "<< nCurWindowSize << "; i++){\n ";
+ }
+ else {
+ ss << "0; i < "<< nCurWindowSize << "; i++){\n";
+ }
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " arg"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " arg"<<i<<"=";
+ ss <<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " }\n";
+ ss << " else\n";
+ ss << " arg"<<i<<"= 0;\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << " if (isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " arg"<<i<<"= 0;\n";
+ ss << " else\n";
+ ss << " arg"<<i<<"=";
+ ss <<vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ }
+ ss << " double N1=floor(arg3);\n"
+ " double M1=floor(arg2);\n"
+ " double n1=floor(arg1);\n"
+ " double x1=floor(arg0);\n"
+ " double num[9];\n"
+ " double PI = 3.1415926535897932384626433832795;\n"
+ " double tmp;\n"
+ " if( (x1 < 0.0) || (n1 < x1) || (M1 < x1) || (N1 < n1) ||"
+ "(N1 < M1) || (x1 < n1 - N1 + M1) )\n"
+ " {\n"
+ " tmp = DBL_MIN;\n"
+ " return tmp;\n"
+ " }\n"
+ " num[0]=M1;\n"
+ " num[1]=x1;\n"
+ " num[2]=M1-x1;\n"
+ " num[3]=N1-M1;\n"
+ " num[4]=n1-x1;\n"
+ " num[5]=N1-M1-n1+x1;\n"
+ " num[6]=N1;\n"
+ " num[7]=n1;\n"
+ " num[8]=N1-n1;\n"
+ " for(int i=0;i<9;i++)\n"
+ " {\n"
+ " if(num[i]<171)\n"
+ " {\n"
+ " if(num[i]==0)\n"
+ " num[i]=0;\n"
+ " else\n"
+ " num[i]=log(tgamma(num[i])*num[i]);\n"
+ " }\n"
+ " else\n"
+ " num[i]=0.5*log(2.0*PI)+(num[i]+0.5)*log(num[i])-num[i]+"
+ "(1.0*pow(12.0*num[i],-1)-1.0*pow(360*pow(num[i],3),-1));\n"
+ " }\n";
+ ss << " tmp=pow(M_E,(num[0]+num[3]+num[7]+num[8]";
+ ss << "-num[1]-num[2]-num[4]-num[5]-num[6]));\n";
+ ss << " return tmp;\n";
+ ss << "}\n";
+}
+
+namespace {
+
+enum MixDoubleString
+{
+ svDoubleVectorRefDoubleString,
+ svDoubleVectorRefDouble,
+ svDoubleVectorRefString,
+ svDoubleVectorRefNULL,
+ svSingleVectorRefDoubleString,
+ svSingleVectorRefDouble,
+ svSingleVectorRefString,
+ svSingleVectorRefNULL,
+ svDoubleDouble
+};
+
+}
+
+void OpMinA::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &vSubArguments)
+{
+ int isMixed = 0;
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double tmp0 = 1.79769e+308;\n";
+ ss <<"\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ if(pDVR->GetArrays()[0].mpNumericArray
+ && pDVR->GetArrays()[0].mpStringArray)
+ isMixed = svDoubleVectorRefDoubleString;
+ else if(pDVR->GetArrays()[0].mpNumericArray)
+ isMixed = svDoubleVectorRefDouble;
+ else if(pDVR->GetArrays()[0].mpStringArray)
+ isMixed = svDoubleVectorRefString;
+ else
+ isMixed = svDoubleVectorRefNULL;
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed()) {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++){\n";
+ } else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed()) {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+"<< nCurWindowSize << "; i++){\n";
+ } else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed()){
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < "<< nCurWindowSize << "; i++){\n";
+ }
+ else {
+ ss << "0; i < "<< nCurWindowSize << "; i++){\n";
+ }
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+
+ if(pSVR->GetArray().mpNumericArray
+ && pSVR->GetArray().mpStringArray)
+ isMixed = svSingleVectorRefDoubleString;
+ else if(pSVR->GetArray().mpNumericArray)
+ isMixed = svSingleVectorRefDouble;
+ else if(pSVR->GetArray().mpStringArray)
+ isMixed = svSingleVectorRefString;
+ else
+ isMixed = svSingleVectorRefNULL;
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << " {\n";
+ isMixed = svDoubleDouble;
+ }
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ if(isMixed == svDoubleVectorRefDoubleString
+ || isMixed == svSingleVectorRefDoubleString)
+ {
+ ss << " if (!isnan(";
+ ss << vSubArguments[i]->GenDoubleSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " tmp0 = tmp0 > ";
+ ss << vSubArguments[i]->GenDoubleSlidingWindowDeclRef();
+ ss << " ? ";
+ ss << vSubArguments[i]->GenDoubleSlidingWindowDeclRef();
+ ss << " : tmp0;\n";
+ ss << " else if(isnan(";
+ ss << vSubArguments[i]->GenDoubleSlidingWindowDeclRef();
+ ss << ") && ";
+ ss << vSubArguments[i]->GenStringSlidingWindowDeclRef();
+ ss << " != 0)\n";
+ ss << " tmp0 = tmp0 > 0.0 ? 0.0 : tmp0;\n";
+ ss << " }\n";
+ }
+ else if(isMixed == svDoubleVectorRefDouble
+ || isMixed == svSingleVectorRefDouble)
+ {
+ ss << " if (!isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " tmp0 = tmp0 > ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " ? " << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " : tmp0;";
+ ss <<"\n }\n";
+ }
+ else if(isMixed == svDoubleVectorRefString)
+ {
+ ss << " if(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " == 0)\n continue;\n";
+ ss << " tmp0 = tmp0 > 0.0 ? 0.0 : tmp0;\n";
+ ss << " }\n";
+ }
+ else if(isMixed == svSingleVectorRefString)
+ {
+ ss << " if(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " != 0)\n";
+ ss << " tmp0 = tmp0 > 0.0 ? 0.0 : tmp0;\n";
+ ss << " }\n";
+ }
+ else if(isMixed == svDoubleDouble)
+ {
+ ss << " tmp0 = tmp0 > ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " ? " << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " : tmp0;\n }\n";
+ }
+ else
+ {
+ ss << " }\n";
+ }
+ }
+ else
+ {
+ ss << " tmp0 = tmp0 > ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " ? " << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " : tmp0;";
+ ss <<"\n }\n";
+ }
+ }
+ ss << " return tmp0 == 1.79769e+308 ? 0.0 : tmp0;\n";
+ ss << "}\n";
+}
+void OpCountA::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &
+vSubArguments)
+{
+ int isMixed = 0;
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double nCount = 0.0;\n";
+
+ ss <<"\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ if(pDVR->GetArrays()[0].mpNumericArray
+ && pDVR->GetArrays()[0].mpStringArray)
+ isMixed = svDoubleVectorRefDoubleString;
+ else if(pDVR->GetArrays()[0].mpNumericArray)
+ isMixed = svDoubleVectorRefDouble;
+ else if(pDVR->GetArrays()[0].mpStringArray)
+ isMixed = svDoubleVectorRefString;
+ else
+ isMixed = svDoubleVectorRefNULL;
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed()) {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++){\n";
+ } else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed()) {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+"<< nCurWindowSize << "; i++){\n";
+ } else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed()){
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < "<< nCurWindowSize << "; i++){\n";
+ }
+ else {
+ ss << "0; i < "<< nCurWindowSize << "; i++){\n";
+ }
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+
+ if(pSVR->GetArray().mpNumericArray
+ && pSVR->GetArray().mpStringArray)
+ isMixed = svSingleVectorRefDoubleString;
+ else if(pSVR->GetArray().mpNumericArray)
+ isMixed = svSingleVectorRefDouble;
+ else if(pSVR->GetArray().mpStringArray)
+ isMixed = svSingleVectorRefString;
+ else
+ isMixed = svSingleVectorRefNULL;
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << " {\n";
+ isMixed = svDoubleDouble;
+ }
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ if(isMixed == svDoubleVectorRefDoubleString
+ || isMixed == svSingleVectorRefDoubleString)
+ {
+ ss << " if (!isnan(";
+ ss << vSubArguments[i]->GenDoubleSlidingWindowDeclRef();
+ ss << ")){\n";
+ ss << " nCount+=1.0;\n";
+ ss << " }\n";
+ ss << " else if(isnan(";
+ ss << vSubArguments[i]->GenDoubleSlidingWindowDeclRef();
+ ss << ") && ";
+ ss<< vSubArguments[i]->GenStringSlidingWindowDeclRef();
+ ss << " != 0)\n";
+ ss << " nCount+=1.0;\n";
+ ss << " }\n";
+ }
+ else if(isMixed == svDoubleVectorRefDouble
+ || isMixed == svSingleVectorRefDouble)
+ {
+ ss << " if (!isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ")){\n";
+ ss << " nCount+=1.0;\n";
+ ss <<"}\n }\n";
+ }
+ else if(isMixed == svDoubleVectorRefString)
+ {
+ ss << " if (!isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " nCount+=1.0;\n";
+ ss <<"\n }\n";
+ }
+ else if(isMixed == svSingleVectorRefString)
+ {
+ ss << " if(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " != 0)\n";
+ ss << " nCount+=1.0;\n";
+ ss << " }\n";
+ }
+ else if(isMixed == svDoubleDouble)
+ {
+ ss << " nCount+=1.0;\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " }\n";
+ }
+ }
+ else
+ {
+ ss << " nCount+=1.0;\n";
+ ss << " }\n";
+ }
+ }
+ ss << " return nCount;\n";
+ ss << "}\n";
+}
+void OpMaxA::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &
+vSubArguments)
+{
+ int isMixed = 0;
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double tmp0 = 2.22507e-308;\n";
+
+ ss <<"\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ if(pDVR->GetArrays()[0].mpNumericArray
+ && pDVR->GetArrays()[0].mpStringArray)
+ isMixed = svDoubleVectorRefDoubleString;
+ else if(pDVR->GetArrays()[0].mpNumericArray)
+ isMixed = svDoubleVectorRefDouble;
+ else if(pDVR->GetArrays()[0].mpStringArray)
+ isMixed = svDoubleVectorRefString;
+ else
+ isMixed = svDoubleVectorRefNULL;
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed()) {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++){\n";
+ } else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed()) {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+"<< nCurWindowSize << "; i++){\n";
+ } else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed()){
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < "<< nCurWindowSize << "; i++){\n";
+ }
+ else {
+ ss << "0; i < "<< nCurWindowSize << "; i++){\n";
+ }
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+
+ if(pSVR->GetArray().mpNumericArray
+ && pSVR->GetArray().mpStringArray)
+ isMixed = svSingleVectorRefDoubleString;
+ else if(pSVR->GetArray().mpNumericArray)
+ isMixed = svSingleVectorRefDouble;
+ else if(pSVR->GetArray().mpStringArray)
+ isMixed = svSingleVectorRefString;
+ else
+ isMixed = svSingleVectorRefNULL;
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << " {\n";
+ isMixed = svDoubleDouble;
+ }
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ if(isMixed == svDoubleVectorRefDoubleString
+ || isMixed == svSingleVectorRefDoubleString)
+ {
+ ss << " if (!isnan(";
+ ss << vSubArguments[i]->GenDoubleSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " tmp0 = tmp0 < ";
+ ss << vSubArguments[i]->GenDoubleSlidingWindowDeclRef();
+ ss << " ? ";
+ ss << vSubArguments[i]->GenDoubleSlidingWindowDeclRef();
+ ss << " : tmp0;\n";
+ ss << " else if(isnan(";
+ ss << vSubArguments[i]->GenDoubleSlidingWindowDeclRef();
+ ss << ") && ";
+ ss << vSubArguments[i]->GenStringSlidingWindowDeclRef();
+ ss << " != 0)\n";
+ ss << " tmp0 = tmp0 < 0.0 ? 0.0 : tmp0;\n";
+ ss << " }\n";
+ }
+ else if(isMixed == svDoubleVectorRefDouble
+ || isMixed == svSingleVectorRefDouble)
+ {
+ ss << " if (!isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " tmp0 = tmp0 < ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " ? " << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " : tmp0;";
+ ss <<"\n }\n";
+ }
+ else if(isMixed == svDoubleVectorRefString)
+ {
+ ss << " if(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " == 0)\n continue;\n";
+ ss << " tmp0 = tmp0 < 0.0 ? 0.0 : tmp0;\n";
+ ss << " }\n";
+ }
+ else if(isMixed == svSingleVectorRefString)
+ {
+ ss << " if(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " != 0)\n";
+ ss << " tmp0 = tmp0 < 0.0 ? 0.0 : tmp0;\n";
+ ss << " }\n";
+ }
+ else if(isMixed == svDoubleDouble)
+ {
+ ss << " tmp0 = tmp0 < ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " ? " << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " : tmp0;\n }\n";
+ }
+ else
+ {
+ ss << " }\n";
+ }
+ }
+ else
+ {
+ ss << " tmp0 = tmp0 < ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " ? " << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " : tmp0;";
+ ss <<"\n }\n";
+ }
+ }
+ ss << " return tmp0 == 2.22507e-308 ? 0.0 : tmp0;\n";
+ ss << "}\n";
+}
+void OpAverageA::GenSlidingWindowFunction(
+ std::stringstream &ss, const std::string &sSymName, SubArguments &
+vSubArguments)
+{
+ int isMixed = 0;
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss << "{\n";
+ ss << " int gid0=get_global_id(0);\n";
+ ss << " double tmp0 = 0.0;\n";
+ ss << " double nCount = 0.0;\n";
+ ss <<"\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ if(pDVR->GetArrays()[0].mpNumericArray
+ && pDVR->GetArrays()[0].mpStringArray)
+ isMixed = svDoubleVectorRefDoubleString;
+ else if(pDVR->GetArrays()[0].mpNumericArray)
+ isMixed = svDoubleVectorRefDouble;
+ else if(pDVR->GetArrays()[0].mpStringArray)
+ isMixed = svDoubleVectorRefString;
+ else
+ isMixed = svDoubleVectorRefNULL;
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed()) {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++){\n";
+ } else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed()) {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+"<< nCurWindowSize << "; i++){\n";
+ } else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed()){
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < "<< nCurWindowSize << "; i++){\n";
+ }
+ else {
+ ss << "0; i < "<< nCurWindowSize << "; i++){\n";
+ }
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+
+ if(pSVR->GetArray().mpNumericArray
+ && pSVR->GetArray().mpStringArray)
+ isMixed = svSingleVectorRefDoubleString;
+ else if(pSVR->GetArray().mpNumericArray)
+ isMixed = svSingleVectorRefDouble;
+ else if(pSVR->GetArray().mpStringArray)
+ isMixed = svSingleVectorRefString;
+ else
+ isMixed = svSingleVectorRefNULL;
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << "){\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << " {\n";
+ isMixed = svDoubleDouble;
+ }
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ if(isMixed == svDoubleVectorRefDoubleString
+ || isMixed == svSingleVectorRefDoubleString)
+ {
+ ss << " if (!isnan(";
+ ss << vSubArguments[i]->GenDoubleSlidingWindowDeclRef();
+ ss << ")){\n";
+ ss << " tmp0 +=";
+ ss << vSubArguments[i]->GenDoubleSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " nCount+=1.0;\n";
+ ss << " }\n";
+ ss << " else if(isnan(";
+ ss << vSubArguments[i]->GenDoubleSlidingWindowDeclRef();
+ ss << ") && ";
+ ss << vSubArguments[i]->GenStringSlidingWindowDeclRef();
+ ss << " != 0)\n";
+ ss << " nCount+=1.0;\n";
+ ss << " }\n";
+ }
+ else if(isMixed == svDoubleVectorRefDouble
+ || isMixed == svSingleVectorRefDouble)
+ {
+ ss << " if (!isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ")){\n";
+ ss << " tmp0 +=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " nCount+=1.0;\n";
+ ss <<"}\n }\n";
+ }
+ else if(isMixed == svDoubleVectorRefString)
+ {
+ ss << " if (!isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " nCount+=1.0;\n";
+ ss <<"\n }\n";
+ }
+ else if(isMixed == svSingleVectorRefString)
+ {
+ ss << " if(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " != 0)\n";
+ ss << " nCount+=1.0;\n";
+ ss << " }\n";
+ }
+ else if(isMixed == svDoubleDouble)
+ {
+ ss << " tmp0 +=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " nCount+=1.0;\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " }\n";
+ }
+ }
+ else
+ {
+ ss << " tmp0 +=";
+ ss << vSubArguments[i]->GenDoubleSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " nCount+=1.0;\n";
+ ss << " }\n";
+ }
+ }
+ ss << " return tmp0*pow(nCount,-1);\n";
+ ss << "}\n";
+}
+void OpVarA::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ int isMixedDV = 0;
+ int isMixedSV = 0;
+ ss << "\ndouble " << sSymName;
+ ss << "_" << BinFuncName() << "(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << "){\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double fSum = 0.0;\n";
+ ss << " double fMean = 0.0;\n";
+ ss << " double vSum = 0.0;\n";
+ ss << " double fCount = 0.0;\n";
+ ss << " double arg = 0.0;\n";
+ unsigned i = vSubArguments.size();
+ while (i--)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+
+ if(ocPush == vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ assert(pDVR);
+ if(pDVR->GetArrays()[0].mpNumericArray
+ && pDVR->GetArrays()[0].mpStringArray)
+ isMixedDV = svDoubleVectorRefDoubleString;
+ else if(pDVR->GetArrays()[0].mpNumericArray)
+ isMixedDV = svDoubleVectorRefDouble;
+ else if(pDVR->GetArrays()[0].mpStringArray)
+ isMixedDV = svDoubleVectorRefString;
+ else
+ isMixedDV = svDoubleVectorRefNULL;
+
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < " << pDVR->GetArrayLength() << "; i++)\n";
+ ss << " {\n";
+ }
+
+ if(isMixedDV == svDoubleVectorRefDoubleString)
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenDoubleSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (isnan(arg) && ";
+ ss << vSubArguments[i]->GenStringSlidingWindowDeclRef();
+ ss << " == 0)\n";
+ ss << " continue;\n";
+ ss << " if(isnan(arg) && ";
+ ss << vSubArguments[i]->GenStringSlidingWindowDeclRef();
+ ss << " != 0)\n";
+ ss << " {\n";
+ ss << " fCount = fCount + 1.0;\n";
+ ss << " continue;\n";
+ ss << " }\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount = fCount + 1.0;\n";
+ ss << " }\n";
+ }
+ else if(isMixedDV == svDoubleVectorRefDouble)
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (isnan(arg))\n";
+ ss << " continue;\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount = fCount + 1.0;\n";
+ ss << " }\n";
+ }
+ else if(isMixedDV == svDoubleVectorRefString)
+ {
+ ss << " if (";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " == 0)\n";
+ ss << " continue;\n";
+ ss << " fCount = fCount + 1.0;\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " continue;\n";
+ ss << " }\n";
+ }
+
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ assert(pSVR);
+ if(pSVR->GetArray().mpNumericArray
+ && pSVR->GetArray().mpStringArray)
+ isMixedSV = svSingleVectorRefDoubleString;
+ else if(pSVR->GetArray().mpNumericArray)
+ isMixedSV = svSingleVectorRefDouble;
+ else if(pSVR->GetArray().mpStringArray)
+ isMixedSV = svSingleVectorRefString;
+ else
+ isMixedSV = svSingleVectorRefNULL;
+
+ if(isMixedSV == svSingleVectorRefDoubleString)
+ {
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenDoubleSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (!isnan(arg))\n";
+ ss << " {\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount = fCount + 1.0;\n";
+ ss << " }\n";
+ ss << " if (isnan(arg) && ";
+ ss << vSubArguments[i]->GenStringSlidingWindowDeclRef();
+ ss << " != 0)\n";
+ ss << " fCount = fCount + 1.0;\n";
+ ss << " }\n";
+ }
+ else if(isMixedSV == svSingleVectorRefDouble)
+ {
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (!isnan(arg))\n";
+ ss << " {\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount += 1.0;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else if(isMixedSV == svSingleVectorRefString)
+ {
+
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " if (";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " != 0)\n";
+ ss << " fCount = fCount + 1.0;\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " arg =0.0;\n";
+ }
+ }
+ else
+ {
+ ss << " arg = " << pCur->GetDouble() << ";\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount = fCount + 1.0;\n";
+ }
+ }
+ else
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount = fCount + 1.0;\n";
+ }
+ if (i == 0)
+ {
+ ss << " fMean = fSum * pow(fCount,-1.0);\n";
+ }
+ }
+ i = vSubArguments.size();
+ while (i--)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ if(pDVR->GetArrays()[0].mpNumericArray
+ && pDVR->GetArrays()[0].mpStringArray)
+ isMixedDV = svDoubleVectorRefDoubleString;
+ else if(pDVR->GetArrays()[0].mpNumericArray)
+ isMixedDV = svDoubleVectorRefDouble;
+ else if(pDVR->GetArrays()[0].mpStringArray)
+ isMixedDV = svDoubleVectorRefString;
+ else
+ isMixedDV = svDoubleVectorRefNULL;
+
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < " << pDVR->GetArrayLength() << "; i++)\n";
+ ss << " {\n";
+ }
+ if(isMixedDV == svDoubleVectorRefDoubleString)
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenDoubleSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (isnan(arg) && ";
+ ss << vSubArguments[i]->GenStringSlidingWindowDeclRef();
+ ss << " == 0)\n";
+ ss << " continue;\n";
+ ss << " if(isnan(arg) && ";
+ ss << vSubArguments[i]->GenStringSlidingWindowDeclRef();
+ ss << " != 0)\n";
+ ss << " arg = 0.0;\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ ss << " }\n";
+
+ }
+ else if(isMixedDV == svDoubleVectorRefDouble)
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (isnan(arg))\n";
+ ss << " continue;\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ ss << " }\n";
+
+ }
+ else if(isMixedDV == svDoubleVectorRefString)
+ {
+ ss << " if (";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " == 0)\n";
+ ss << " continue;\n";
+ ss << " arg = 0.0;\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " continue;\n";
+ ss << " }\n";
+ }
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ if(pSVR->GetArray().mpNumericArray
+ && pSVR->GetArray().mpStringArray)
+ isMixedSV = svSingleVectorRefDoubleString;
+ else if(pSVR->GetArray().mpNumericArray)
+ isMixedSV = svSingleVectorRefDouble;
+ else if(pSVR->GetArray().mpStringArray)
+ isMixedSV = svSingleVectorRefString;
+ else
+ isMixedSV = svSingleVectorRefNULL;
+
+ if(isMixedSV == svSingleVectorRefDoubleString)
+ {
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenDoubleSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (!isnan(arg))\n";
+ ss << " vSum += (arg - fMean)*(arg - fMean);\n";
+ ss << " if (isnan(arg) && ";
+ ss << vSubArguments[i]->GenStringSlidingWindowDeclRef();
+ ss << " != 0)\n";
+ ss << " {\n";
+ ss << " arg = 0.0;\n";
+ ss << " vSum += (arg - fMean)*(arg - fMean);\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else if(isMixedSV == svSingleVectorRefDouble)
+ {
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (!isnan(arg))\n";
+ ss << " {\n";
+ ss << " vSum += (arg - fMean)*(arg - fMean);\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else if(isMixedSV == svSingleVectorRefString)
+ {
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " if (";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " != 0)\n";
+ ss << " {\n";
+ ss << " arg = 0.0;\n";
+ ss << " vSum += (arg - fMean)*(arg - fMean);\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " arg = 0.0;\n";
+ }
+ }
+ else
+ {
+ ss << " arg = " << pCur->GetDouble() << ";\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ }
+ }
+ else
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ }
+ }
+ ss << " if (fCount <= 1.0)\n";
+ ss << " return DBL_MAX;\n";
+ ss << " else\n";
+ ss << " return vSum * pow(fCount - 1.0,-1.0);\n";
+ ss << "}\n";
+}
+
+void OpVarPA::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ int isMixedDV = 0;
+ int isMixedSV = 0;
+ ss << "\ndouble " << sSymName;
+ ss << "_" << BinFuncName() << "(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << "){\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double fSum = 0.0;\n";
+ ss << " double fMean = 0.0;\n";
+ ss << " double vSum = 0.0;\n";
+ ss << " double fCount = 0.0;\n";
+ ss << " double arg = 0.0;\n";
+ unsigned i = vSubArguments.size();
+ while (i--)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+
+ if(ocPush == vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ if(pDVR->GetArrays()[0].mpNumericArray
+ && pDVR->GetArrays()[0].mpStringArray)
+ isMixedDV = svDoubleVectorRefDoubleString;
+ else if(pDVR->GetArrays()[0].mpNumericArray)
+ isMixedDV = svDoubleVectorRefDouble;
+ else if(pDVR->GetArrays()[0].mpStringArray)
+ isMixedDV = svDoubleVectorRefString;
+ else
+ isMixedDV = svDoubleVectorRefNULL;
+
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < " << pDVR->GetArrayLength() << "; i++)\n";
+ ss << " {\n";
+ }
+
+ if(isMixedDV == svDoubleVectorRefDoubleString)
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenDoubleSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (isnan(arg) && ";
+ ss << vSubArguments[i]->GenStringSlidingWindowDeclRef();
+ ss << " == 0)\n";
+ ss << " continue;\n";
+ ss << " if(isnan(arg) && ";
+ ss << vSubArguments[i]->GenStringSlidingWindowDeclRef();
+ ss << " != 0)\n";
+ ss << " {\n";
+ ss << " fCount = fCount + 1.0;\n";
+ ss << " continue;\n";
+ ss << " }\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount = fCount + 1.0;\n";
+ ss << " }\n";
+ }
+ else if(isMixedDV == svDoubleVectorRefDouble)
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (isnan(arg))\n";
+ ss << " continue;\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount = fCount + 1.0;\n";
+ ss << " }\n";
+ }
+ else if(isMixedDV == svDoubleVectorRefString)
+ {
+ ss << " if (";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " == 0)\n";
+ ss << " continue;\n";
+ ss << " fCount = fCount + 1.0;\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " continue;\n";
+ ss << " }\n";
+ }
+
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ if(pSVR->GetArray().mpNumericArray
+ && pSVR->GetArray().mpStringArray)
+ isMixedSV = svSingleVectorRefDoubleString;
+ else if(pSVR->GetArray().mpNumericArray)
+ isMixedSV = svSingleVectorRefDouble;
+ else if(pSVR->GetArray().mpStringArray)
+ isMixedSV = svSingleVectorRefString;
+ else
+ isMixedSV = svSingleVectorRefNULL;
+
+ if(isMixedSV == svSingleVectorRefDoubleString)
+ {
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenDoubleSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (!isnan(arg))\n";
+ ss << " {\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount = fCount + 1.0;\n";
+ ss << " }\n";
+ ss << " if (isnan(arg) && ";
+ ss << vSubArguments[i]->GenStringSlidingWindowDeclRef();
+ ss << " != 0)\n";
+ ss << " fCount = fCount + 1.0;\n";
+ ss << " }\n";
+ }
+ else if(isMixedSV == svSingleVectorRefDouble)
+ {
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (!isnan(arg))\n";
+ ss << " {\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount += 1.0;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else if(isMixedSV == svSingleVectorRefString)
+ {
+
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " if (";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " != 0)\n";
+ ss << " fCount = fCount + 1.0;\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " arg =0.0;\n";
+ }
+ }
+ else
+ {
+ ss << " arg = " << pCur->GetDouble() << ";\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount = fCount + 1.0;\n";
+ }
+ }
+ else
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount = fCount + 1.0;\n";
+ }
+ if (i == 0)
+ {
+ ss << " fMean = fSum * pow(fCount,-1.0);\n";
+ }
+ }
+ i = vSubArguments.size();
+ while (i--)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ if(pDVR->GetArrays()[0].mpNumericArray
+ && pDVR->GetArrays()[0].mpStringArray)
+ isMixedDV = svDoubleVectorRefDoubleString;
+ else if(pDVR->GetArrays()[0].mpNumericArray)
+ isMixedDV = svDoubleVectorRefDouble;
+ else if(pDVR->GetArrays()[0].mpStringArray)
+ isMixedDV = svDoubleVectorRefString;
+ else
+ isMixedDV = svDoubleVectorRefNULL;
+
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < " << pDVR->GetArrayLength() << "; i++)\n";
+ ss << " {\n";
+ }
+ if(isMixedDV == svDoubleVectorRefDoubleString)
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenDoubleSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (isnan(arg) && ";
+ ss << vSubArguments[i]->GenStringSlidingWindowDeclRef();
+ ss << " == 0)\n";
+ ss << " continue;\n";
+ ss << " if(isnan(arg) && ";
+ ss << vSubArguments[i]->GenStringSlidingWindowDeclRef();
+ ss << " != 0)\n";
+ ss << " arg = 0.0;\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ ss << " }\n";
+
+ }
+ else if(isMixedDV == svDoubleVectorRefDouble)
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (isnan(arg))\n";
+ ss << " continue;\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ ss << " }\n";
+
+ }
+ else if(isMixedDV == svDoubleVectorRefString)
+ {
+ ss << " if (";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " == 0)\n";
+ ss << " continue;\n";
+ ss << " arg = 0.0;\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " continue;\n";
+ ss << " }\n";
+ }
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ if(pSVR->GetArray().mpNumericArray
+ && pSVR->GetArray().mpStringArray)
+ isMixedSV = svSingleVectorRefDoubleString;
+ else if(pSVR->GetArray().mpNumericArray)
+ isMixedSV = svSingleVectorRefDouble;
+ else if(pSVR->GetArray().mpStringArray)
+ isMixedSV = svSingleVectorRefString;
+ else
+ isMixedSV = svSingleVectorRefNULL;
+
+ if(isMixedSV == svSingleVectorRefDoubleString)
+ {
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenDoubleSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (!isnan(arg))\n";
+ ss << " vSum += (arg - fMean)*(arg - fMean);\n";
+ ss << " if (isnan(arg) && ";
+ ss << vSubArguments[i]->GenStringSlidingWindowDeclRef();
+ ss << " != 0)\n";
+ ss << " {\n";
+ ss << " arg = 0.0;\n";
+ ss << " vSum += (arg - fMean)*(arg - fMean);\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else if(isMixedSV == svSingleVectorRefDouble)
+ {
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (!isnan(arg))\n";
+ ss << " {\n";
+ ss << " vSum += (arg - fMean)*(arg - fMean);\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else if(isMixedSV == svSingleVectorRefString)
+ {
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " if (";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " != 0)\n";
+ ss << " {\n";
+ ss << " arg = 0.0;\n";
+ ss << " vSum += (arg - fMean)*(arg - fMean);\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " arg = 0.0;\n";
+ }
+ }
+ else
+ {
+ ss << " arg = " << pCur->GetDouble() << ";\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ }
+ }
+ else
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ }
+ }
+ ss << " if (fCount == 0.0)\n";
+ ss << " return DBL_MAX;\n";
+ ss << " else\n";
+ ss << " return vSum * pow(fCount,-1.0);\n";
+ ss << "}\n";
+}
+void OpStDevA::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ int isMixedDV = 0;
+ int isMixedSV = 0;
+ ss << "\ndouble " << sSymName;
+ ss << "_" << BinFuncName() << "(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << "){\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double fSum = 0.0;\n";
+ ss << " double fMean = 0.0;\n";
+ ss << " double vSum = 0.0;\n";
+ ss << " double fCount = 0.0;\n";
+ ss << " double arg = 0.0;\n";
+ unsigned i = vSubArguments.size();
+ while (i--)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+
+ if(ocPush == vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ if(pDVR->GetArrays()[0].mpNumericArray
+ && pDVR->GetArrays()[0].mpStringArray)
+ isMixedDV = svDoubleVectorRefDoubleString;
+ else if(pDVR->GetArrays()[0].mpNumericArray)
+ isMixedDV = svDoubleVectorRefDouble;
+ else if(pDVR->GetArrays()[0].mpStringArray)
+ isMixedDV = svDoubleVectorRefString;
+ else
+ isMixedDV = svDoubleVectorRefNULL;
+
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < " << pDVR->GetArrayLength() << "; i++)\n";
+ ss << " {\n";
+ }
+
+ if(isMixedDV == svDoubleVectorRefDoubleString)
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenDoubleSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (isnan(arg) && ";
+ ss << vSubArguments[i]->GenStringSlidingWindowDeclRef();
+ ss << " == 0)\n";
+ ss << " continue;\n";
+ ss << " if(isnan(arg) && ";
+ ss << vSubArguments[i]->GenStringSlidingWindowDeclRef();
+ ss << " != 0)\n";
+ ss << " {\n";
+ ss << " fCount = fCount + 1.0;\n";
+ ss << " continue;\n";
+ ss << " }\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount = fCount + 1.0;\n";
+ ss << " }\n";
+ }
+ else if(isMixedDV == svDoubleVectorRefDouble)
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (isnan(arg))\n";
+ ss << " continue;\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount = fCount + 1.0;\n";
+ ss << " }\n";
+ }
+ else if(isMixedDV == svDoubleVectorRefString)
+ {
+ ss << " if (";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " == 0)\n";
+ ss << " continue;\n";
+ ss << " fCount = fCount + 1.0;\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " continue;\n";
+ ss << " }\n";
+ }
+
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ if(pSVR->GetArray().mpNumericArray
+ && pSVR->GetArray().mpStringArray)
+ isMixedSV = svSingleVectorRefDoubleString;
+ else if(pSVR->GetArray().mpNumericArray)
+ isMixedSV = svSingleVectorRefDouble;
+ else if(pSVR->GetArray().mpStringArray)
+ isMixedSV = svSingleVectorRefString;
+ else
+ isMixedSV = svSingleVectorRefNULL;
+
+ if(isMixedSV == svSingleVectorRefDoubleString)
+ {
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenDoubleSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (!isnan(arg))\n";
+ ss << " {\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount = fCount + 1.0;\n";
+ ss << " }\n";
+ ss << " if (isnan(arg) && ";
+ ss << vSubArguments[i]->GenStringSlidingWindowDeclRef();
+ ss << " != 0)\n";
+ ss << " fCount = fCount + 1.0;\n";
+ ss << " }\n";
+ }
+ else if(isMixedSV == svSingleVectorRefDouble)
+ {
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (!isnan(arg))\n";
+ ss << " {\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount += 1.0;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else if(isMixedSV == svSingleVectorRefString)
+ {
+
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " if (";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " != 0)\n";
+ ss << " fCount = fCount + 1.0;\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " arg =0.0;\n";
+ }
+ }
+ else
+ {
+ ss << " arg = " << pCur->GetDouble() << ";\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount = fCount + 1.0;\n";
+ }
+ }
+ else
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount = fCount + 1.0;\n";
+ }
+ if (i == 0)
+ {
+ ss << " fMean = fSum * pow(fCount,-1.0);\n";
+ }
+ }
+ i = vSubArguments.size();
+ while (i--)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ if(pDVR->GetArrays()[0].mpNumericArray
+ && pDVR->GetArrays()[0].mpStringArray)
+ isMixedDV = svDoubleVectorRefDoubleString;
+ else if(pDVR->GetArrays()[0].mpNumericArray)
+ isMixedDV = svDoubleVectorRefDouble;
+ else if(pDVR->GetArrays()[0].mpStringArray)
+ isMixedDV = svDoubleVectorRefString;
+ else
+ isMixedDV = svDoubleVectorRefNULL;
+
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < " << pDVR->GetArrayLength() << "; i++)\n";
+ ss << " {\n";
+ }
+ if(isMixedDV == svDoubleVectorRefDoubleString)
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenDoubleSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (isnan(arg) && ";
+ ss << vSubArguments[i]->GenStringSlidingWindowDeclRef();
+ ss << " == 0)\n";
+ ss << " continue;\n";
+ ss << " if(isnan(arg) && ";
+ ss << vSubArguments[i]->GenStringSlidingWindowDeclRef();
+ ss << " != 0)\n";
+ ss << " arg = 0.0;\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ ss << " }\n";
+
+ }
+ else if(isMixedDV == svDoubleVectorRefDouble)
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (isnan(arg))\n";
+ ss << " continue;\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ ss << " }\n";
+
+ }
+ else if(isMixedDV == svDoubleVectorRefString)
+ {
+ ss << " if (";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " == 0)\n";
+ ss << " continue;\n";
+ ss << " arg = 0.0;\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " continue;\n";
+ ss << " }\n";
+ }
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ if(pSVR->GetArray().mpNumericArray
+ && pSVR->GetArray().mpStringArray)
+ isMixedSV = svSingleVectorRefDoubleString;
+ else if(pSVR->GetArray().mpNumericArray)
+ isMixedSV = svSingleVectorRefDouble;
+ else if(pSVR->GetArray().mpStringArray)
+ isMixedSV = svSingleVectorRefString;
+ else
+ isMixedSV = svSingleVectorRefNULL;
+
+ if(isMixedSV == svSingleVectorRefDoubleString)
+ {
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenDoubleSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (!isnan(arg))\n";
+ ss << " vSum += (arg - fMean)*(arg - fMean);\n";
+ ss << " if (isnan(arg) && ";
+ ss << vSubArguments[i]->GenStringSlidingWindowDeclRef();
+ ss << " != 0)\n";
+ ss << " {\n";
+ ss << " arg = 0.0;\n";
+ ss << " vSum += (arg - fMean)*(arg - fMean);\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else if(isMixedSV == svSingleVectorRefDouble)
+ {
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (!isnan(arg))\n";
+ ss << " {\n";
+ ss << " vSum += (arg - fMean)*(arg - fMean);\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else if(isMixedSV == svSingleVectorRefString)
+ {
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " if (";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " != 0)\n";
+ ss << " {\n";
+ ss << " arg = 0.0;\n";
+ ss << " vSum += (arg - fMean)*(arg - fMean);\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " arg = 0.0;\n";
+ }
+ }
+ else
+ {
+ ss << " arg = " << pCur->GetDouble() << ";\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ }
+ }
+ else
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ }
+ }
+ ss << " if (fCount <= 1.0)\n";
+ ss << " return DBL_MAX;\n";
+ ss << " else\n";
+ ss << " return sqrt(vSum * pow(fCount - 1.0,-1.0));\n";
+ ss << "}\n";
+}
+
+void OpStDevPA::GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ int isMixedDV = 0;
+ int isMixedSV = 0;
+ ss << "\ndouble " << sSymName;
+ ss << "_" << BinFuncName() << "(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << "){\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double fSum = 0.0;\n";
+ ss << " double fMean = 0.0;\n";
+ ss << " double vSum = 0.0;\n";
+ ss << " double fCount = 0.0;\n";
+ ss << " double arg = 0.0;\n";
+ unsigned i = vSubArguments.size();
+ while (i--)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+
+ if(ocPush == vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ if(pDVR->GetArrays()[0].mpNumericArray
+ && pDVR->GetArrays()[0].mpStringArray)
+ isMixedDV = svDoubleVectorRefDoubleString;
+ else if(pDVR->GetArrays()[0].mpNumericArray)
+ isMixedDV = svDoubleVectorRefDouble;
+ else if(pDVR->GetArrays()[0].mpStringArray)
+ isMixedDV = svDoubleVectorRefString;
+ else
+ isMixedDV = svDoubleVectorRefNULL;
+
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < " << pDVR->GetArrayLength() << "; i++)\n";
+ ss << " {\n";
+ }
+
+ if(isMixedDV == svDoubleVectorRefDoubleString)
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenDoubleSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (isnan(arg) && ";
+ ss << vSubArguments[i]->GenStringSlidingWindowDeclRef();
+ ss << " == 0)\n";
+ ss << " continue;\n";
+ ss << " if(isnan(arg) && ";
+ ss << vSubArguments[i]->GenStringSlidingWindowDeclRef();
+ ss << " != 0)\n";
+ ss << " {\n";
+ ss << " fCount = fCount + 1.0;\n";
+ ss << " continue;\n";
+ ss << " }\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount = fCount + 1.0;\n";
+ ss << " }\n";
+ }
+ else if(isMixedDV == svDoubleVectorRefDouble)
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (isnan(arg))\n";
+ ss << " continue;\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount = fCount + 1.0;\n";
+ ss << " }\n";
+ }
+ else if(isMixedDV == svDoubleVectorRefString)
+ {
+ ss << " if (";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " == 0)\n";
+ ss << " continue;\n";
+ ss << " fCount = fCount + 1.0;\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " continue;\n";
+ ss << " }\n";
+ }
+
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ if(pSVR->GetArray().mpNumericArray
+ && pSVR->GetArray().mpStringArray)
+ isMixedSV = svSingleVectorRefDoubleString;
+ else if(pSVR->GetArray().mpNumericArray)
+ isMixedSV = svSingleVectorRefDouble;
+ else if(pSVR->GetArray().mpStringArray)
+ isMixedSV = svSingleVectorRefString;
+ else
+ isMixedSV = svSingleVectorRefNULL;
+
+ if(isMixedSV == svSingleVectorRefDoubleString)
+ {
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenDoubleSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (!isnan(arg))\n";
+ ss << " {\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount = fCount + 1.0;\n";
+ ss << " }\n";
+ ss << " if (isnan(arg) && ";
+ ss << vSubArguments[i]->GenStringSlidingWindowDeclRef();
+ ss << " != 0)\n";
+ ss << " fCount = fCount + 1.0;\n";
+ ss << " }\n";
+ }
+ else if(isMixedSV == svSingleVectorRefDouble)
+ {
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (!isnan(arg))\n";
+ ss << " {\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount += 1.0;\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else if(isMixedSV == svSingleVectorRefString)
+ {
+
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " if (";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " != 0)\n";
+ ss << " fCount = fCount + 1.0;\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " arg =0.0;\n";
+ }
+ }
+ else
+ {
+ ss << " arg = " << pCur->GetDouble() << ";\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount = fCount + 1.0;\n";
+ }
+ }
+ else
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " fSum += arg;\n";
+ ss << " fCount = fCount + 1.0;\n";
+ }
+ if (i == 0)
+ {
+ ss << " fMean = fSum * pow(fCount,-1.0);\n";
+ }
+ }
+ i = vSubArguments.size();
+ while (i--)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+
+ if(ocPush==vSubArguments[i]->GetFormulaToken()->GetOpCode())
+ {
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ if(pDVR->GetArrays()[0].mpNumericArray
+ && pDVR->GetArrays()[0].mpStringArray)
+ isMixedDV = svDoubleVectorRefDoubleString;
+ else if(pDVR->GetArrays()[0].mpNumericArray)
+ isMixedDV = svDoubleVectorRefDouble;
+ else if(pDVR->GetArrays()[0].mpStringArray)
+ isMixedDV = svDoubleVectorRefString;
+ else
+ isMixedDV = svDoubleVectorRefNULL;
+
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ if (!pDVR->IsStartFixed() && pDVR->IsEndFixed())
+ {
+ ss << "gid0; i < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i < " << pDVR->GetArrayLength();
+ ss << " && i < gid0+" << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else if (!pDVR->IsStartFixed() && !pDVR->IsEndFixed())
+ {
+ ss << "0; i + gid0 < " << pDVR->GetArrayLength();
+ ss << " && i < " << nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ }
+ else
+ {
+ ss << "0; i < " << pDVR->GetArrayLength() << "; i++)\n";
+ ss << " {\n";
+ }
+ if(isMixedDV == svDoubleVectorRefDoubleString)
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenDoubleSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (isnan(arg) && ";
+ ss << vSubArguments[i]->GenStringSlidingWindowDeclRef();
+ ss << " == 0)\n";
+ ss << " continue;\n";
+ ss << " if(isnan(arg) && ";
+ ss << vSubArguments[i]->GenStringSlidingWindowDeclRef();
+ ss << " != 0)\n";
+ ss << " arg = 0.0;\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ ss << " }\n";
+
+ }
+ else if(isMixedDV == svDoubleVectorRefDouble)
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (isnan(arg))\n";
+ ss << " continue;\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ ss << " }\n";
+
+ }
+ else if(isMixedDV == svDoubleVectorRefString)
+ {
+ ss << " if (";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " == 0)\n";
+ ss << " continue;\n";
+ ss << " arg = 0.0;\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " continue;\n";
+ ss << " }\n";
+ }
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pSVR =
+ static_cast< const formula::SingleVectorRefToken* >(pCur);
+ if(pSVR->GetArray().mpNumericArray
+ && pSVR->GetArray().mpStringArray)
+ isMixedSV = svSingleVectorRefDoubleString;
+ else if(pSVR->GetArray().mpNumericArray)
+ isMixedSV = svSingleVectorRefDouble;
+ else if(pSVR->GetArray().mpStringArray)
+ isMixedSV = svSingleVectorRefString;
+ else
+ isMixedSV = svSingleVectorRefNULL;
+
+ if(isMixedSV == svSingleVectorRefDoubleString)
+ {
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenDoubleSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if (!isnan(arg))\n";
+ ss << " vSum += (arg - fMean)*(arg - fMean);\n";
+ ss << " if (isnan(arg) && ";
+ ss << vSubArguments[i]->GenStringSlidingWindowDeclRef();
+ ss << " != 0)\n";
+ ss << " {\n";
+ ss << " arg = 0.0;\n";
+ ss << " vSum += (arg - fMean)*(arg - fMean);\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else if(isMixedSV == svSingleVectorRefDouble)
+ {
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " if (!isnan(arg))\n";
+ ss << " {\n";
+ ss << " vSum += (arg - fMean)*(arg - fMean);\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else if(isMixedSV == svSingleVectorRefString)
+ {
+ ss << " if (gid0 < " << pSVR->GetArrayLength() << ")\n";
+ ss << " {\n";
+ ss << " if (";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << " != 0)\n";
+ ss << " {\n";
+ ss << " arg = 0.0;\n";
+ ss << " vSum += (arg - fMean)*(arg - fMean);\n";
+ ss << " }\n";
+ ss << " }\n";
+ }
+ else
+ {
+ ss << " arg = 0.0;\n";
+ }
+ }
+ else
+ {
+ ss << " arg = " << pCur->GetDouble() << ";\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ }
+ }
+ else
+ {
+ ss << " arg = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef() << ";\n";
+ ss << " vSum += (arg - fMean) * (arg - fMean);\n";
+ }
+ }
+ ss << " if (fCount == 1.0)\n";
+ ss << " return DBL_MAX;\n";
+ ss << " else\n";
+ ss << " return sqrt(vSum * pow(fCount,-1.0));\n";
+ ss << "}\n";
+}
+
+void OpAveDev:: GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments)
+{
+ ss << "\ndouble " << sSymName;
+ ss << "_"<< BinFuncName() <<"( ";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ }
+ ss << ")\n";
+ ss <<"{\n";
+ ss << " int gid0 = get_global_id(0);\n";
+ ss << " double sum=0.0;\n";
+ ss << " double length;\n";
+ ss << " double totallength=0;\n";
+ ss << " double tmp = 0;\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " length="<<nCurWindowSize;
+ ss << ";\n";
+ ss << " for (int i = ";
+ ss << "0; i < "<< nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ ss << " double arg"<<i<<" = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(isnan(arg"<<i<<")||((gid0+i)>=";
+ ss << pDVR->GetArrayLength();
+ ss << "))\n";
+ ss << " {\n";
+ ss << " length-=1.0;\n";
+ ss << " continue;\n";
+ ss << " }\n";
+ ss << " sum += arg"<<i<<";\n";
+ ss << " }\n";
+ ss << " totallength +=length;\n";
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ ss << " tmp = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(!isnan(tmp))\n";
+ ss << " {\n";
+ ss << " sum += tmp;\n";
+ ss << " totallength +=1;\n";
+ ss << " }\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << " tmp = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " sum += tmp;\n";
+ ss << " totallength +=1;\n";
+ }
+ }
+ ss << " double mean = sum * pow(totallength,-1);\n";
+ ss << " sum = 0.0;\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ FormulaToken *pCur = vSubArguments[i]->GetFormulaToken();
+ assert(pCur);
+ if (pCur->GetType() == formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pDVR =
+ static_cast<const formula::DoubleVectorRefToken *>(pCur);
+ size_t nCurWindowSize = pDVR->GetRefRowSize();
+ ss << " for (int i = ";
+ ss << "0; i < "<< nCurWindowSize << "; i++)\n";
+ ss << " {\n";
+ ss << " double arg"<<i<<" = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(isnan(arg"<<i<<")||((gid0+i)>=";
+ ss << pDVR->GetArrayLength();
+ ss << "))\n";
+ ss << " {\n";
+ ss << " continue;\n";
+ ss << " }\n";
+ ss << " sum += fabs(arg"<<i<<"-mean);\n";
+ ss << " }\n";
+ }
+ else if (pCur->GetType() == formula::svSingleVectorRef)
+ {
+ ss << " tmp = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " if(!isnan(tmp))\n";
+ ss << " {\n";
+ ss << " sum += fabs(tmp-mean);\n";
+ ss << " }\n";
+ }
+ else if (pCur->GetType() == formula::svDouble)
+ {
+ ss << " tmp = ";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ ss << " sum += fabs(tmp-mean);\n";
+ }
+ }
+ ss << " tmp=sum*pow(totallength,-1);\n";
+ ss << " return tmp;\n";
+ ss << "}";
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/opencl/op_statistical.hxx b/sc/source/core/opencl/op_statistical.hxx
new file mode 100644
index 000000000..a77ef71a9
--- /dev/null
+++ b/sc/source/core/opencl/op_statistical.hxx
@@ -0,0 +1,550 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include "opbase.hxx"
+
+namespace sc::opencl {
+
+class OpStandard: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Standard"; }
+};
+class OpExponDist: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "ExponDist"; }
+};
+class OpVar: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Var"; }
+ virtual bool canHandleMultiVector() const override { return true; }
+};
+class OpSTEYX: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "STEYX"; }
+};
+class OpVarP: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "VarP"; }
+ virtual bool canHandleMultiVector() const override { return true; }
+};
+class OpZTest: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>&) override;
+ virtual std::string BinFuncName() const override { return "ZTest"; }
+};
+class OpStDevP: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "StDevP"; }
+};
+
+class OpStDev: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "StDev"; }
+};
+class OpSkewp: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Skewp"; }
+};
+class OpSlope: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Slope"; }
+};
+class OpWeibull: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Weibull"; }
+};
+class OpFdist: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>&) override;
+ virtual std::string BinFuncName() const override { return "Fdist"; }
+};
+class OpTDist: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "TDist"; }
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>&) override;
+};
+class OpTInv: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "TInv"; }
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>&) override;
+};
+class OpTTest: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "TTest"; }
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>&) override;
+};
+class OpSkew: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Skew"; }
+};
+class OpFisher: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Fisher"; }
+};
+
+class OpFisherInv: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "FisherInv"; }
+};
+
+class OpGamma: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Gamma"; }
+};
+
+class OpCorrel: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Correl"; }
+};
+
+class OpNegbinomdist: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "OpNegbinomdist"; }
+};
+
+class OpPearson: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "OpPearson"; }
+};
+
+class OpGammaLn: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "GammaLn"; }
+};
+
+class OpGauss: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,
+ std::set<std::string>& ) override;
+ virtual std::string BinFuncName() const override { return "Gauss"; }
+};
+
+class OpGeoMean: public CheckVariables
+{
+public:
+ OpGeoMean(): CheckVariables() {}
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "GeoMean"; }
+};
+
+class OpHarMean: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "HarMean"; }
+};
+
+class OpRsq: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "OpRsq"; }
+};
+class OpNormdist:public Normal{
+ public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "OpNormdist"; }
+};
+class OpMedian:public Normal{
+ public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "OpMedian"; }
+};
+class OpNormsdist:public Normal{
+ public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "OpNormsdist"; }
+};
+class OpNorminv:public Normal{
+ public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "OpNorminv"; }
+};
+class OpNormsinv:public Normal{
+ public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "OpNormsinv"; }
+};
+class OpPhi:public Normal{
+ public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "OpPhi"; }
+};
+class OpKurt: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Kurt"; }
+};
+class OpCovar: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream& ss,
+ const std::string &sSymName, SubArguments& vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Covar"; }
+};
+
+class OpPermut:public Normal{
+ public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "OpPermut"; }
+};
+class OpPermutationA:public Normal{
+ public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "OpPermutationA";}
+};
+
+class OpConfidence: public Normal
+{
+public:
+ virtual std::string GetBottom() override { return "0"; }
+
+ virtual void GenSlidingWindowFunction(std::stringstream& ss,
+ const std::string &sSymName, SubArguments& vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,
+ std::set<std::string>& ) override;
+
+ virtual std::string BinFuncName() const override { return "Confidence"; }
+};
+class OpIntercept: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Intercept"; }
+};
+class OpLogInv: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "LogInv"; }
+};
+class OpCritBinom: public Normal
+{
+public:
+ virtual std::string GetBottom() override { return "0"; }
+
+ virtual void GenSlidingWindowFunction(std::stringstream& ss,
+ const std::string &sSymName, SubArguments& vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,
+ std::set<std::string>& ) override;
+
+ virtual std::string BinFuncName() const override { return "CritBinom"; }
+};
+class OpForecast: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "Forecast"; }
+};
+class OpLogNormDist: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "LogNormdist"; }
+};
+class OpGammaDist: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ void BinInlineFun(std::set<std::string>& decls,std::set<std::string>& funs) override;
+ virtual std::string BinFuncName() const override { return "GammaDist"; }
+};
+class OpHypGeomDist:public Normal{
+ public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "OpHypGeomDist"; }
+};
+class OpChiDist:public Normal{
+ public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>&) override;
+ virtual std::string BinFuncName() const override { return "OpChiDist"; }
+};
+class OpBinomdist:public Normal{
+ public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>&) override;
+ virtual std::string BinFuncName() const override { return "OpBinomdist"; }
+};
+class OpChiSqDist: public CheckVariables
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "ChiSqDist"; }
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>& ) override;
+};
+
+class OpChiSqInv: public CheckVariables
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "ChiSqInv"; }
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>& ) override;
+};
+class OpChiInv:public Normal{
+ public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>&) override;
+ virtual std::string BinFuncName() const override { return "OpChiInv"; }
+};
+class OpPoisson:public Normal{
+ public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>&) override;
+ virtual std::string BinFuncName() const override { return "OpPoisson"; }
+};
+
+class OpGammaInv: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ void BinInlineFun(std::set<std::string>& decls,std::set<std::string>& funs
+) override;
+ virtual std::string BinFuncName() const override { return "GammaInv"; }
+};
+class OpFInv: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ void BinInlineFun(std::set<std::string>& decls,std::set<std::string>& funs
+) override;
+ virtual std::string BinFuncName() const override { return "FInv"; }
+};
+class OpFTest: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ void BinInlineFun(std::set<std::string>& decls,std::set<std::string>& funs
+) override;
+ virtual std::string BinFuncName() const override { return "FTest"; }
+};
+class OpDevSq: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "DevSq"; }
+};
+class OpB: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ void BinInlineFun(std::set<std::string>& decls,std::set<std::string>& funs
+) override;
+ virtual std::string BinFuncName() const override { return "B"; }
+};
+class OpBetaDist: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ void BinInlineFun(std::set<std::string>& decls,std::set<std::string>& funs
+) override;
+ virtual std::string BinFuncName() const override { return "BetaDist"; }
+};
+class OpBetainv:public Normal{
+ public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual void BinInlineFun(std::set<std::string>& ,std::set<std::string>&) override;
+ virtual std::string BinFuncName() const override { return "OpBetainv"; }
+};
+class OpMinA: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "OpMinA"; }
+ virtual bool takeString() const override { return true; }
+ virtual bool takeNumeric() const override { return true; }
+ virtual bool canHandleMultiVector() const override { return true; }
+};
+class OpCountA: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "OpCountA"; }
+ virtual bool takeString() const override { return true; }
+ virtual bool takeNumeric() const override { return true; }
+ virtual bool canHandleMultiVector() const override { return true; }
+};
+class OpMaxA: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "OpMaxA"; }
+ virtual bool takeString() const override { return true; }
+ virtual bool takeNumeric() const override { return true; }
+ virtual bool canHandleMultiVector() const override { return true; }
+};
+class OpVarA: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "OpVarA"; }
+ virtual bool takeString() const override { return true; }
+ virtual bool takeNumeric() const override { return true; }
+ virtual bool canHandleMultiVector() const override { return true; }
+};
+class OpVarPA: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "OpVarPA"; }
+ virtual bool takeString() const override { return true; }
+ virtual bool takeNumeric() const override { return true; }
+ virtual bool canHandleMultiVector() const override { return true; }
+};
+class OpStDevPA: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "OpStDevPA"; }
+ virtual bool takeString() const override { return true; }
+ virtual bool takeNumeric() const override { return true; }
+};
+class OpAverageA: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "OpAverageA"; }
+ virtual bool takeString() const override { return true; }
+ virtual bool takeNumeric() const override { return true; }
+ virtual bool canHandleMultiVector() const override { return true; }
+};
+class OpStDevA: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "OpStDevA"; }
+ virtual bool takeString() const override { return true; }
+ virtual bool takeNumeric() const override { return true; }
+};
+class OpAveDev: public Normal
+{
+public:
+ virtual void GenSlidingWindowFunction(std::stringstream &ss,
+ const std::string &sSymName, SubArguments &vSubArguments) override;
+ virtual std::string BinFuncName() const override { return "AveDev"; }
+ virtual bool canHandleMultiVector() const override { return true; }
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/opencl/opbase.cxx b/sc/source/core/opencl/opbase.cxx
new file mode 100644
index 000000000..e197ea002
--- /dev/null
+++ b/sc/source/core/opencl/opbase.cxx
@@ -0,0 +1,390 @@
+/* -*- 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 <opencl/openclwrapper.hxx>
+#include <formula/vectortoken.hxx>
+#include <sal/log.hxx>
+
+#include "opbase.hxx"
+
+using namespace formula;
+
+namespace sc::opencl {
+
+UnhandledToken::UnhandledToken(
+ const char* m, const std::string& fn, int ln ) :
+ mMessage(m), mFile(fn), mLineNumber(ln) {}
+
+OpenCLError::OpenCLError( const std::string& function, cl_int error, const std::string& file, int line ) :
+ mFunction(function), mError(error), mFile(file), mLineNumber(line)
+{
+ // Not sure if this SAL_INFO() is useful; the place in
+ // CLInterpreterContext::launchKernel() where OpenCLError is
+ // caught already uses SAL_WARN() to display it.
+
+ // SAL_INFO("sc.opencl", "OpenCL error: " << openclwrapper::errorString(mError));
+}
+
+Unhandled::Unhandled( const std::string& fn, int ln ) :
+ mFile(fn), mLineNumber(ln) {}
+
+InvalidParameterCount::InvalidParameterCount( int parameterCount, const std::string& file, int ln ) :
+ mParameterCount(parameterCount), mFile(file), mLineNumber(ln) {}
+
+DynamicKernelArgument::DynamicKernelArgument( const ScCalcConfig& config, const std::string& s,
+ const FormulaTreeNodeRef& ft ) :
+ mCalcConfig(config), mSymName(s), mFormulaTree(ft) { }
+
+std::string DynamicKernelArgument::GenDoubleSlidingWindowDeclRef( bool ) const
+{
+ return std::string("");
+}
+
+/// When Mix, it will be called
+std::string DynamicKernelArgument::GenStringSlidingWindowDeclRef( bool ) const
+{
+ return std::string("");
+}
+
+/// Generate use/references to the argument
+void DynamicKernelArgument::GenDeclRef( std::stringstream& ss ) const
+{
+ ss << mSymName;
+}
+
+void DynamicKernelArgument::GenSlidingWindowFunction( std::stringstream& ) {}
+
+FormulaToken* DynamicKernelArgument::GetFormulaToken() const
+{
+ return mFormulaTree->GetFormulaToken();
+}
+
+std::string DynamicKernelArgument::DumpOpName() const
+{
+ return std::string("");
+}
+
+void DynamicKernelArgument::DumpInlineFun( std::set<std::string>&, std::set<std::string>& ) const {}
+
+const std::string& DynamicKernelArgument::GetName() const
+{
+ return mSymName;
+}
+
+bool DynamicKernelArgument::NeedParallelReduction() const
+{
+ return false;
+}
+
+VectorRef::VectorRef( const ScCalcConfig& config, const std::string& s, const FormulaTreeNodeRef& ft, int idx ) :
+ DynamicKernelArgument(config, s, ft), mpClmem(nullptr), mnIndex(idx)
+{
+ if (mnIndex)
+ {
+ std::stringstream ss;
+ ss << mSymName << "s" << mnIndex;
+ mSymName = ss.str();
+ }
+}
+
+VectorRef::~VectorRef()
+{
+ if (mpClmem)
+ {
+ cl_int err;
+ err = clReleaseMemObject(mpClmem);
+ SAL_WARN_IF(err != CL_SUCCESS, "sc.opencl", "clReleaseMemObject failed: " << openclwrapper::errorString(err));
+ }
+}
+
+/// Generate declaration
+void VectorRef::GenDecl( std::stringstream& ss ) const
+{
+ ss << "__global double *" << mSymName;
+}
+
+/// When declared as input to a sliding window function
+void VectorRef::GenSlidingWindowDecl( std::stringstream& ss ) const
+{
+ VectorRef::GenDecl(ss);
+}
+
+/// When referenced in a sliding window function
+std::string VectorRef::GenSlidingWindowDeclRef( bool nested ) const
+{
+ std::stringstream ss;
+ formula::SingleVectorRefToken* pSVR =
+ dynamic_cast<formula::SingleVectorRefToken*>(DynamicKernelArgument::GetFormulaToken());
+ if (pSVR && !nested)
+ ss << "(gid0 < " << pSVR->GetArrayLength() << "?";
+ ss << mSymName << "[gid0]";
+ if (pSVR && !nested)
+ ss << ":NAN)";
+ return ss.str();
+}
+
+void VectorRef::GenSlidingWindowFunction( std::stringstream& ) {}
+
+size_t VectorRef::GetWindowSize() const
+{
+ FormulaToken* pCur = mFormulaTree->GetFormulaToken();
+ assert(pCur);
+ if (const formula::DoubleVectorRefToken* pCurDVR =
+ dynamic_cast<const formula::DoubleVectorRefToken*>(pCur))
+ {
+ return pCurDVR->GetRefRowSize();
+ }
+ else if (dynamic_cast<const formula::SingleVectorRefToken*>(pCur))
+ {
+ // Prepare intermediate results (on CPU for now)
+ return 1;
+ }
+ else
+ {
+ throw Unhandled(__FILE__, __LINE__);
+ }
+}
+
+std::string VectorRef::DumpOpName() const
+{
+ return std::string("");
+}
+
+void VectorRef::DumpInlineFun( std::set<std::string>&, std::set<std::string>& ) const {}
+
+const std::string& VectorRef::GetName() const
+{
+ return mSymName;
+}
+
+cl_mem VectorRef::GetCLBuffer() const
+{
+ return mpClmem;
+}
+
+bool VectorRef::NeedParallelReduction() const
+{
+ return false;
+}
+
+void Normal::GenSlidingWindowFunction(
+ std::stringstream& ss, const std::string& sSymName, SubArguments& vSubArguments )
+{
+ std::vector<std::string> argVector;
+ ss << "\ndouble " << sSymName;
+ ss << "_" << BinFuncName() << "(";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ if (i)
+ ss << ",";
+ vSubArguments[i]->GenSlidingWindowDecl(ss);
+ argVector.push_back(vSubArguments[i]->GenSlidingWindowDeclRef());
+ }
+ ss << ") {\n\t";
+ ss << "double tmp = " << GetBottom() << ";\n\t";
+ ss << "int gid0 = get_global_id(0);\n\t";
+ ss << "tmp = ";
+ ss << Gen(argVector);
+ ss << ";\n\t";
+ ss << "return tmp;\n";
+ ss << "}";
+}
+
+void CheckVariables::GenTmpVariables(
+ std::stringstream& ss, const SubArguments& vSubArguments )
+{
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ ss << " double tmp";
+ ss << i;
+ ss << ";\n";
+ }
+}
+
+void CheckVariables::CheckSubArgumentIsNan( std::stringstream& ss,
+ SubArguments& vSubArguments, int argumentNum )
+{
+ int i = argumentNum;
+ if (vSubArguments[i]->GetFormulaToken()->GetType() ==
+ formula::svSingleVectorRef)
+ {
+ const formula::SingleVectorRefToken* pTmpDVR1 =
+ static_cast<const formula::SingleVectorRefToken*>(vSubArguments[i]->GetFormulaToken());
+ ss << " if(singleIndex>=";
+ ss << pTmpDVR1->GetArrayLength();
+ ss << " ||";
+ ss << "isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef(true);
+ ss << "))\n";
+ ss << " tmp";
+ ss << i;
+ ss << "=0;\n else \n";
+ ss << " tmp";
+ ss << i;
+ ss << "=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef(true);
+ ss << ";\n";
+ }
+ if (vSubArguments[i]->GetFormulaToken()->GetType() ==
+ formula::svDoubleVectorRef)
+ {
+ const formula::DoubleVectorRefToken* pTmpDVR2 =
+ static_cast<const formula::DoubleVectorRefToken*>(vSubArguments[i]->GetFormulaToken());
+ ss << " if(doubleIndex>=";
+ ss << pTmpDVR2->GetArrayLength();
+ ss << " ||";
+ ss << "isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " tmp";
+ ss << i;
+ ss << "=0;\n else \n";
+ ss << " tmp";
+ ss << i;
+ ss << "=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+ }
+ if (vSubArguments[i]->GetFormulaToken()->GetType() != formula::svDouble &&
+ vSubArguments[i]->GetFormulaToken()->GetOpCode() == ocPush)
+ return;
+
+ ss << " if(";
+ ss << "isnan(";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << "))\n";
+ ss << " tmp";
+ ss << i;
+ ss << "=0;\n else \n";
+ ss << " tmp";
+ ss << i;
+ ss << "=";
+ ss << vSubArguments[i]->GenSlidingWindowDeclRef();
+ ss << ";\n";
+
+}
+
+void CheckVariables::CheckSubArgumentIsNan2( std::stringstream& ss,
+ SubArguments& vSubArguments, int argumentNum, const std::string& p )
+{
+ int i = argumentNum;
+ if (vSubArguments[i]->GetFormulaToken()->GetType() == formula::svDouble)
+ {
+ ss << " tmp";
+ ss << i;
+ ss << "=";
+ vSubArguments[i]->GenDeclRef(ss);
+ ss << ";\n";
+ return;
+ }
+
+ ss << " tmp";
+ ss << i;
+ ss << "= fsum(";
+ vSubArguments[i]->GenDeclRef(ss);
+ if (vSubArguments[i]->GetFormulaToken()->GetType() ==
+ formula::svDoubleVectorRef)
+ ss << "[" << p.c_str() << "]";
+ else if (vSubArguments[i]->GetFormulaToken()->GetType() ==
+ formula::svSingleVectorRef)
+ ss << "[get_group_id(1)]";
+ ss << ", 0);\n";
+}
+
+void CheckVariables::CheckAllSubArgumentIsNan(
+ std::stringstream& ss, SubArguments& vSubArguments )
+{
+ ss << " int k = gid0;\n";
+ for (size_t i = 0; i < vSubArguments.size(); i++)
+ {
+ CheckSubArgumentIsNan(ss, vSubArguments, i);
+ }
+}
+
+void CheckVariables::UnrollDoubleVector( std::stringstream& ss,
+ const std::stringstream& unrollstr, const formula::DoubleVectorRefToken* pCurDVR,
+ int nCurWindowSize )
+{
+ int unrollSize = 16;
+ if (!pCurDVR->IsStartFixed() && pCurDVR->IsEndFixed())
+ {
+ ss << " loop = (" << nCurWindowSize << " - gid0)/";
+ ss << unrollSize << ";\n";
+ }
+ else if (pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed())
+ {
+ ss << " loop = (" << nCurWindowSize << " + gid0)/";
+ ss << unrollSize << ";\n";
+
+ }
+ else
+ {
+ ss << " loop = " << nCurWindowSize << "/" << unrollSize << ";\n";
+ }
+
+ ss << " for ( int j = 0;j< loop; j++)\n";
+ ss << " {\n";
+ ss << " int i = ";
+ if (!pCurDVR->IsStartFixed() && pCurDVR->IsEndFixed())
+ {
+ ss << "gid0 + j * " << unrollSize << ";\n";
+ }
+ else
+ {
+ ss << "j * " << unrollSize << ";\n";
+ }
+
+ if (!pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed())
+ {
+ ss << " int doubleIndex = i+gid0;\n";
+ }
+ else
+ {
+ ss << " int doubleIndex = i;\n";
+ }
+
+ for (int j = 0; j < unrollSize; j++)
+ {
+ ss << unrollstr.str();
+ ss << "i++;\n";
+ ss << "doubleIndex++;\n";
+ }
+ ss << " }\n";
+ ss << " for (int i = ";
+ if (!pCurDVR->IsStartFixed() && pCurDVR->IsEndFixed())
+ {
+ ss << "gid0 + loop *" << unrollSize << "; i < ";
+ ss << nCurWindowSize << "; i++)\n";
+ }
+ else if (pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed())
+ {
+ ss << "0 + loop *" << unrollSize << "; i < gid0+";
+ ss << nCurWindowSize << "; i++)\n";
+ }
+ else
+ {
+ ss << "0 + loop *" << unrollSize << "; i < ";
+ ss << nCurWindowSize << "; i++)\n";
+ }
+ ss << " {\n";
+ if (!pCurDVR->IsStartFixed() && !pCurDVR->IsEndFixed())
+ {
+ ss << " int doubleIndex = i+gid0;\n";
+ }
+ else
+ {
+ ss << " int doubleIndex = i;\n";
+ }
+ ss << unrollstr.str();
+ ss << " }\n";
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/opencl/opbase.hxx b/sc/source/core/opencl/opbase.hxx
new file mode 100644
index 000000000..b4a2780f7
--- /dev/null
+++ b/sc/source/core/opencl/opbase.hxx
@@ -0,0 +1,246 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include <clew/clew.h>
+#include <formula/token.hxx>
+#include <formula/types.hxx>
+#include <memory>
+#include <set>
+#include <vector>
+
+namespace formula { class DoubleVectorRefToken; }
+namespace formula { class FormulaToken; }
+struct ScCalcConfig;
+
+namespace sc::opencl {
+
+class FormulaTreeNode;
+
+/// Exceptions
+
+/// Failed in parsing
+class UnhandledToken
+{
+public:
+ UnhandledToken( const char* m, const std::string& fn, int ln );
+
+ std::string mMessage;
+ std::string mFile;
+ int mLineNumber;
+};
+
+/// Failed in marshaling
+class OpenCLError
+{
+public:
+ OpenCLError( const std::string& function, cl_int error, const std::string& file, int line );
+
+ std::string mFunction;
+ cl_int mError;
+ std::string mFile;
+ int mLineNumber;
+};
+
+/// Inconsistent state
+class Unhandled
+{
+public:
+ Unhandled( const std::string& fn, int ln );
+
+ std::string mFile;
+ int mLineNumber;
+};
+
+class InvalidParameterCount
+{
+public:
+ InvalidParameterCount( int parameterCount, const std::string& file, int ln );
+
+ int mParameterCount;
+ std::string mFile;
+ int const mLineNumber;
+};
+
+// Helper macro to be used in code emitting OpenCL code for Calc functions.
+// Requires the vSubArguments parameter.
+#define CHECK_PARAMETER_COUNT(min, max) \
+ do { \
+ const int count = vSubArguments.size(); \
+ if( count < ( min ) || count > ( max )) \
+ throw InvalidParameterCount( count, __FILE__, __LINE__ ); \
+ } while( false )
+
+typedef std::shared_ptr<FormulaTreeNode> FormulaTreeNodeRef;
+
+class FormulaTreeNode
+{
+public:
+ explicit FormulaTreeNode( const formula::FormulaToken* ft ) : mpCurrentFormula(ft)
+ {
+ Children.reserve(8);
+ }
+ std::vector<FormulaTreeNodeRef> Children;
+ formula::FormulaToken* GetFormulaToken() const
+ {
+ return const_cast<formula::FormulaToken*>(mpCurrentFormula.get());
+ }
+
+private:
+ formula::FormulaConstTokenRef mpCurrentFormula;
+};
+
+/// (Partially) abstract base class for an operand
+class DynamicKernelArgument
+{
+public:
+ /// delete copy constructor
+ DynamicKernelArgument( const DynamicKernelArgument& ) = delete;
+
+ /// delete copy-assignment operator
+ const DynamicKernelArgument& operator=( const DynamicKernelArgument& ) = delete;
+
+ DynamicKernelArgument( const ScCalcConfig& config, const std::string& s, const FormulaTreeNodeRef& ft );
+ virtual ~DynamicKernelArgument() {}
+
+ /// Generate declaration
+ virtual void GenDecl( std::stringstream& ss ) const = 0;
+
+ /// When declared as input to a sliding window function
+ virtual void GenSlidingWindowDecl( std::stringstream& ss ) const = 0;
+
+ /// When referenced in a sliding window function
+ virtual std::string GenSlidingWindowDeclRef( bool = false ) const = 0;
+
+ /// Create buffer and pass the buffer to a given kernel
+ virtual size_t Marshal( cl_kernel, int, int, cl_program ) = 0;
+
+ virtual size_t GetWindowSize() const = 0;
+
+ /// When Mix, it will be called
+ virtual std::string GenDoubleSlidingWindowDeclRef( bool = false ) const;
+
+ /// When Mix, it will be called
+ virtual std::string GenStringSlidingWindowDeclRef( bool = false ) const;
+
+ /// Generate use/references to the argument
+ virtual void GenDeclRef( std::stringstream& ss ) const;
+
+ virtual void GenSlidingWindowFunction( std::stringstream& );
+ formula::FormulaToken* GetFormulaToken() const;
+ virtual std::string DumpOpName() const;
+ virtual void DumpInlineFun( std::set<std::string>&, std::set<std::string>& ) const;
+ const std::string& GetName() const;
+ virtual bool NeedParallelReduction() const;
+ /// If there's actually no argument, i.e. it expands to no code.
+ virtual bool IsEmpty() const { return false; }
+
+protected:
+ const ScCalcConfig& mCalcConfig;
+ std::string mSymName;
+ FormulaTreeNodeRef mFormulaTree;
+};
+
+typedef std::shared_ptr<DynamicKernelArgument> DynamicKernelArgumentRef;
+
+/// Holds an input (read-only) argument reference to a SingleVectorRef.
+/// or a DoubleVectorRef for non-sliding-window argument of complex functions
+/// like SumOfProduct
+/// In most of the cases the argument is introduced
+/// by a Push operation in the given RPN.
+class VectorRef : public DynamicKernelArgument
+{
+public:
+ VectorRef( const ScCalcConfig& config, const std::string& s, const FormulaTreeNodeRef& ft, int index = 0 );
+ virtual ~VectorRef() override;
+
+ /// Generate declaration
+ virtual void GenDecl( std::stringstream& ss ) const override;
+ /// When declared as input to a sliding window function
+ virtual void GenSlidingWindowDecl( std::stringstream& ss ) const override;
+
+ /// When referenced in a sliding window function
+ virtual std::string GenSlidingWindowDeclRef( bool = false ) const override;
+
+ /// Create buffer and pass the buffer to a given kernel
+ virtual size_t Marshal( cl_kernel, int, int, cl_program ) override;
+
+ virtual void GenSlidingWindowFunction( std::stringstream& ) override;
+ virtual size_t GetWindowSize() const override;
+ virtual std::string DumpOpName() const override;
+ virtual void DumpInlineFun( std::set<std::string>&, std::set<std::string>& ) const override;
+ const std::string& GetName() const;
+ cl_mem GetCLBuffer() const;
+ virtual bool NeedParallelReduction() const override;
+
+protected:
+ // Used by marshaling
+ cl_mem mpClmem;
+ // index in multiple double vector refs that have multiple ranges
+ const int mnIndex;
+};
+
+/// Abstract class for code generation
+class OpBase
+{
+public:
+ virtual std::string GetBottom() { return "";};
+ virtual std::string Gen2( const std::string&/*lhs*/,
+ const std::string&/*rhs*/ ) const { return "";}
+ static std::string Gen( std::vector<std::string>& /*argVector*/ ) { return "";};
+ virtual std::string BinFuncName() const { return "";};
+ virtual void BinInlineFun( std::set<std::string>&,
+ std::set<std::string>& ) { }
+ virtual bool takeString() const = 0;
+ virtual bool takeNumeric() const = 0;
+ // Whether DoubleRef containing more than one column is handled properly.
+ virtual bool canHandleMultiVector() const { return false; }
+ //Continue process 'Zero' or Not(like OpMul, not continue process when meet
+ // 'Zero'
+ virtual bool ZeroReturnZero() { return false;}
+ virtual ~OpBase() { }
+};
+
+class SlidingFunctionBase : public OpBase
+{
+public:
+ typedef std::vector<DynamicKernelArgumentRef> SubArguments;
+ virtual void GenSlidingWindowFunction( std::stringstream&,
+ const std::string&, SubArguments& ) = 0;
+};
+
+class Normal : public SlidingFunctionBase
+{
+public:
+ virtual void GenSlidingWindowFunction( std::stringstream& ss,
+ const std::string& sSymName, SubArguments& vSubArguments ) override;
+ virtual bool takeString() const override { return false; }
+ virtual bool takeNumeric() const override { return true; }
+};
+
+class CheckVariables : public Normal
+{
+public:
+ static void GenTmpVariables( std::stringstream& ss, const SubArguments& vSubArguments );
+ static void CheckSubArgumentIsNan( std::stringstream& ss,
+ SubArguments& vSubArguments, int argumentNum );
+ static void CheckAllSubArgumentIsNan( std::stringstream& ss,
+ SubArguments& vSubArguments );
+ // only check isnan
+ static void CheckSubArgumentIsNan2( std::stringstream& ss,
+ SubArguments& vSubArguments, int argumentNum, const std::string& p );
+ static void UnrollDoubleVector( std::stringstream& ss,
+ const std::stringstream& unrollstr, const formula::DoubleVectorRefToken* pCurDVR,
+ int nCurWindowSize );
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/opencl/opinlinefun_finacial.cxx b/sc/source/core/opencl/opinlinefun_finacial.cxx
new file mode 100644
index 000000000..bc036f274
--- /dev/null
+++ b/sc/source/core/opencl/opinlinefun_finacial.cxx
@@ -0,0 +1,1882 @@
+/* -*- 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/.
+ */
+
+#ifndef SC_OPENCL_OPINLINFUN_finacial
+#define SC_OPENCL_OPINLINFUN_finacial
+
+std::string nCorrValDecl ="double constant nCorrVal[]"
+"= {0, 9e-1, 9e-2, 9e-3, 9e-4, 9e-5, 9e-6, 9e-7, "
+"9e-8,9e-9, 9e-10, 9e-11, 9e-12, 9e-13, 9e-14, 9e-15};\n";
+
+std::string SCdEpsilonDecl =
+"constant double SCdEpsilon = 1.0E-7;\n";
+
+std::string RoundDecl = "double Round(double fValue);\n";
+
+std::string Round =
+"double Round(double fValue)\n"
+"{\n"
+" if ( fValue == 0.0 )\n"
+" return fValue;\n"
+"\n"
+" double fFac = 0;\n"
+" int nExp;\n"
+" if ( fValue > 0.0 )\n"
+" nExp = ( floor( log10( fValue ) ) );\n"
+" else\n"
+" nExp = 0;\n"
+" int nIndex = 15 - nExp;\n"
+" if ( nIndex > 15 )\n"
+" nIndex = 15;\n"
+" else if ( nIndex <= 1 )\n"
+" nIndex = 0;\n"
+" fValue = floor( fValue + 0.5 + nCorrVal[nIndex] );\n"
+" return fValue;\n"
+"}\n";
+
+std::string GetPMT_newDecl =
+"double GetPMT_new( double fRate, double fNper, double fPv, double fFv,"
+"int nPayType );\n";
+std::string GetPMT_new=
+"double GetPMT_new( double fRate, double fNper, double fPv, double fFv,"
+"int nPayType)\n"
+"{\n"
+" double fPmt;\n"
+" double fTerm = pow( 1.0 + fRate, fNper );\n"
+" if( nPayType > 0 )\n"
+" fPmt = ( fFv * fRate *pow ( fTerm - 1.0,-1 ) + fPv * fRate *pow( "
+"( 1.0 - pow( fTerm,-1) ),-1) )* pow ( 1.0 + fRate,-1 );\n"
+" else\n"
+" fPmt = fFv * fRate *pow ( fTerm - 1.0 ,-1) + fPv * fRate *pow( "
+"1.0 - pow( fTerm,-1),-1 );\n"
+" return -fPmt;\n"
+"}\n";
+std::string GetFVDecl =
+"double GetFV( double fRate, double fNper, double fPmt,"
+"double fPv, int nPayType );\n";
+
+std::string GetFV =
+"double GetFV( double fRate, double fNper, double fPmt,"
+"double fPv, int nPayType )\n"
+"{\n"
+" double fFv;\n"
+" if( fRate == 0.0 )\n"
+" fFv = fPv + fPmt * fNper;\n"
+" else\n"
+" {\n"
+" double fTerm = pow( 1.0 + fRate, fNper );\n"
+" if( nPayType > 0 )\n"
+" fFv = fPv * fTerm + fPmt * ( 1.0 + fRate ) *( fTerm - 1.0 ) "
+"/ fRate;\n"
+" else\n"
+" fFv = fPv * fTerm + fPmt * ( fTerm - 1.0 ) / fRate;\n"
+" }\n"
+" return -fFv;\n"
+"}\n";
+
+std::string GetFV_newDecl =
+"double GetFV_new( double fRate, double fNper, double fPmt,"
+"double fPv, int nPayType );\n";
+
+std::string GetFV_new =
+"double GetFV_new( double fRate, double fNper, double fPmt,"
+"double fPv, int nPayType )\n"
+"{\n"
+" double fFv;\n"
+" double fTerm = pow( 1.0 + fRate, fNper );\n"
+" if( nPayType > 0 )\n"
+" fFv = fPv * fTerm + fPmt * ( 1.0 + fRate ) *( fTerm - 1.0 ) "
+"*pow( fRate,-1);\n"
+" else\n"
+" fFv = fPv * fTerm + fPmt * ( fTerm - 1.0 ) *pow( fRate,-1);\n"
+" return -fFv;\n"
+"}\n";
+
+std::string IsLeapYearDecl =
+"bool IsLeapYear( int n );\n";
+
+std::string IsLeapYear =
+"bool IsLeapYear( int n )\n"
+"{\n"
+" return ( (( ( n % 4 ) == 0 ) && ( ( n % 100 ) != 0)) || ( ( n % 400 ) == "
+"0 ) );\n"
+"}\n";
+
+std::string DaysInMonthDecl=
+"int DaysInMonth( int nMonth, int nYear );\n";
+
+std::string DaysInMonth =
+"int DaysInMonth( int nMonth, int nYear )\n"
+"{\n"
+" int aDaysInMonth[12] = { 31, 28, 31, 30, 31, 30,\n"
+" 31, 31, 30, 31, 30, 31 };\n"
+"\n"
+" if ( nMonth != 2 )\n"
+" return aDaysInMonth[nMonth-1];\n"
+" else\n"
+" {\n"
+" if ( IsLeapYear(nYear) )\n"
+" return aDaysInMonth[nMonth-1] + 1;\n"
+" else\n"
+" return aDaysInMonth[nMonth-1];\n"
+" }\n"
+"}\n";
+std::string DaysInMonth_newDecl=
+"int DaysInMonth( int nMonth, int nYear );\n";
+
+std::string DaysInMonth_new =
+"int DaysInMonth( int nMonth, int nYear )\n"
+"{\n"
+" int tmp = 0;\n"
+" switch(nMonth)\n"
+" {\n"
+" case 1:\n"
+" case 3:\n"
+" case 5:\n"
+" case 7:\n"
+" case 8:\n"
+" case 10:\n"
+" case 12:\n"
+" tmp = 31;\n"
+" break;\n"
+" case 4:\n"
+" case 6:\n"
+" case 9:\n"
+" case 11:\n"
+" tmp =30;\n"
+" break;\n"
+" case 2:\n"
+" if ( IsLeapYear(nYear)==1)\n"
+" tmp = 29;\n"
+" else\n"
+" tmp = 28;\n"
+" break;\n"
+" }\n"
+" return tmp;\n"
+"}\n";
+
+std::string DaysToDateDecl =
+"void DaysToDate( int nDays, int *rDay, int* rMonth, int* rYear );\n";
+
+std::string DaysToDate =
+"void DaysToDate( int nDays, int *rDay, int* rMonth, int* rYear )\n"
+"{\n"
+"\n"
+" int nTempDays;\n"
+" int i = 0;\n"
+" bool bCalc;\n"
+
+" do\n"
+" {\n"
+" nTempDays = nDays;\n"
+" *rYear = (int)((nTempDays / 365) - i);\n"
+" nTempDays -= ((int) *rYear -1) * 365;\n"
+" nTempDays -= (( *rYear -1) / 4) - (( *rYear -1) / 100) +"
+"((*rYear -1) / 400);\n"
+" bCalc = false;\n"
+" if ( nTempDays < 1 )\n"
+" {\n"
+" i++;\n"
+" bCalc = true;\n"
+" }\n"
+" else\n"
+" {\n"
+" if ( nTempDays > 365 )\n"
+" {\n"
+" if ( (nTempDays != 366) || !IsLeapYear( *rYear ) )\n"
+" {\n"
+" i--;\n"
+" bCalc = true;\n"
+" }\n"
+" }\n"
+" }\n"
+" }\n"
+" while ( bCalc );\n"
+" if(nTempDays!=0){\n"
+" for (*rMonth = 1; (int)nTempDays > DaysInMonth( *rMonth, *rYear );"
+"*rMonth+=1)\n"
+" {\n"
+" nTempDays -= DaysInMonth( *rMonth, *rYear );\n"
+" }\n"
+" *rDay = (int)nTempDays;\n"
+" }\n"
+"}\n";
+
+std::string DateToDaysDecl=
+"int DateToDays( int nDay, int nMonth, int nYear );\n";
+
+std::string DateToDays=
+"int DateToDays( int nDay, int nMonth, int nYear )\n"
+"{\n"
+" int nDays = ((int)nYear-1) * 365;\n"
+" nDays += ((nYear-1) / 4) - ((nYear-1) / 100) + ((nYear-1) / 400);\n"
+" for( int i = 1; i < nMonth; i++ )\n"
+" nDays += DaysInMonth(i,nYear);\n"
+" nDays += nDay;\n"
+"\n"
+" return nDays;\n"
+"}\n";
+
+std::string DateToDays_newDecl=
+"int DateToDays_new( int nDay, int nMonth, int nYear );\n";
+
+std::string DateToDays_new=
+"int DateToDays_new( int nDay, int nMonth, int nYear )\n"
+"{\n"
+" int nDays = (nYear-1) * 365;\n"
+" nDays += (int)((nYear-1) *pow(4.0,-1.0)- (nYear-1) *pow( 100.0,-1.0)"
+"+ (nYear-1) *pow(400.0,-1.0));\n"
+" for( int i = 1; i < nMonth; i++ )\n"
+" nDays += DaysInMonth(i,nYear);\n"
+" nDays += nDay;\n"
+"\n"
+" return nDays;\n"
+"}\n";
+
+std::string GetNullDateDecl=
+"int GetNullDate();\n";
+
+std::string GetNullDate=
+"int GetNullDate()\n"
+"{\n"
+" return DateToDays(30,12,1899 );\n"
+"}\n";
+std::string GetNullDate_newDecl=
+"int GetNullDate_new();\n";
+
+std::string GetNullDate_new=
+"int GetNullDate_new()\n"
+"{\n"
+" return DateToDays_new(30,12,1899 );\n"
+"}\n";
+
+std::string ScaDateDecl=
+"void ScaDate( int nNullDate, int nDate, int nBase,int *nOrigDay, "
+"int *nMonth,int *nYear,int *bLastDayMode,int *bLastDay,"
+"int *b30Days,int *bUSMode,int *nDay);\n";
+
+std::string ScaDate=
+"void ScaDate( int nNullDate, int nDate, int nBase,int *nOrigDay, "
+"int *nMonth,int *nYear,int *bLastDayMode,int *bLastDay,"
+"int *b30Days,int *bUSMode,int *nDay)\n"
+"{\n"
+" DaysToDate( nNullDate + nDate, nOrigDay, nMonth, nYear );\n"
+" *bLastDayMode = (nBase != 5);\n"
+" *bLastDay = (*nOrigDay >= DaysInMonth( *nMonth, *nYear ));\n"
+" *b30Days = (nBase == 0) || (nBase == 4);\n"
+" *bUSMode = (nBase == 0);\n"
+" if( *b30Days)\n"
+" {\n"
+" *nDay = min( *nOrigDay, 30);\n"
+" if( *bLastDay || (*nDay >=DaysInMonth( *nMonth, *nYear )) )\n"
+" *nDay = 30;\n"
+" }\n"
+" else\n"
+" {\n"
+" int nLastDay = DaysInMonth( *nMonth, *nYear );\n"
+" *nDay = *bLastDay ? nLastDay : min( *nOrigDay, nLastDay );\n"
+" }\n"
+"}\n";
+
+std::string lcl_GetCouppcdDecl=
+"int lcl_GetCouppcd(int nNullDate,int nSettle,int nMat,int nFreq,int nBase);\n";
+
+std::string lcl_GetCouppcd=
+"int lcl_GetCouppcd(int nNullDate,int nSettle, int nMat,int nFreq,int nBase)\n"
+"{\n"
+" int aDate = nMat;\n"
+" int rDay=0,rMonth=0, rYear=0,rbLastDayMode=0, rbLastDay=0,rb30Days=0,"
+"rbUSMode=0,rnDay=0;\n"
+" int sDay=0,sMonth=0, sYear=0,sbLastDayMode=0, sbLastDay=0,sb30Days=0,"
+"sbUSMode=0,snDay=0;\n"
+" ScaDate( nNullDate,nSettle,nBase,&sDay,&sMonth,&sYear,&sbLastDayMode,"
+"&sbLastDay,&sb30Days,&sbUSMode,&snDay);\n"
+" ScaDate( nNullDate,aDate,nBase,&rDay,&rMonth,&rYear,&rbLastDayMode,"
+"&rbLastDay,&rb30Days,&rbUSMode,&rnDay);\n"
+" rYear=sYear;\n"
+" setDay(rDay,rMonth,rYear,rbLastDay,rb30Days,&rnDay);\n"
+" if(checklessthan(rYear,sYear,rMonth,sMonth,rnDay,snDay,rbLastDay,"
+"sbLastDay,rDay,sDay))\n"
+" {\n"
+" rYear+=1;\n"
+" setDay(rDay,rMonth,rYear,rbLastDay,rb30Days,&rnDay);\n"
+" }\n"
+" while(checklessthan(sYear,rYear,sMonth,rMonth,snDay,rnDay,sbLastDay,"
+"rbLastDay,sDay,rDay))\n"
+" {\n"
+" double d = -1*(12/nFreq);\n"
+" addMonths(rb30Days,rbLastDay,&rnDay,rDay,&rMonth,d,&rYear);\n"
+" }\n"
+" int nLastDay = DaysInMonth( rMonth, rYear );\n"
+" int nRealDay = (rbLastDayMode && rbLastDay) ? nLastDay :"
+"min( nLastDay, rDay );\n"
+" return DateToDays( nRealDay, rMonth, rYear ) - nNullDate;\n"
+"}\n";
+
+std::string lcl_GetCoupncdDecl=
+"int lcl_GetCoupncd(int nNullDate,int nSettle,int nMat,int nFreq,int nBase);\n";
+
+std::string lcl_GetCoupncd=
+"int lcl_GetCoupncd(int nNullDate,int nSettle, int nMat,int nFreq,int nBase)\n"
+"{\n"
+" int aDate = nMat;\n"
+" int rDay=0,rMonth=0, rYear=0,rbLastDayMode=0, rbLastDay=0,rb30Days=0,"
+"rbUSMode=0,rnDay=0;\n"
+" int sDay=0,sMonth=0, sYear=0,sbLastDayMode=0, sbLastDay=0,sb30Days=0,"
+"sbUSMode=0,snDay=0;\n"
+" ScaDate( nNullDate,nSettle,nBase,&sDay,&sMonth,&sYear,&sbLastDayMode,"
+"&sbLastDay,&sb30Days,&sbUSMode,&snDay);\n"
+" ScaDate( nNullDate,aDate,nBase,&rDay,&rMonth,&rYear,&rbLastDayMode,"
+"&rbLastDay,&rb30Days,&rbUSMode,&rnDay);\n"
+" rYear=sYear;\n"
+" setDay(rDay,rMonth,rYear,rbLastDay,rb30Days,&rnDay);\n"
+" if(checklessthan(sYear,rYear,sMonth,rMonth,snDay,rnDay,sbLastDay,rbLastDay"
+",sDay,rDay))\n"
+" {\n"
+" rYear-=1;\n"
+" setDay(rDay,rMonth,rYear,rbLastDay,rb30Days,&rnDay);\n"
+" }\n"
+" while(!checklessthan(sYear,rYear,sMonth,rMonth,snDay,rnDay,sbLastDay,"
+"rbLastDay,sDay,rDay))\n"
+" {\n"
+" addMonths(rb30Days,rbLastDay,&rnDay,rDay,&rMonth,12/nFreq,&rYear);\n"
+" }\n"
+" int nLastDay = DaysInMonth( rMonth, rYear );\n"
+" int nRealDay = (rbLastDayMode && rbLastDay) ? nLastDay :"
+"min( nLastDay, rDay );\n"
+" return DateToDays( nRealDay, rMonth, rYear ) - nNullDate;\n"
+"}\n";
+
+std::string addMonthsDecl=
+"void addMonths(int b30Days,int bLastDay,int *nDay,int nOrigDay,"
+"int *nMonth,int nMonthCount,int *year);\n";
+
+std::string addMonths=
+"void addMonths(int b30Days,int bLastDay,int *nDay,int nOrigDay,"
+"int *nMonth,int nMonthCount,int *year)\n"
+"{\n"
+" int nNewMonth = nMonthCount + *nMonth;\n"
+" if( nNewMonth > 12 )\n"
+" {\n"
+" --nNewMonth;\n"
+" *year+=nNewMonth / 12 ;\n"
+" *nMonth = ( nNewMonth % 12 ) + 1;\n"
+" }\n"
+" else if( nNewMonth < 1 )\n"
+" {\n"
+" *year+= nNewMonth / 12 - 1 ;\n"
+" *nMonth = nNewMonth % 12 + 12 ;\n"
+" }\n"
+" else\n"
+" *nMonth = nNewMonth ;\n"
+" if( b30Days )\n"
+" {\n"
+" *nDay = min( nOrigDay, 30);\n"
+" if( bLastDay || (*nDay >= DaysInMonth( *nMonth, *year )) )\n"
+" *nDay = 30;\n"
+" }\n"
+" else\n"
+" {\n"
+" int nLastDay = DaysInMonth( *nMonth, *year );\n"
+" *nDay = bLastDay ? nLastDay : min( nOrigDay, nLastDay );\n"
+" }\n"
+"}\n";
+
+std::string getDaysInMonthRangeDecl=
+"int getDaysInMonthRange( int nFrom, int nTo,int b30Days,int year);\n";
+
+std::string getDaysInMonthRange=
+"int getDaysInMonthRange( int nFrom, int nTo,int b30Days,int year)\n"
+"{\n"
+" if( nFrom > nTo )\n"
+" return 0;\n"
+" int nRet = 0;\n"
+" if( b30Days )\n"
+" nRet = (nTo - nFrom + 1) * 30;\n"
+" else\n"
+" {\n"
+" for( int nMonthIx = nFrom; nMonthIx <= nTo; ++nMonthIx )\n"
+" nRet += b30Days ? 30 : DaysInMonth( nMonthIx, year );\n"
+" }\n"
+" return nRet;\n"
+"}\n";
+
+std::string GetDaysInYearsDecl=
+"int GetDaysInYears( int nYear1, int nYear2 );\n";
+
+std::string GetDaysInYears=
+"int GetDaysInYears( int nYear1, int nYear2 )\n"
+"{\n"
+" int nLeaps = 0;\n"
+" for( int n = nYear1 ; n <= nYear2 ; n++ )\n"
+" {\n"
+" if( IsLeapYear( n ) )\n"
+" nLeaps++;\n"
+" }\n"
+" int nSum = 1;\n"
+" nSum += nYear2;\n"
+" nSum -= nYear1;\n"
+" nSum *= 365;\n"
+" nSum += nLeaps;\n"
+" return nSum;\n"
+"}\n";
+
+std::string GetDaysInYearDecl=
+"int GetDaysInYear( int nNullDate, int nDate, int nMode );\n";
+
+std::string GetDaysInYear=
+"int GetDaysInYear( int nNullDate, int nDate, int nMode )\n"
+"{\n"
+" switch( nMode )\n"
+" {\n"
+" case 0:\n"
+" case 2:\n"
+" case 4:\n"
+" return 360;\n"
+" case 1:\n"
+" {\n"
+" int nD=0, nM=0, nY=0;\n"
+" nDate += nNullDate;\n"
+" DaysToDate( nDate, &nD, &nM, &nY );\n"
+" return IsLeapYear( nY )? 366 : 365;\n"
+" }\n"
+" case 3:\n"
+" return 365;\n"
+" }\n"
+"}\n";
+
+std::string getDaysInYearRangeDecl =
+"int getDaysInYearRange( int nFrom, int nTo,int b30Days );\n";
+
+std::string getDaysInYearRange=
+"int getDaysInYearRange( int nFrom, int nTo,int b30Days )\n"
+"{\n"
+" if( nFrom > nTo )\n"
+" return 0;\n"
+" return b30Days ? ((nTo - nFrom + 1) * 360) : GetDaysInYears( nFrom, nTo)"
+";\n"
+"}\n";
+
+std::string getDiffDecl=
+"int getDiff(int rFrom,int rTo,int fDay,int fMonth,int fYear,int fbLastDayMode,"
+"int fbLastDay,int fb30Days,int fbUSMode,int fnDay,int tDay,int tMonth,"
+"int tYear,int tbLastDayMode,int tbLastDay,int tb30Days,"
+"int tbUSMode,int tnDay);\n";
+
+std::string getDiff=
+"int getDiff(int rFrom,int rTo,int fDay,int fMonth,int fYear,int fbLastDayMode,"
+"int fbLastDay,int fb30Days,int fbUSMode,int fnDay,int tDay,int tMonth,"
+"int tYear,int tbLastDayMode,int tbLastDay,int tb30Days,"
+"int tbUSMode,int tnDay)\n"
+"{\n"
+" if(rFrom>rTo)\n"
+" {\n"
+" int d=fDay;fDay=tDay;tDay=d;\n"
+" int m=fMonth;fMonth=tMonth;tMonth=m;\n"
+" int y=fYear;fYear=tYear;tYear=y;\n"
+" int a=fbLastDayMode;fbLastDayMode=tbLastDayMode;tbLastDayMode=a;\n"
+" int b=fbLastDay;fbLastDay=tbLastDay;tbLastDay=b;\n"
+" int c=fb30Days;fb30Days=tb30Days;tb30Days=c;\n"
+" int e=fbUSMode;fbUSMode=tbUSMode;tbUSMode=e;\n"
+" int f=fnDay;fnDay=tnDay;tnDay=f;\n"
+" }\n"
+" int nDiff=0;\n"
+" if( tb30Days )\n"
+" {\n"
+" if( tbUSMode )\n"
+" {\n"
+" if( ((fMonth == 2) || (fnDay < 30)) && (tDay == 31) )\n"
+" tnDay = 31;\n"
+" else if( (tMonth == 2) && tbLastDay )\n"
+" tnDay = DaysInMonth( 2, tYear );\n"
+" }\n"
+" else\n"
+" {\n"
+" if( (fMonth == 2) && (fnDay == 30) )\n"
+" fnDay = DaysInMonth( 2, fYear );\n"
+" if( (tMonth == 2) && (tnDay == 30) )\n"
+" tnDay = DaysInMonth( 2, tYear );\n"
+" }\n"
+" }\n"
+" if( (fYear < tYear) || ((fYear == tYear) && (fMonth < tMonth)) )\n"
+" {\n"
+" int d = fb30Days ? 30:DaysInMonth(fMonth,fYear);\n"
+" nDiff = d- fnDay + 1;\n"
+" fDay = fnDay = 1;\n"
+" fbLastDay = 0;\n"
+" addMonths(fb30Days,fbLastDay,&fnDay,fDay,&fMonth,1,&fYear);\n"
+" if( fYear < tYear )\n"
+" {\n"
+" nDiff += getDaysInMonthRange( fMonth, 12,fb30Days,fYear);\n"
+" addMonths(fb30Days,fbLastDay,&fnDay,fDay,&fMonth,13-fMonth,&fYear"
+");\n"
+" nDiff += getDaysInYearRange( fYear, tYear - 1,fb30Days);\n"
+" fYear+=tYear - fYear;\n"
+" }\n"
+" nDiff += getDaysInMonthRange(fMonth, tMonth - 1,fb30Days ,fYear );\n"
+" addMonths(fb30Days,fbLastDay,&fnDay,fDay,&fMonth,tMonth-fMonth,&fYear"
+");\n"
+" }\n"
+" nDiff += tnDay - fnDay;\n"
+" return nDiff > 0 ? nDiff : 0;\n"
+"}\n";
+
+std::string lcl_GetcoupdaybsDecl=
+"int lcl_Getcoupdaybs(int nNullDate,int nSettle, int nMat,int nFreq,int nBase);\n";
+
+std::string lcl_Getcoupdaybs=
+"int lcl_Getcoupdaybs(int nNullDate,int nSettle, int nMat,int nFreq,int nBase)\n"
+"{\n"
+" int aDate = nMat;\n"
+" int rDay=0,rMonth=0, rYear=0;int mDay=0,mMonth=0, mYear=0;int sDay=0,"
+"sMonth=0, sYear=0;\n"
+" int rbLastDayMode=0, rbLastDay=0,rb30Days=0,rbUSMode=0,rnDay=0;\n"
+" int sbLastDayMode=0, sbLastDay=0,sb30Days=0,sbUSMode=0,snDay=0;\n"
+" ScaDate( nNullDate,aDate,nBase,&rDay,&rMonth,&rYear,&rbLastDayMode,&"
+"rbLastDay,&rb30Days,&rbUSMode,&rnDay);\n"
+" ScaDate( nNullDate,nSettle,nBase,&sDay,&sMonth,&sYear,&sbLastDayMode,&"
+"sbLastDay,&sb30Days,&sbUSMode,&snDay);\n"
+" rYear= sYear;\n"
+" nSettle=nSettle+nNullDate;\n"
+" aDate=DateToDays( rDay,rMonth,rYear );\n"
+" if( aDate < nSettle )\n"
+" {\n"
+" rYear+= 1;\n"
+" aDate=DateToDays( rDay,rMonth,rYear );\n"
+" }\n"
+" while(aDate > nSettle )\n"
+" {\n"
+" addMonths(rb30Days,rbLastDay,&rnDay,rDay,&rMonth,-1*(12/nFreq),&rYear"
+");\n"
+" aDate=DateToDays( rDay,rMonth,rYear );\n"
+" }\n"
+" return getDiff( aDate, nSettle, rDay, rMonth, rYear, rbLastDayMode, "
+"rbLastDay, rb30Days, rbUSMode, rnDay, sDay, sMonth, sYear, sbLastDayMode,"
+"sbLastDay, sb30Days, sbUSMode, snDay);\n"
+"}\n";
+
+std::string lcl_Getcoupdaybs_newDecl=
+"int lcl_Getcoupdaybs_new(int nNullDate,int nSettle,int nMat,int nFreq,"
+"int nBase);\n";
+
+std::string lcl_Getcoupdaybs_new=
+"int lcl_Getcoupdaybs_new(int nNullDate,int nSettle,int nMat,int nFreq,"
+"int nBase)\n"
+"{\n"
+" int aDate = nMat;\n"
+" int rDay=0,rMonth=0, rYear=0,rbLastDayMode=0, rbLastDay=0,rb30Days=0,"
+"rbUSMode=0,rnDay=0;\n"
+" int sDay=0,sMonth=0, sYear=0,sbLastDayMode=0, sbLastDay=0,sb30Days=0,"
+"sbUSMode=0,snDay=0;\n"
+" ScaDate( nNullDate,nSettle,nBase,&sDay,&sMonth,&sYear,&sbLastDayMode,"
+"&sbLastDay,&sb30Days,&sbUSMode,&snDay);\n"
+" ScaDate( nNullDate,aDate,nBase,&rDay,&rMonth,&rYear,&rbLastDayMode,"
+"&rbLastDay,&rb30Days,&rbUSMode,&rnDay);\n"
+" rYear=sYear;\n"
+" setDay(rDay,rMonth,rYear,rbLastDay,rb30Days,&rnDay);\n"
+" aDate=DateToDays_new( rnDay,rMonth,rYear);\n"
+" if(checklessthan(rYear,sYear,rMonth,sMonth,rnDay,snDay,rbLastDay,"
+"sbLastDay,rDay,sDay))\n"
+" {\n"
+" rYear+=1;\n"
+" setDay(rDay,rMonth,rYear,rbLastDay,rb30Days,&rnDay);\n"
+" aDate=DateToDays_new( rnDay,rMonth,rYear );\n"
+" }\n"
+" while(checklessthan(sYear,rYear,sMonth,rMonth,snDay,rnDay,sbLastDay,"
+"rbLastDay,sDay,rDay))\n"
+" {\n"
+" double d = -1*(12/nFreq);\n"
+" addMonths(rb30Days,rbLastDay,&rnDay,rDay,&rMonth,d,&rYear);\n"
+" aDate=DateToDays_new( rnDay,rMonth,rYear );\n"
+" }\n"
+" return getDiff( aDate,nSettle+nNullDate,rDay,rMonth,rYear,rbLastDayMode,"
+"rbLastDay,rb30Days,rbUSMode,rnDay,sDay,sMonth,sYear,sbLastDayMode,sbLastDay,"
+"sb30Days,sbUSMode, snDay);\n"
+"}\n";
+
+std::string lcl_GetcoupdaysDecl=
+"int lcl_Getcoupdays(int nNullDate,int nSettle, "
+"int nMat,int nFreq,int nBase);\n";
+
+std::string lcl_Getcoupdays=
+"int lcl_Getcoupdays(int nNullDate,int nSettle, "
+"int nMat,int nFreq,int nBase)\n"
+"{\n"
+" int aDate = nMat;\n"
+" int rDay=0,rMonth=0, rYear=0;int mDay=0,mMonth=0, mYear=0;int sDay=0,"
+"sMonth=0, sYear=0;\n"
+" int rbLastDayMode=0, rbLastDay=0,rb30Days=0,rbUSMode=0,rnDay=0;\n"
+" int sbLastDayMode=0, sbLastDay=0,sb30Days=0,sbUSMode=0,snDay=0;\n"
+" ScaDate( nNullDate,aDate,nBase,&rDay,&rMonth,&rYear,&rbLastDayMode,&"
+"rbLastDay,&rb30Days,&rbUSMode,&rnDay);\n"
+" ScaDate( nNullDate,nSettle,nBase,&sDay,&sMonth,&sYear,&sbLastDayMode,&"
+"sbLastDay,&sb30Days,&sbUSMode,&snDay);\n"
+" rYear= sYear;\n"
+" nSettle=nSettle+nNullDate;\n"
+" aDate=DateToDays( rDay,rMonth,rYear );\n"
+" if( aDate < nSettle )\n"
+" { \n"
+" rYear+= 1;\n"
+" aDate=DateToDays( rDay,rMonth,rYear );\n"
+" }\n"
+" while(aDate > nSettle )\n"
+" {\n"
+" addMonths(rb30Days,rbLastDay,&rnDay,rDay,&rMonth,-1*(12/nFreq),&rYear"
+");\n"
+" aDate=DateToDays( rDay,rMonth,rYear );\n"
+" }\n"
+" int aNextDate=aDate;int aDay=rDay,aMonth=rMonth, aYear=rYear;\n"
+" int abLastDayMode=rbLastDayMode, abLastDay=rbLastDay,ab30Days=rb30Days,"
+"abUSMode=rbUSMode,anDay=rnDay;\n"
+" addMonths(ab30Days,abLastDay,&anDay,aDay,&aMonth,12/nFreq,&aYear);\n"
+" return getDiff( aDate, aNextDate, rDay, rMonth, rYear, rbLastDayMode, "
+"rbLastDay, rb30Days, rbUSMode, rnDay, aDay, aMonth, aYear, abLastDayMode,"
+"abLastDay, ab30Days, abUSMode, anDay);\n"
+"}\n";
+
+std::string lcl_Getcoupdays_newDecl=
+"int lcl_Getcoupdays_new(int nNullDate,int nSettle, "
+"int nMat,int nFreq,int nBase);\n";
+
+std::string lcl_Getcoupdays_new=
+"int lcl_Getcoupdays_new(int nNullDate,int nSettle, "
+"int nMat,int nFreq,int nBase)\n"
+"{\n"
+" int aDate = nMat;\n"
+" int rDay=0,rMonth=0, rYear=0,rbLastDayMode=0, rbLastDay=0,rb30Days=0,"
+"rbUSMode=0,rnDay=0;\n"
+" int sDay=0,sMonth=0, sYear=0,sbLastDayMode=0, sbLastDay=0,sb30Days=0,"
+"sbUSMode=0,snDay=0;\n"
+" ScaDate( nNullDate,nSettle,nBase,&sDay,&sMonth,&sYear,&sbLastDayMode,"
+"&sbLastDay,&sb30Days,&sbUSMode,&snDay);\n"
+" ScaDate( nNullDate,aDate,nBase,&rDay,&rMonth,&rYear,&rbLastDayMode,"
+"&rbLastDay,&rb30Days,&rbUSMode,&rnDay);\n"
+" rYear=sYear;\n"
+" setDay(rDay,rMonth,rYear,rbLastDay,rb30Days,&rnDay);\n"
+" aDate=DateToDays_new( rnDay,rMonth,rYear);\n"
+" if(checklessthan(rYear,sYear,rMonth,sMonth,rnDay,snDay,rbLastDay,"
+"sbLastDay,rDay,sDay))\n"
+" {\n"
+" rYear+=1;\n"
+" setDay(rDay,rMonth,rYear,rbLastDay,rb30Days,&rnDay);\n"
+" aDate=DateToDays_new( rnDay,rMonth,rYear );\n"
+" }\n"
+" while(checklessthan(sYear,rYear,sMonth,rMonth,snDay,rnDay,sbLastDay,"
+"rbLastDay,sDay,rDay))\n"
+" {\n"
+" double d = -1*12*pow((double)nFreq,-1.0);\n"
+" addMonths(rb30Days,rbLastDay,&rnDay,rDay,&rMonth,d,&rYear);\n"
+" aDate=DateToDays_new( rnDay,rMonth,rYear );\n"
+" }\n"
+" int aNextDate=aDate;int aDay=rDay,aMonth=rMonth, aYear=rYear;\n"
+" int abLastDayMode=rbLastDayMode, abLastDay=rbLastDay,ab30Days=rb30Days,"
+"abUSMode=rbUSMode,anDay=rnDay;\n"
+" int tmp = (int)(12*pow((double)nFreq,-1.0));\n"
+" addMonths(ab30Days,abLastDay,&anDay,aDay,&aMonth,tmp,&aYear);\n"
+" return getDiff( aDate, aNextDate, rDay, rMonth, rYear, rbLastDayMode, "
+"rbLastDay, rb30Days, rbUSMode, rnDay, aDay, aMonth, aYear, abLastDayMode,"
+"abLastDay, ab30Days, abUSMode, anDay);\n"
+"}\n";
+
+std::string lcl_GetcoupnumDecl=
+"int lcl_Getcoupnum(int nNullDate,int nSettle, int nMat,int nFreq);\n";
+
+std::string lcl_Getcoupnum=
+"int lcl_Getcoupnum(int nNullDate,int nSettle, int nMat,int nFreq)\n"
+"{\n"
+" int aDate = nMat;int rDay=0,rMonth=0, rYear=0;int mDay=0,mMonth=0, mYear="
+"0;\n"
+" int sDay=0,sMonth=0, sYear=0;\n"
+" DaysToDate(aDate+nNullDate,&rDay, &rMonth, &rYear );\n"
+" DaysToDate(nMat+nNullDate,&mDay, &mMonth, &mYear );\n"
+" DaysToDate(nSettle+nNullDate,&sDay, &sMonth, &sYear );\n"
+" rYear= sYear;\n"
+" nSettle=nSettle+nNullDate;\n"
+" aDate=DateToDays( rDay,rMonth,rYear );\n"
+" if( aDate < nSettle )\n"
+" rYear+= 1;\n"
+" int d=DateToDays( rDay,rMonth,rYear );\n"
+" int nMonthCount=-1*(12 / nFreq);\n"
+" while(d > nSettle )\n"
+" {\n"
+" int nNewMonth = nMonthCount + rMonth;\n"
+" if( nNewMonth > 12 )\n"
+" {\n"
+" --nNewMonth;\n"
+" rYear+=nNewMonth / 12;\n"
+" rMonth = nNewMonth % 12 + 1;\n"
+" }\n"
+" else if( nNewMonth < 1 )\n"
+" {\n"
+" rYear+= nNewMonth / 12 - 1;\n"
+" rMonth = nNewMonth % 12 + 12;\n"
+" }\n"
+" else\n"
+" rMonth = nNewMonth;\n"
+" d=DateToDays( rDay,rMonth,rYear );\n"
+" }\n"
+" int n=(mYear-rYear)*12+mMonth-rMonth;\n"
+" n=n*nFreq/12;\n"
+" return n;\n"
+"}\n";
+std::string lcl_Getcoupnum_newDecl=
+"double lcl_Getcoupnum_new(int nNullDate,int nSettle,int nMat,int nFreq,int"
+" nBase);\n";
+std::string lcl_Getcoupnum_new=
+"double lcl_Getcoupnum_new(int nNullDate,int nSettle, int nMat,int nFreq,int"
+" nBase)\n"
+"{\n"
+" int aDate = nMat;\n"
+" int mDay=0,mMonth=0, mYear=0;\n"
+" int rDay=0,rMonth=0, rYear=0,rbLastDayMode=0, rbLastDay=0,rb30Days=0,"
+"rbUSMode=0,rnDay=0;\n"
+" int sDay=0,sMonth=0, sYear=0,sbLastDayMode=0, sbLastDay=0,sb30Days=0,"
+"sbUSMode=0,snDay=0;\n"
+" ScaDate( nNullDate,nSettle,nBase,&sDay,&sMonth,&sYear,&sbLastDayMode,"
+"&sbLastDay,&sb30Days,&sbUSMode,&snDay);\n"
+" ScaDate( nNullDate,aDate,nBase,&rDay,&rMonth,&rYear,&rbLastDayMode,"
+"&rbLastDay,&rb30Days,&rbUSMode,&rnDay);\n"
+" mMonth = rMonth, mYear = rYear;\n"
+" rYear=sYear;\n"
+" setDay(rDay,rMonth,rYear,rbLastDay,rb30Days,&rnDay);\n"
+" if(checklessthan(rYear,sYear,rMonth,sMonth,rnDay,snDay,rbLastDay,"
+"sbLastDay,rDay,sDay))\n"
+" {\n"
+" rYear+=1;\n"
+" setDay(rDay,rMonth,rYear,rbLastDay,rb30Days,&rnDay);\n"
+" }\n"
+" int m= checklessthan(sYear,rYear,sMonth,rMonth,snDay,rnDay,sbLastDay,"
+"rbLastDay,sDay,rDay);\n"
+" while(m)\n"
+" {\n"
+" double d = -1*(12/nFreq);\n"
+" addMonths(rb30Days,rbLastDay,&rnDay,rDay,&rMonth,d,&rYear);\n"
+" m = checklessthan(sYear,rYear,sMonth,rMonth,snDay,rnDay,sbLastDay,"
+"rbLastDay,sDay,rDay);\n"
+" }\n"
+" int n=(mYear-rYear)*12+mMonth-rMonth;\n"
+" double tmp = (double)(n*nFreq)/12.0;\n"
+" return tmp;\n"
+"}\n";
+
+std::string setDayDecl=
+"void setDay(int nOrigDay, int nMonth,int nYear,int bLastDay,int b30Days,"
+"int *nDay);\n";
+std::string setDay=
+"void setDay(int nOrigDay, int nMonth,int nYear,int bLastDay,int b30Days,"
+"int *nDay)\n"
+"{\n"
+" if( b30Days )\n"
+" {\n"
+" *nDay = min( nOrigDay, 30);\n"
+" if( bLastDay || (*nDay >= DaysInMonth( nMonth, nYear )) )\n"
+" *nDay = 30;\n"
+" }\n"
+" else\n"
+" {\n"
+" int nLastDay = DaysInMonth( nMonth, nYear );\n"
+" *nDay = bLastDay ? nLastDay : min( nOrigDay, nLastDay );\n"
+" }\n"
+"}\n";
+
+std::string coupdaysDecl=
+"double coupdays(int nSettle,int nMat,int nFreq,int nBase);\n";
+
+std::string coupdays=
+"double coupdays(int nSettle,int nMat,int nFreq,int nBase)\n"
+"{\n"
+" int nNullDate=GetNullDate();\n"
+" if( nBase == 1 )\n"
+" return lcl_Getcoupdays(nNullDate, nSettle, nMat,nFreq, nBase);\n"
+" else\n"
+" return (double)GetDaysInYear(0,0,nBase)/nFreq;\n"
+"}\n";
+std::string coupdays_newDecl=
+"double coupdays_new(int nSettle,int nMat,int nFreq,int nBase);\n";
+
+std::string coupdays_new=
+"double coupdays_new(int nSettle,int nMat,int nFreq,int nBase)\n"
+"{\n"
+" int nNullDate=693594;\n"
+" if( nBase == 1 )\n"
+" return lcl_Getcoupdays_new(nNullDate, nSettle, nMat,nFreq, nBase);\n"
+" else\n"
+" return (double)GetDaysInYear(0,0,nBase)*pow((double)nFreq,-1.0);\n"
+"}\n";
+
+std::string coupdaybsDecl=
+"double coupdaybs( int nSettle,int nMat,int nFreq,int nBase);\n";
+
+std::string coupdaybs=
+"double coupdaybs( int nSettle,int nMat,int nFreq,int nBase)\n"
+"{\n"
+" int nNullDate=GetNullDate();\n"
+" return lcl_Getcoupdaybs(nNullDate, nSettle, nMat,nFreq, nBase);\n"
+"}\n";
+
+std::string coupdaybs_newDecl=
+"double coupdaybs_new( int nSettle,int nMat,int nFreq,int nBase);\n";
+
+std::string coupdaybs_new=
+"double coupdaybs_new( int nSettle,int nMat,int nFreq,int nBase)\n"
+"{\n"
+" int nNullDate=693594;\n"
+" return lcl_Getcoupdaybs_new(nNullDate, nSettle, nMat,nFreq, nBase);\n"
+"}\n";
+
+std::string coupdaysncDecl=
+"double coupdaysnc( int nSettle,int nMat,int nFreq,int nBase);\n";
+
+std::string coupdaysnc=
+ "double coupdaysnc( int nSettle,int nMat,int nFreq,int nBase)\n"
+"{\n"
+" int nNullDate=GetNullDate();\n"
+" if((nBase != 0) && (nBase != 4))\n"
+" {\n"
+" int aDate = nMat;\n"
+" int rDay=0,rMonth=0, rYear=0;int mDay=0,mMonth=0, mYear=0;int sDay=0,"
+"sMonth=0, sYear=0;\n"
+" int rbLastDayMode=0, rbLastDay=0,rb30Days=0,rbUSMode=0,rnDay=0;\n"
+" int sbLastDayMode=0, sbLastDay=0,sb30Days=0,sbUSMode=0,snDay=0;\n"
+" ScaDate( nNullDate,aDate,nBase,&rDay,&rMonth,&rYear,&rbLastDayMode,&"
+"rbLastDay,&rb30Days,&rbUSMode,&rnDay);\n"
+" ScaDate( nNullDate,nSettle,nBase,&sDay,&sMonth,&sYear,&sbLastDayMode,"
+"&sbLastDay,&sb30Days,&sbUSMode,&snDay);\n"
+" rYear= sYear;\n"
+" nSettle=nSettle+nNullDate;\n"
+" aDate=DateToDays( rDay,rMonth,rYear );\n"
+" if( aDate > nSettle )\n"
+" {\n"
+" rYear-= 1;\n"
+" aDate=DateToDays( rDay,rMonth,rYear );\n"
+" }\n"
+" while(aDate <= nSettle )\n"
+" {\n"
+" addMonths(rb30Days,rbLastDay,&rnDay,rDay,&rMonth,12/nFreq,&rYear)"
+";\n"
+" aDate=DateToDays( rDay,rMonth,rYear );\n"
+" }\n"
+" return getDiff( nSettle, aDate, sDay, sMonth, sYear, sbLastDayMode, "
+"sbLastDay, sb30Days, sbUSMode, snDay, rDay, rMonth, rYear, rbLastDayMode, "
+"rbLastDay, rb30Days, rbUSMode, rnDay);\n"
+" }\n"
+" else\n"
+" return coupdays(nSettle,nMat,nFreq,nBase)- coupdaybs( nSettle,nMat,"
+"nFreq,nBase);\n"
+"}\n";
+std::string coupdaysnc_newDecl=
+"double coupdaysnc_new( int nSettle,int nMat,int nFreq,int nBase);\n";
+
+std::string coupdaysnc_new=
+"double coupdaysnc_new( int nSettle,int nMat,int nFreq,int nBase)\n"
+"{\n"
+" int nNullDate=693594;\n"
+" if((nBase != 0) && (nBase != 4))\n"
+" {\n"
+" int aDate = nMat;\n"
+" int rDay=0,rMonth=0, rYear=0,rbLastDayMode=0, rbLastDay=0,rb30Days=0,"
+"rbUSMode=0,rnDay=0;\n"
+" int sDay=0,sMonth=0, sYear=0,sbLastDayMode=0, sbLastDay=0,sb30Days=0,"
+"sbUSMode=0,snDay=0;\n"
+" ScaDate( nNullDate,aDate,nBase,&rDay,&rMonth,&rYear,&rbLastDayMode,"
+"&rbLastDay,&rb30Days,&rbUSMode,&rnDay);\n"
+" ScaDate( nNullDate,nSettle,nBase,&sDay,&sMonth,&sYear,&sbLastDayMode,"
+"&sbLastDay,&sb30Days,&sbUSMode,&snDay);\n"
+" rYear=sYear;\n"
+" setDay(rDay,rMonth,rYear,rbLastDay,rb30Days,&rnDay);\n"
+" aDate=DateToDays( rnDay,rMonth,rYear);\n"
+" if(checklessthan(sYear,rYear,sMonth,rMonth,snDay,rnDay,sbLastDay,rbLastDay"
+",sDay,rDay))\n"
+" {\n"
+" rYear-=1;\n"
+" setDay(rDay,rMonth,rYear,rbLastDay,rb30Days,&rnDay);\n"
+" aDate=DateToDays( rnDay,rMonth,rYear );\n"
+" }\n"
+" while(!checklessthan(sYear,rYear,sMonth,rMonth,snDay,rnDay,sbLastDay,"
+"rbLastDay,sDay,rDay))\n"
+" {\n"
+" addMonths(rb30Days,rbLastDay,&rnDay,rDay,&rMonth,12/nFreq,&rYear);\n"
+" aDate=DateToDays( rnDay,rMonth,rYear );\n"
+" }\n"
+" return getDiff( nSettle+nNullDate,aDate,sDay,sMonth,sYear,sbLastDayMode, "
+"sbLastDay, sb30Days, sbUSMode, snDay, rDay, rMonth, rYear, rbLastDayMode, "
+"rbLastDay, rb30Days, rbUSMode, rnDay);\n"
+" }\n"
+" else\n"
+" return coupdays_new(nSettle,nMat,nFreq,nBase)- coupdaybs_new( nSettle,"
+"nMat,nFreq,nBase);\n"
+"}\n";
+
+std::string checklessthanDecl=
+"int checklessthan(int aYear,int bYear,int aMonth,int bMonth,int anDay,int "
+"bnDay,int abLastDay,int bbLastDay,int anOrigDay,int bnOrigDay);\n";
+std::string checklessthan=
+"int checklessthan(int aYear,int bYear,int aMonth,int bMonth,int anDay,int "
+"bnDay,int abLastDay,int bbLastDay,int anOrigDay,int bnOrigDay)\n"
+"{\n"
+" if( aYear != bYear )\n"
+" return aYear < bYear;\n"
+" if( aMonth != bMonth )\n"
+" return aMonth < bMonth;\n"
+" if( anDay != bnDay )\n"
+" return anDay < bnDay;\n"
+" if( abLastDay || bbLastDay )\n"
+" return !abLastDay && bbLastDay;\n"
+" return anOrigDay < bnOrigDay;\n"
+"}\n";
+
+std::string coupnumDecl=
+"double coupnum( int nSettle,int nMat,int nFreq,int nBase);\n";
+
+std::string coupnum=
+"double coupnum( int nSettle,int nMat,int nFreq,int nBase)\n"
+"{\n"
+" int nNullDate=GetNullDate();\n"
+" return lcl_Getcoupnum(nNullDate,nSettle,nMat,nFreq);\n"
+"}\n";
+std::string coupnum_newDecl=
+"double coupnum_new( int nSettle,int nMat,int nFreq,int nBase);\n";
+
+std::string coupnum_new=
+"double coupnum_new( int nSettle,int nMat,int nFreq,int nBase)\n"
+"{\n"
+" int nNullDate=693594;\n"
+" return lcl_Getcoupnum_new(nNullDate,nSettle,nMat,nFreq,nBase);\n"
+"}\n";
+
+std::string getPrice_Decl=
+"double getPrice_(int nSettle, int nMat, double fRate, double fYield,\n"
+ "double fRedemp, int nFreq, int nBase );\n";
+
+std::string getPrice_=
+"double getPrice_(int nSettle, int nMat, double fRate, double fYield,\n"
+ "double fRedemp, int nFreq, int nBase )\n"
+"{\n"
+" double fFreq = nFreq;\n"
+" double fE = coupdays( nSettle, nMat, nFreq, nBase );\n"
+" double fDSC_E = coupdaysnc( nSettle, nMat, nFreq, nBase ) / fE;\n"
+" double fN = coupnum( nSettle, nMat, nFreq, nBase );\n"
+" double fA = coupdaybs( nSettle, nMat, nFreq, nBase );\n"
+" double fRet = fRedemp / ( pow( 1.0 + fYield / fFreq, fN - 1.0 + "
+"fDSC_E ) );\n"
+" fRet -= 100.0 * fRate / fFreq * fA / fE;\n"
+" double fT1 = 100.0 * fRate / fFreq;\n"
+" double fT2 = 1.0 + fYield / fFreq;\n"
+" for( double fK = 0.0 ; fK < fN ; fK+=1.0 )\n"
+" fRet += fT1 / pow( fT2, fK + fDSC_E );\n"
+" return fRet;\n"
+"}\n";
+std::string getPrice_new_Decl=
+"double getPrice_(int nSettle, int nMat, double fRate, double fYield,\n"
+ "double fRedemp, int nFreq, int nBase );\n";
+
+std::string getPrice_new=
+"double getPrice_(int nSettle, int nMat, double fRate, double fYield,\n"
+ "double fRedemp, int nFreq, int nBase )\n"
+"{\n"
+" double fFreq = nFreq;\n"
+" double fE = coupdays_new( nSettle, nMat, nFreq, nBase );\n"
+" double fDSC_E = coupdaysnc_new( nSettle, nMat, nFreq, nBase ) / fE;\n"
+" double fN = coupnum_new( nSettle, nMat, nFreq, nBase );\n"
+" double fA = coupdaybs_new( nSettle, nMat, nFreq, nBase );\n"
+" double fRet = fRedemp / ( pow( 1.0 + fYield / fFreq, fN - 1.0 + "
+"fDSC_E ) );\n"
+" fRet -= 100.0 * fRate / fFreq * fA / fE;\n"
+" double fT1 = 100.0 * fRate / fFreq;\n"
+" double fT2 = 1.0 + fYield / fFreq;\n"
+" for( double fK = 0.0 ; fK < fN ; fK+=1.0 )\n"
+" fRet += fT1 / pow( fT2, fK + fDSC_E );\n"
+" return fRet;\n"
+"}\n";
+
+std::string getYield_Decl=
+"double getYield_( int nNullDate, int nSettle, int nMat, double fCoup,"
+"double fPrice,double fRedemp, int nFreq, int nBase);\n";
+
+std::string getYield_=
+"double getYield_( int nNullDate, int nSettle, int nMat, double fCoup,"
+"double fPrice,double fRedemp, int nFreq, int nBase )\n"
+"{\n"
+" double fRate = fCoup;\n"
+" double fPriceN = 0.0;\n"
+" double fYield1 = 0.0;\n"
+" double fYield2 = 1.0;\n"
+" double fPrice1 = getPrice_(nSettle, nMat, fRate, fYield1, fRedemp, "
+"nFreq, nBase );\n"
+" double fPrice2 = getPrice_(nSettle, nMat, fRate, fYield2, fRedemp, "
+"nFreq, nBase );\n"
+" double fYieldN = ( fYield2 - fYield1 ) * 0.5;\n"
+" for( unsigned int nIter = 0 ; nIter < 100 && fPriceN != fPrice ; nIter++ "
+")\n"
+" {\n"
+" fPriceN = getPrice_(nSettle, nMat, fRate, fYieldN, fRedemp, nFreq, "
+"nBase );\n"
+" if( fPrice == fPrice1 )\n"
+" return fYield1;\n"
+" else if( fPrice == fPrice2 )\n"
+" return fYield2;\n"
+" else if( fPrice == fPriceN )\n"
+" return fYieldN;\n"
+" else if( fPrice < fPrice2 )\n"
+" {\n"
+" fYield2 *= 2.0;\n"
+" fPrice2 = getPrice_(nSettle, nMat, fRate, fYield2, fRedemp, nFreq"
+", nBase );\n"
+" fYieldN = ( fYield2 - fYield1 ) * 0.5;\n"
+" }\n"
+" else\n"
+" {\n"
+" if( fPrice < fPriceN )\n"
+" {\n"
+" fYield1 = fYieldN;\n"
+" fPrice1 = fPriceN;\n"
+" }\n"
+" else\n"
+" {\n"
+" fYield2 = fYieldN;\n"
+" fPrice2 = fPriceN;\n"
+" }\n"
+" fYieldN = fYield2 - ( fYield2 - fYield1 ) * ( ( fPrice - fPrice2 "
+") / ( fPrice1 - fPrice2 ) );\n"
+" }\n"
+" }\n"
+" return fYieldN;\n"
+"}\n";
+
+std::string GetYearFracDecl=
+"double GetYearFrac( int nNullDate, int nStartDate, int nEndDate,"
+"int nMode );\n";
+
+std::string GetYearFrac=
+"double GetYearFrac( int nNullDate, int nStartDate, int nEndDate,"
+"int nMode ) \n"
+"{\n"
+" if( nStartDate == nEndDate )\n"
+" return 0.0; \n"
+
+ " if( nStartDate > nEndDate )\n"
+ " {\n"
+ " int n = nEndDate;\n"
+ " nEndDate = nStartDate;\n"
+ " nStartDate = n;\n"
+ " }\n"
+
+ " int nDate1 = nStartDate + nNullDate;\n"
+ " int nDate2 = nEndDate + nNullDate;\n"
+
+ " int nDay1, nDay2;\n"
+ " int nMonth1, nMonth2;\n"
+ " int nYear1, nYear2;\n"
+
+ " DaysToDate( nDate1, &nDay1, &nMonth1, &nYear1 );\n"
+ " DaysToDate( nDate2, &nDay2, &nMonth2, &nYear2 );\n"
+
+ " int nDayDiff;\n"
+ " switch( nMode )\n"
+ " {\n"
+ " case 0: \n"
+ " if ( nDay1 == 31 )\n"
+ " {\n"
+ " nDay1--;\n"
+ " }\n"
+ " if ( nDay1 == 30 && nDay2 == 31 )\n"
+ " {\n"
+ " nDay2--;\n"
+ " }\n"
+ " else\n"
+ " {\n"
+ " if ( nMonth1 == 2 && nDay1 == "
+ "( IsLeapYear( nYear1 ) ? 29 : 28 ) )\n"
+ " {\n"
+ " nDay1 = 30;\n"
+ " if ( nMonth2 == 2 && nDay2 == "
+ "( IsLeapYear( nYear2 ) ? 29 : 28 ) )\n"
+ " {\n"
+ " nDay2 = 30;\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ " nDayDiff = ( nYear2 - nYear1 ) * 360 + "
+ "( nMonth2 - nMonth1 ) * 30 + ( nDay2 - nDay1 );\n"
+ " break;\n"
+ " case 1: \n"
+ " case 2: \n"
+ " case 3: \n"
+ " nDayDiff = nDate2 - nDate1;\n"
+ " break;\n"
+ " case 4: \n"
+ " if ( nDay1 == 31 )\n"
+ " {\n"
+ " nDay1--;\n"
+ " }\n"
+ " if ( nDay2 == 31 )\n"
+ " {\n"
+ " nDay2--;\n"
+ " }\n"
+ " nDayDiff = ( nYear2 - nYear1 ) * 360 + "
+ "( nMonth2 - nMonth1 ) * 30 + ( nDay2 - nDay1 );\n"
+ " break;\n"
+ " }\n"
+
+ " double nDaysInYear;\n"
+ " switch( nMode )\n"
+ " {\n"
+ " case 0: \n"
+ " case 2: \n"
+ " case 4: \n"
+ " nDaysInYear = 360;\n"
+ " break;\n"
+ " case 1: \n"
+ " {\n"
+ " bool isYearDifferent = ( nYear1 != nYear2 );\n"
+ " if ( isYearDifferent &&\n"
+ " ( ( nYear2 != nYear1 + 1 ) ||\n"
+ " ( nMonth1 < nMonth2 ) ||\n"
+ " ( nMonth1 == nMonth2 && nDay1 < nDay2 ) ) )\n"
+ " {\n"
+
+ " int nDayCount = 0;\n"
+ " for ( int i = nYear1; i <= nYear2; i++ )\n"
+ " nDayCount += ( IsLeapYear( i ) ? 366 : 365 );\n"
+
+ " nDaysInYear = ( double ) nDayCount / "
+ "( double ) ( nYear2 - nYear1 + 1 );\n"
+ " }\n"
+ " else\n"
+ " {\n"
+ " if ( isYearDifferent && IsLeapYear( nYear1 ) )\n"
+ " {\n"
+ " nDaysInYear = 366;\n"
+ " }\n"
+ " else\n"
+ " {\n"
+
+ " if ( ( IsLeapYear( nYear1 ) && nMonth1 <= 2 "
+ "&& nDay1 <= 29 ) ||\n"
+ " ( IsLeapYear( nYear2 ) && ( nMonth2 > 3 || "
+ "( nMonth2 == 2 && nDay1 == 29 ) ) ) )\n"
+ " {\n"
+ " nDaysInYear = 366;\n"
+ " }\n"
+ " else\n"
+ " {\n"
+ " nDaysInYear = 365;\n"
+ " for ( int i = nYear1; i <= nYear2; i++ )\n"
+ " {\n"
+ " if ( IsLeapYear( i ) )\n"
+ " {\n"
+ " nDaysInYear = 366;\n"
+ " break;\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ " break;\n"
+ " case 3: \n"
+ " nDaysInYear = 365;\n"
+ " break;\n"
+ " }\n"
+ " return (double)( nDayDiff ) / (nDaysInYear);\n"
+"}\n";
+
+std::string GetYieldmatDecl=
+ "double GetYieldmat( int nNullDate, int nSettle, int nMat, int nIssue,\n"
+ "double fRate, double fPrice, int nBase );\n";
+
+std::string GetYieldmat=
+ "double GetYieldmat( int nNullDate, int nSettle, int nMat, int nIssue,\n"
+ "double fRate, double fPrice, int nBase )\n"
+"{\n"
+" double fIssMat = GetYearFrac_new( nNullDate, nIssue, nMat, nBase );\n"
+" double fIssSet = GetYearFrac_new( nNullDate, nIssue, nSettle, nBase );\n"
+" double fSetMat = GetYearFrac_new( nNullDate, nSettle, nMat, nBase );\n"
+" double y = 1.0 + fIssMat * fRate;\n"
+" y =y * pow( (fPrice / 100.0 + fIssSet * fRate),-1);\n"
+" y-=1.0;\n"
+" y = y * pow(fSetMat,-1);\n"
+" return y;\n"
+"}\n";
+
+std::string GetDiffDateDecl=
+"int GetDiffDate( int nNullDate, int nStartDate, int nEndDate, int nMode,"
+" int* pOptDaysIn1stYear );\n";
+
+std::string GetDiffDate=
+"int GetDiffDate( int nNullDate, int nStartDate, int nEndDate, int nMode,"
+" int* pOptDaysIn1stYear )\n"
+"{\n"
+" bool bNeg = nStartDate > nEndDate;\n"
+" if( bNeg )\n"
+" {\n"
+" int n = nEndDate;\n"
+" nEndDate = nStartDate;\n"
+" nStartDate = n;\n"
+" }\n"
+" int nRet;\n"
+" switch( nMode )\n"
+" {\n"
+" case 0: \n"
+" case 4: \n"
+" {\n"
+" int nD1, nM1, nY1, nD2, nM2, nY2;\n"
+" nStartDate += nNullDate;\n"
+" nEndDate += nNullDate;\n"
+" DaysToDate( nStartDate, &nD1, &nM1, &nY1 );\n"
+" DaysToDate( nEndDate, &nD2, &nM2, &nY2 );\n"
+" bool bLeap = IsLeapYear( nY1 );\n"
+" int nDays, nMonths;\n"
+" nMonths = nM2 - nM1;\n"
+" nDays = nD2 - nD1;\n"
+" nMonths += ( nY2 - nY1 ) * 12;\n"
+" nRet = nMonths * 30 + nDays;\n"
+" if( nMode == 0 && nM1 == 2 && nM2 != 2 && nY1 == nY2 )\n"
+" nRet -= bLeap? 1 : 2;\n"
+" if( pOptDaysIn1stYear )\n"
+" *pOptDaysIn1stYear = 360;\n"
+" }\n"
+" break;\n"
+" case 1: \n"
+" if( pOptDaysIn1stYear )\n"
+" {\n"
+" int nD, nM, nY;\n"
+" DaysToDate( nStartDate + nNullDate, &nD, &nM, &nY );\n"
+" *pOptDaysIn1stYear = IsLeapYear( nY )? 366 : 365;\n"
+" }\n"
+" nRet = nEndDate - nStartDate;\n"
+" break;\n"
+" case 2: \n"
+" nRet = nEndDate - nStartDate;\n"
+" if( pOptDaysIn1stYear )\n"
+" *pOptDaysIn1stYear = 360;\n"
+" break;\n"
+" case 3: \n"
+" nRet = nEndDate - nStartDate;\n"
+" if( pOptDaysIn1stYear )\n"
+" *pOptDaysIn1stYear = 365;\n"
+" break;\n"
+" }\n"
+" return bNeg? -nRet : nRet;\n"
+"}\n";
+
+std::string GetYearDiffDecl=
+"double GetYearDiff( int nNullDate, int nStartDate, int nEndDate,"
+"int nMode);\n";
+
+std::string GetYearDiff=
+"double GetYearDiff( int nNullDate, int nStartDate, int nEndDate,"
+"int nMode )\n"
+"{\n"
+" int nDays1stYear;\n"
+" int nTotalDays = GetDiffDate( nNullDate, nStartDate, nEndDate,"
+"nMode, &"
+"nDays1stYear );\n"
+" return (double)(nTotalDays)*pow((double)nDays1stYear,-1);\n"
+"}\n";
+
+std::string GetDiffDate360_Decl=
+"int GetDiffDate360_(\n"
+" int nDay1, int nMonth1, int nYear1, bool bLeapYear1,\n"
+" int nDay2, int nMonth2, int nYear2,\n"
+" bool bUSAMethod );\n";
+
+std::string GetDiffDate360_=
+"int GetDiffDate360_(\n"
+" int nDay1, int nMonth1, int nYear1, bool bLeapYear1,\n"
+" int nDay2, int nMonth2, int nYear2,\n"
+" bool bUSAMethod )\n"
+"{\n"
+" if( nDay1 == 31 )\n"
+" nDay1--;\n"
+" else if( bUSAMethod && ( nMonth1 == 2 && ( nDay1 == 29 || ( nDay1 == 28 "
+"&& !bLeapYear1 ) ) ) )\n"
+" nDay1 = 30;\n"
+" if( nDay2 == 31 )\n"
+" {\n"
+" if( bUSAMethod && nDay1 != 30 )\n"
+" {\n"
+" nDay2 = 1;\n"
+" if( nMonth2 == 12 )\n"
+" {\n"
+" nYear2++;\n"
+" nMonth2 = 1;\n"
+" }\n"
+" else\n"
+" nMonth2++;\n"
+" }\n"
+" else\n"
+" nDay2 = 30;\n"
+" }\n"
+" return nDay2 + nMonth2 * 30 + nYear2 * 360 - nDay1 - nMonth1 * 30 - "
+"nYear1 * 360;\n"
+"}\n";
+
+std::string GetDiffDate360Decl=
+"int GetDiffDate360( int nNullDate, int nDate1, int nDate2,"
+"bool bUSAMethod);\n";
+
+std::string GetDiffDate360=
+"int GetDiffDate360( int nNullDate, int nDate1, int nDate2,"
+"bool bUSAMethod )\n"
+"{\n"
+" nDate1 += nNullDate;\n"
+" nDate2 += nNullDate;\n"
+" int nDay1, nMonth1, nYear1, nDay2, nMonth2, nYear2;\n"
+" DaysToDate( nDate1, &nDay1, &nMonth1, &nYear1 );\n"
+" DaysToDate( nDate2, &nDay2, &nMonth2, &nYear2 );\n"
+" return GetDiffDate360_( nDay1, nMonth1, nYear1, IsLeapYear( nYear1 ), "
+"nDay2, nMonth2, nYear2, bUSAMethod );\n"
+"}\n";
+
+std::string GetDurationDecl=
+"double GetDuration( \n"
+" int nNullDate, int nSettle, int nMat, double fCoup,\n"
+" double fYield, int nFreq, int nBase );\n";
+
+std::string GetDuration=
+"double GetDuration( \n"
+" int nNullDate, int nSettle, int nMat, double fCoup,\n"
+" double fYield, int nFreq, int nBase )\n"
+"{\n"
+" double fYearfrac = GetYearFrac( nNullDate, nSettle, nMat, nBase );\n"
+" double fNumOfCoups = lcl_Getcoupnum(nNullDate,nSettle,nMat,nFreq);\n"
+" double fDur = 0.0;\n"
+" double f100 = 100.0;\n"
+" fCoup *= f100 / nFreq;\n"
+" fYield /= nFreq;\n"
+" fYield += 1.0;\n"
+" double nDiff = fYearfrac * nFreq - fNumOfCoups;\n"
+" int t;\n"
+" for( t = 1 ; t < fNumOfCoups ; t++ )\n"
+" fDur += ( t + nDiff ) * ( fCoup ) / pow( fYield, t + nDiff );\n"
+" fDur += ( fNumOfCoups + nDiff ) * ( fCoup + f100 ) /"
+" pow( fYield, fNumOfCoups + nDiff );\n"
+" double p = 0.0;\n"
+" for( t = 1 ; t < fNumOfCoups ; t++ )\n"
+" p += fCoup / pow( fYield, t + nDiff );\n"
+" p += ( fCoup + f100 ) / pow( fYield, fNumOfCoups + nDiff );\n"
+" fDur /= p;\n"
+" fDur /= nFreq;\n"
+" return fDur;\n""}\n";
+
+std::string GetDuration_newDecl=
+"double GetDuration_new( \n"
+" int nNullDate, int nSettle, int nMat, double fCoup,\n"
+" double fYield, int nFreq, int nBase );\n";
+
+std::string GetDuration_new=
+"double GetDuration_new( \n"
+" int nNullDate, int nSettle, int nMat, double fCoup,\n"
+" double fYield, int nFreq, int nBase )\n"
+" {\n"
+" double fYearfrac = GetYearFrac(nNullDate,nSettle,nMat,nBase);\n"
+" double fNumOfCoups = lcl_Getcoupnum_new(nNullDate,nSettle,nMat,"
+"nFreq,nBase);\n"
+" double fDur = 0.0;\n"
+" fCoup = fCoup * 100.0 * pow(nFreq, -1.0);\n"
+" fYield = fYield * pow(nFreq, -1.0);\n"
+" fYield += 1.0;\n"
+" double nDiff = fYearfrac * nFreq - fNumOfCoups;\n"
+" int t;\n"
+" double tmp0 = 0, tmp1 = 0, tmp2 = 0;\n"
+" for( t = 1 ; t < fNumOfCoups ; t++ ){\n"
+" tmp0 = (t + nDiff) * ( fCoup ) ;\n"
+" tmp1 = pow( fYield, t + nDiff ) ;\n"
+" tmp2 = tmp0 * pow(tmp1, -1);\n"
+" fDur += tmp2;\n"
+" }\n"
+" fDur += (fNumOfCoups + nDiff) * (fCoup + 100.0) * pow(pow(fYield,"
+" fNumOfCoups + nDiff ),-1);\n"
+" double p = 0.0;\n"
+" for( t = 1 ; t < fNumOfCoups ; t++ ){\n"
+" tmp0 = pow( fYield, t + nDiff );\n"
+" p += fCoup * pow(tmp0, -1);}\n"
+" p += (fCoup + 100.0) * pow(pow(fYield, fNumOfCoups + nDiff), -1);\n"
+" fDur = fDur * pow(p, -1.0);\n"
+" fDur = fDur * pow(nFreq, -1.0);\n"
+" return fDur;\n"
+" }\n";
+
+std::string ScGetDDBDecl=
+"double ScGetDDB(double fCost, double fSalvage, double fLife, double fPeriod,"
+"double fFactor);\n";
+
+std::string ScGetDDB=
+"double ScGetDDB(double fCost, double fSalvage, double fLife, double fPeriod,"
+"double fFactor)\n"
+"{\n"
+" double fDdb, fRate, fOldValue, fNewValue;\n"
+" fRate = fFactor / fLife;\n"
+" if (fRate >= 1.0)\n"
+" {\n"
+" fRate = 1.0;\n"
+" if (fPeriod == 1.0)\n"
+" fOldValue = fCost;\n"
+" else\n"
+" fOldValue = 0.0;\n"
+" }\n"
+" else\n"
+" fOldValue = fCost * pow(1.0 - fRate, fPeriod - 1.0);\n"
+" fNewValue = fCost * pow(1.0 - fRate, fPeriod);\n"
+
+" if (fNewValue < fSalvage)\n"
+" fDdb = fOldValue - fSalvage;\n"
+" else\n"
+" fDdb = fOldValue - fNewValue;\n"
+" if (fDdb < 0.0)\n"
+" fDdb = 0.0;\n"
+" return fDdb;\n"
+"}\n";
+
+std::string DblMinDecl=
+"inline double DblMin( double a, double b );\n";
+
+std::string DblMin=
+"inline double DblMin( double a, double b )\n"
+"{\n"
+" return (a < b) ? a : b;\n"
+"}\n";
+
+std::string ScInterVDBDecl=
+"double ScInterVDB(double fCost, double fSalvage, double fLife, double fLife1,"
+"double fPeriod, double fFactor);\n";
+
+std::string ScInterVDB=
+"double ScInterVDB(double fCost, double fSalvage, double fLife, double fLife1,"
+"double fPeriod, double fFactor)\n"
+"{\n"
+" double fVdb=0;\n"
+" double fIntEnd = ceil(fPeriod);\n"
+" int nLoopEnd = fIntEnd;\n"
+
+" double fTerm, fSln;\n"
+" double fSalvageValue = fCost - fSalvage;\n"
+" int nNowSln = 0;\n"
+" double fDdb;\n"
+" int i;\n"
+" fSln=0;\n"
+" for ( i = 1; i <= nLoopEnd; i++)\n"
+" {\n"
+" if(!nNowSln)\n"
+" {\n"
+" fDdb = ScGetDDB(fCost, fSalvage, fLife, (double) i, fFactor);\n"
+" fSln = fSalvageValue/ (fLife1 - (double) (i-1));\n"
+" if (fSln > fDdb)\n"
+" {\n"
+" fTerm = fSln;\n"
+" nNowSln = 1;\n"
+" }\n"
+" else\n"
+" {\n"
+" fTerm = fDdb;\n"
+" fSalvageValue =fSalvageValue- fDdb;\n"
+" }\n"
+" }\n"
+" else\n"
+" {\n"
+" fTerm = fSln;\n"
+" }\n"
+
+" if ( i == nLoopEnd)\n"
+" fTerm *= ( fPeriod + 1.0 - fIntEnd );\n"
+
+" fVdb += fTerm;\n"
+" }\n"
+" return fVdb;\n"
+"}\n";
+
+std::string VDBImplementDecl=
+"double VDBImplement(double fCost, double fSalvage, double fLife, double fStart"
+", double fEnd, double fFactor, bool bNoSwitch);\n";
+
+std::string VDBImplement=
+"double VDBImplement(double fCost, double fSalvage, double fLife, double fStart"
+", double fEnd, double fFactor, bool bNoSwitch)\n"
+"{\n"
+" double result=0;\n"
+" double fIntStart = floor(fStart);\n"
+" double fIntEnd = ceil(fEnd);\n"
+" int nLoopStart = (int) fIntStart;\n"
+" int nLoopEnd = (int) fIntEnd;\n"
+" if (bNoSwitch)\n"
+" {\n"
+" for (int i = nLoopStart + 1; i <= nLoopEnd; i++)\n"
+" {\n"
+" double fTerm = ScGetDDB(fCost, fSalvage, fLife, (double) i, fFactor"
+");\n"
+" if ( i == nLoopStart+1 )\n"
+" fTerm *= ( DblMin( fEnd, fIntStart + 1.0 ) - fStart );\n"
+" else if ( i == nLoopEnd )\n"
+" fTerm *= ( fEnd + 1.0 - fIntEnd );\n"
+" result += fTerm;\n"
+" }\n"
+" }\n"
+" else\n"
+" {\n"
+" double fLife1=fLife;\n"
+" if(!isequal(fStart,floor(fStart)))\n"
+" {\n"
+" if(fFactor>1)\n"
+" {\n"
+" if(fStart>fLife/2 || isequal(fStart,fLife/2))\n"
+" {\n"
+" double fPart=fStart-fLife/2;\n"
+" fStart=fLife/2;\n"
+" fEnd-=fPart;\n"
+" fLife1+=1;\n"
+" }\n"
+" }\n"
+" }\n"
+" fCost-=ScInterVDB(fCost, fSalvage, fLife, fLife1, fStart, fFactor);\n"
+" result=ScInterVDB(fCost, fSalvage, fLife, fLife-fStart, fEnd-fStart,"
+"fFactor);\n"
+" }\n"
+" return result;\n"
+"}\n";
+
+std::string GetOddlpriceDecl=
+"double GetOddlprice( int nNullDate, int nSettle, int nMat, int nLastCoup,\n"
+" double fRate, double fYield, double fRedemp, int nFreq, int nBase );\n";
+
+std::string GetOddlprice=
+"double GetOddlprice( int nNullDate, int nSettle, int nMat, int nLastCoup,\n"
+" double fRate, double fYield, double fRedemp, int nFreq, int nBase )\n"
+"{\n"
+" double fFreq = nFreq ;\n"
+" double fDCi = GetYearFrac( nNullDate, nLastCoup,"
+"nMat, nBase ) * fFreq;\n"
+" double fDSCi = GetYearFrac( nNullDate, nSettle,"
+"nMat, nBase ) * fFreq;\n"
+" double fAi = GetYearFrac( nNullDate, nLastCoup,"
+"nSettle, nBase ) * fFreq;\n"
+" double p = fRedemp + fDCi * 100.0 * fRate / fFreq;\n"
+" p /= fDSCi * fYield / fFreq + 1.0;\n"
+" p -= fAi * 100.0 * fRate / fFreq;\n"
+" return p;\n"
+"}\n";
+
+std::string GetOddlyieldDecl=
+"double GetOddlyield( int nNullDate, int nSettle, int nMat, int nLastCoup,\n"
+" double fRate, double fPrice, double fRedemp, int nFreq, int nBase );\n";
+
+std::string GetOddlyield=
+"double GetOddlyield( int nNullDate, int nSettle, int nMat, int nLastCoup,\n"
+" double fRate, double fPrice, double fRedemp, int nFreq, int nBase ) \n"
+"{\n"
+" double fFreq = nFreq ;\n"
+" double fDCi= GetYearFrac( nNullDate, nLastCoup, nMat, nBase ) * fFreq;\n"
+" double fDSCi= GetYearFrac( nNullDate, nSettle, nMat, nBase ) * fFreq;\n"
+" double fAi= GetYearFrac( nNullDate, nLastCoup, nSettle, nBase )*fFreq;\n"
+" double y = fRedemp + fDCi * 100.0 * fRate / fFreq;\n"
+" y /= fPrice + fAi * 100.0 * fRate / fFreq;\n"
+" y -= 1.0;\n"
+" y *= fFreq / fDSCi;\n"
+" return y;\n"
+"}\n";
+
+std::string GetYearFrac_newDecl=
+"double GetYearFrac_new( int nNullDate, int nStartDate, int nEndDate,"
+"int nMode );\n";
+
+std::string GetYearFrac_new=
+"double GetYearFrac_new( int nNullDate, int nStartDate, int nEndDate,"
+"int nMode ) \n"
+"{\n"
+" if( nStartDate == nEndDate )\n"
+" return 0.0; \n"
+" if( nStartDate > nEndDate )\n"
+" {\n"
+" int n = nEndDate;\n"
+" nEndDate = nStartDate;\n"
+" nStartDate = n;\n"
+" }\n"
+" int nDate1 = nStartDate + nNullDate;\n"
+" int nDate2 = nEndDate + nNullDate;\n"
+" int nDay1, nDay2;\n"
+" int nMonth1, nMonth2;\n"
+" int nYear1, nYear2;\n"
+" DaysToDate_new( nDate1, &nDay1, &nMonth1, &nYear1 );\n"
+" DaysToDate_new( nDate2, &nDay2, &nMonth2, &nYear2 );\n"
+" int nDayDiff;\n"
+" switch( nMode )\n"
+" {\n"
+" case 0: \n"
+" if ( nDay1 == 31 )\n"
+" {\n"
+" nDay1--;\n"
+" }\n"
+" if ( nDay1 == 30 && nDay2 == 31 )\n"
+" {\n"
+" nDay2--;\n"
+" }\n"
+" else\n"
+" {\n"
+" if ( nMonth1 == 2 && nDay1 == "
+"( IsLeapYear( nYear1 ) ? 29 : 28 ) )\n"
+" {\n"
+" nDay1 = 30;\n"
+" if ( nMonth2 == 2 && nDay2 == "
+"( IsLeapYear( nYear2 ) ? 29 : 28 ) )\n"
+" {\n"
+" nDay2 = 30;\n"
+" }\n"
+" }\n"
+" }\n"
+" nDayDiff = ( nYear2 - nYear1 ) * 360 + "
+"( nMonth2 - nMonth1 ) * 30 + ( nDay2 - nDay1 );\n"
+" break;\n"
+" case 1: \n"
+" case 2: \n"
+" case 3: \n"
+" nDayDiff = nDate2 - nDate1;\n"
+" break;\n"
+" case 4: \n"
+" if ( nDay1 == 31 )\n"
+" {\n"
+" nDay1--;\n"
+" }\n"
+" if ( nDay2 == 31 )\n"
+" {\n"
+" nDay2--;\n"
+" }\n"
+" nDayDiff = ( nYear2 - nYear1 ) * 360 + "
+"( nMonth2 - nMonth1 ) * 30 + ( nDay2 - nDay1 );\n"
+" break;\n"
+" }\n"
+" double nDaysInYear;\n"
+" switch( nMode )\n"
+" {\n"
+" case 0: \n"
+" case 2: \n"
+" case 4: \n"
+" nDaysInYear = 360;\n"
+" break;\n"
+" case 1: \n"
+" {\n"
+" bool isYearDifferent = ( nYear1 != nYear2 );\n"
+" if ( isYearDifferent &&\n"
+" ( ( nYear2 != nYear1 + 1 ) ||\n"
+" ( nMonth1 < nMonth2 ) ||\n"
+" ( nMonth1 == nMonth2 && nDay1 < nDay2 ) ) )\n"
+" {\n"
+" int nDayCount = 0;\n"
+" for ( int i = nYear1; i <= nYear2; i++ )\n"
+" nDayCount += ( IsLeapYear( i ) ? 366 : 365 );\n"
+" nDaysInYear = ( double ) nDayCount / "
+"( double ) ( nYear2 - nYear1 + 1 );\n"
+" }\n"
+" else\n"
+" {\n"
+" if ( isYearDifferent && IsLeapYear( nYear1 ) )\n"
+" {\n"
+" nDaysInYear = 366;\n"
+" }\n"
+" else\n"
+" {\n"
+" if ( ( IsLeapYear( nYear1 ) && nMonth1 <= 2 "
+"&& nDay1 <= 29 ) ||\n"
+" ( IsLeapYear( nYear2 ) && ( nMonth2 > 3 || "
+"( nMonth2 == 2 && nDay1 == 29 ) ) ) )\n"
+" {\n"
+" nDaysInYear = 366;\n"
+" }\n"
+" else\n"
+" {\n"
+" nDaysInYear = 365;\n"
+" for ( int i = nYear1; i <= nYear2; i++ )\n"
+" {\n"
+" if ( IsLeapYear( i ) )\n"
+" {\n"
+" nDaysInYear = 366;\n"
+" break;\n"
+" }\n"
+" }\n"
+" }\n"
+" }\n"
+" }\n"
+" }\n"
+" break;\n"
+" case 3: \n"
+" nDaysInYear = 365;\n"
+" break;\n"
+" }\n"
+" return (double)( nDayDiff ) / (nDaysInYear);\n"
+"}\n";
+
+std::string DaysToDate_newDecl =
+"void DaysToDate_new( int nDays, int *rDay, int* rMonth, int* rYear );\n";
+
+std::string DaysToDate_new =
+"void DaysToDate_new( int nDays, int *rDay, int* rMonth, int* rYear )\n"
+"{\n"
+" int nTempDays;\n"
+" int i = 0;\n"
+" bool bCalc;\n"
+" do\n"
+" {\n"
+" nTempDays = nDays;\n"
+" *rYear = (int)((nTempDays / 365) - i);\n"
+" nTempDays -= ((int) *rYear -1) * 365;\n"
+" nTempDays -= ((*rYear -1) / 4) - ((*rYear -1) / 100) + ((*rYear -1)"
+" / 400);\n"
+" bCalc = false;\n"
+" if ( nTempDays < 1 )\n"
+" {\n"
+" i++;\n"
+" bCalc = true;\n"
+" }\n"
+" else\n"
+" {\n"
+" if ( nTempDays > 365 )\n"
+" {\n"
+" if ( (nTempDays != 366) || !IsLeapYear( *rYear ) )\n"
+" {\n"
+" i--;\n"
+" bCalc = true;\n"
+" }\n"
+" }\n"
+" }\n"
+" }\n"
+" while ( bCalc );\n"
+" if(nTempDays != 0){\n"
+" for (*rMonth = 1; (int)nTempDays > DaysInMonth( *rMonth, *rYear );"
+" *rMonth += 1)\n"
+" {\n"
+" nTempDays -= DaysInMonth( *rMonth, *rYear ); \n"
+" }\n"
+" *rDay = (int)nTempDays;\n"
+" }\n"
+"}\n";
+
+std::string DaysToDate_LocalBarrierDecl =
+"void DaysToDate( int nDays, int *rDay, int* rMonth, int* rYear );\n";
+
+std::string DaysToDate_LocalBarrier =
+"void DaysToDate( int nDays, int *rDay, int* rMonth, int* rYear )\n"
+"{\n"
+" int nTempDays;\n"
+" int i = 0;\n"
+" bool bCalc;\n"
+" do\n"
+" {\n"
+" nTempDays = nDays;\n"
+" *rYear = (int)((nTempDays / 365) - i);\n"
+" nTempDays -= ((int) *rYear -1) * 365;\n"
+" nTempDays -= ((*rYear -1) / 4) - ((*rYear -1) / 100) + ((*rYear -1)"
+" / 400);\n"
+" bCalc = false;\n"
+" if ( nTempDays < 1 )\n"
+" {\n"
+" i++;\n"
+" bCalc = true;\n"
+" }\n"
+" else\n"
+" {\n"
+" if ( nTempDays > 365 )\n"
+" {\n"
+" if ( (nTempDays != 366) || !IsLeapYear( *rYear ) )\n"
+" {\n"
+" i--;\n"
+" bCalc = true;\n"
+" }\n"
+" }\n"
+" }\n"
+" }\n"
+" while ( bCalc );\n"
+" barrier(CLK_LOCAL_MEM_FENCE);\n"
+" if(nTempDays != 0){\n"
+" for (*rMonth = 1; (int)nTempDays > DaysInMonth( *rMonth, *rYear );"
+" *rMonth += 1)\n"
+" {\n"
+" nTempDays -= DaysInMonth( *rMonth, *rYear ); \n"
+" }\n"
+" *rDay = (int)nTempDays;\n"
+" }\n"
+"}\n";
+
+std::string GetYearDiff_newDecl=
+"double GetYearDiff_new( int nNullDate, int nStartDate, int nEndDate,"
+"int nMode);\n";
+
+std::string GetYearDiff_new=
+"double GetYearDiff_new( int nNullDate, int nStartDate, int nEndDate,"
+"int nMode )\n"
+"{\n"
+" int nDays1stYear;\n"
+" int nTotalDays = GetDiffDate_new( nNullDate, nStartDate, nEndDate,"
+"nMode, &"
+"nDays1stYear );\n"
+" return (double)(nTotalDays)* pow((double)nDays1stYear,-1);\n"
+"}\n";
+
+std::string GetDiffDate_newDecl=
+"int GetDiffDate_new( int nNullDate, int nStartDate, int nEndDate, int nMode,"
+" int* pOptDaysIn1stYear );\n";
+
+std::string GetDiffDate_new=
+"int GetDiffDate_new( int nNullDate, int nStartDate, int nEndDate, int nMode,"
+" int* pOptDaysIn1stYear )\n"
+"{\n"
+" bool bNeg = nStartDate > nEndDate;\n"
+" if( bNeg )\n"
+" {\n"
+" int n = nEndDate;\n"
+" nEndDate = nStartDate;\n"
+" nStartDate = n;\n"
+" }\n"
+" int nRet;\n"
+" switch( nMode )\n"
+" {\n"
+" case 0: \n"
+" case 4: \n"
+" {\n"
+" int nD1, nM1, nY1, nD2, nM2, nY2;\n"
+" nStartDate += nNullDate;\n"
+" nEndDate += nNullDate;\n"
+" DaysToDate_new( nStartDate, &nD1, &nM1, &nY1 );\n"
+" DaysToDate_new( nEndDate, &nD2, &nM2, &nY2 );\n"
+" bool bLeap = IsLeapYear( nY1 );\n"
+" int nDays, nMonths;\n"
+" nMonths = nM2 - nM1;\n"
+" nDays = nD2 - nD1;\n"
+" nMonths += ( nY2 - nY1 ) * 12;\n"
+" nRet = nMonths * 30 + nDays;\n"
+" if( nMode == 0 && nM1 == 2 && nM2 != 2 && nY1 == nY2 )\n"
+" nRet -= bLeap? 1 : 2;\n"
+" if( pOptDaysIn1stYear )\n"
+" *pOptDaysIn1stYear = 360;\n"
+" }\n"
+" break;\n"
+" case 1: \n"
+" if( pOptDaysIn1stYear )\n"
+" {\n"
+" int nD, nM, nY;\n"
+" DaysToDate_new( nStartDate + nNullDate, &nD, &nM, &nY );\n"
+" *pOptDaysIn1stYear = IsLeapYear( nY )? 366 : 365;\n"
+" }\n"
+" nRet = nEndDate - nStartDate;\n"
+" break;\n"
+" case 2: \n"
+" nRet = nEndDate - nStartDate;\n"
+" if( pOptDaysIn1stYear )\n"
+" *pOptDaysIn1stYear = 360;\n"
+" break;\n"
+" case 3: \n"
+" nRet = nEndDate - nStartDate;\n"
+" if( pOptDaysIn1stYear )\n"
+" *pOptDaysIn1stYear = 365;\n"
+" break;\n"
+" }\n"
+" return bNeg? -nRet : nRet;\n"
+"}\n";
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/opencl/opinlinefun_math.hxx b/sc/source/core/opencl/opinlinefun_math.hxx
new file mode 100644
index 000000000..dc962b463
--- /dev/null
+++ b/sc/source/core/opencl/opinlinefun_math.hxx
@@ -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/.
+ */
+
+#pragma once
+
+#include <string>
+
+std::string Math_Intg_Str=
+"\ndouble Intg(double n)\n\
+{\n\
+ if(trunc(n)==n )\n\
+ return n;\n\
+ else if(n<0)\n\
+ return trunc(n)-1;\n\
+ else\n\
+ return trunc(n)+1;\n\
+}\n";
+
+std::string bikDecl = "double bik(double n,double k);\n";
+std::string bik =
+"double bik(double n,double k)\n"
+"{\n"
+" double nVal1 = n;\n"
+" double nVal2 = k;\n"
+" n = n - 1;\n"
+" k = k - 1;\n"
+" while (k > 0)\n"
+" {\n"
+" nVal1 = nVal1 * n;\n"
+" nVal2 = nVal2 * k;\n"
+" k = k - 1;\n"
+" n = n - 1;\n"
+" }\n"
+" return (nVal1 / nVal2);\n"
+"}\n";
+
+std::string local_cothDecl = "double local_coth(double n);\n";
+std::string local_coth =
+"double local_coth(double n)\n"
+"{\n"
+" double a = exp(n);\n"
+" double b = exp(-n);\n"
+" double nVal = (a + b) / (a - b);\n"
+" return nVal;\n"
+"}\n";
+
+std::string local_coshDecl = "double local_cosh(double n);\n";
+std::string local_cosh =
+"double local_cosh(double n)\n"
+"{\n"
+" double nVal = (exp(n) + exp(-n)) / 2;\n"
+" return nVal;\n"
+"}\n";
+std::string atan2Decl = "double arctan2(double y, double x);\n";
+std::string atan2Content =
+"double arctan2(double y, double x)\n"
+"{\n"
+" if(y==0.0)\n"
+" return 0.0;\n"
+" double a,num,den,tmpPi;\n"
+" int flag;\n"
+" tmpPi = 0;\n"
+" if (fabs(x) >= fabs(y))\n"
+" {\n"
+" num = y;\n"
+" den = x;\n"
+" flag = 1;\n"
+" if (x < 0.0)\n"
+" tmpPi = M_PI;\n"
+" }\n"
+" if(fabs(x) < fabs(y))\n"
+" {\n"
+" num = x;\n"
+" den = y;\n"
+" flag = -1;\n"
+" tmpPi = M_PI_2;\n"
+" }\n"
+" a = atan(num/den);\n"
+" a = flag==1?a:-a;\n"
+" a = a + (y >= 0.0 ? tmpPi : -tmpPi);\n"
+" return a;\n"
+"}\n";
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/opencl/opinlinefun_statistical.cxx b/sc/source/core/opencl/opinlinefun_statistical.cxx
new file mode 100644
index 000000000..f981345df
--- /dev/null
+++ b/sc/source/core/opencl/opinlinefun_statistical.cxx
@@ -0,0 +1,1366 @@
+/* -*- 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/.
+ */
+
+#ifndef SC_OPENCL_OPINLINFUN_statistical
+#define SC_OPENCL_OPINLINFUN_statistical
+std::string MinDecl = "#define Min 2.22507e-308\n";
+std::string F_PIDecl="#define M_PI 3.1415926535897932384626433832795\n";
+std::string fBigInvDecl ="#define fBigInv 2.22045e-016\n";
+std::string fMachEpsDecl ="#define fMachEps 2.22045e-016\n";
+std::string fLogDblMaxDecl ="#define fLogDblMax log(1.79769e+308)\n";
+std::string fHalfMachEpsDecl ="#define fHalfMachEps 0.5*2.22045e-016\n";
+std::string fMaxGammaArgumentDecl=
+"#define fMaxGammaArgument 171.624376956302\n";
+std::string GetValueDecl=
+"double GetValue( double x,double fp,double fDF );\n";
+std::string GetValue=
+"double GetValue( double x,double fp,double fDF )\n"
+"{\n"
+" return fp - 2 * GetTDist(x, fDF);\n"
+"}\n";
+std::string GetGammaSeriesDecl=
+"double GetGammaSeries( double fA, double fX );\n";
+std::string GetGammaSeries =
+"double GetGammaSeries( double fA, double fX )\n"
+"{\n"
+" double fDenomfactor = fA;\n"
+" double fSummand = 1.0/fA;\n"
+" double fSum = fSummand;\n"
+" int nCount=1;\n"
+" do\n"
+" {\n"
+" fDenomfactor = fDenomfactor + 1.0;\n"
+" fSummand = fSummand * fX/fDenomfactor;\n"
+" fSum = fSum + fSummand;\n"
+" nCount = nCount+1;\n"
+" } while ( fSummand/fSum > fHalfMachEps && nCount<=10000);\n"
+" if (nCount>10000)\n"
+" {\n"
+" }\n"
+" return fSum;\n"
+"}\n";
+std::string GetGammaContFractionDecl = "double GetGammaContFraction( double "
+"fA, double fX );\n";
+std::string GetGammaContFraction =
+"double GetGammaContFraction( double fA, double fX )\n"
+"{\n"
+" double fBig = 1.0/fBigInv;\n"
+" double fCount = 0.0;\n"
+" double fNum = 0.0;\n"
+" double fY = 1.0 - fA;\n"
+" double fDenom = fX + 2.0-fA;\n"
+" double fPk = 0.0;\n"
+" double fPkm1 = fX + 1.0;\n"
+" double fPkm2 = 1.0;\n"
+" double fQk = 1.0;\n"
+" double fQkm1 = fDenom * fX;\n"
+" double fQkm2 = fX;\n"
+" double fApprox = fPkm1/fQkm1;\n"
+" bool bFinished = false;\n"
+" double fR = 0.0;\n"
+" do\n"
+" {\n"
+" fCount = fCount +1.0;\n"
+" fY = fY+ 1.0;\n"
+" fNum = fY * fCount;\n"
+" fDenom = fDenom +2.0;\n"
+" fPk = fPkm1 * fDenom - fPkm2 * fNum;\n"
+" fQk = fQkm1 * fDenom - fQkm2 * fNum;\n"
+" if (fQk != 0.0)\n"
+" {\n"
+" fR = fPk/fQk;\n"
+" bFinished = (fabs( (fApprox - fR)/fR ) <= fHalfMachEps);\n"
+" fApprox = fR;\n"
+" }\n"
+" fPkm2 = fPkm1;\n"
+" fPkm1 = fPk;\n"
+" fQkm2 = fQkm1;\n"
+" fQkm1 = fQk;\n"
+" if (fabs(fPk) > fBig)\n"
+" {\n"
+" fPkm2 = fPkm2 * fBigInv;\n"
+" fPkm1 = fPkm1 * fBigInv;\n"
+" fQkm2 = fQkm2 * fBigInv;\n"
+" fQkm1 = fQkm1 * fBigInv;\n"
+" }\n"
+" } while (!bFinished && fCount<10000);\n"
+" if (!bFinished)\n"
+" {\n"
+" }\n"
+" return fApprox;\n"
+"}\n";
+std::string GetLowRegIGammaDecl = "double GetLowRegIGamma( double "
+"fA, double fX );\n";
+std::string GetLowRegIGamma =
+"double GetLowRegIGamma( double fA, double fX )\n"
+"{\n"
+" double fLnFactor = fA * log(fX) - fX - lgamma(fA);\n"
+" double fFactor = exp(fLnFactor);\n"
+" if (fX>fA+1.0) \n"
+" return 1.0 - fFactor * GetGammaContFraction(fA,fX);\n"
+" else\n"
+" return fFactor * GetGammaSeries(fA,fX);\n"
+"}\n";
+std::string GetGammaDistDecl = "double GetGammaDist( double fX, double "
+"fAlpha, double fLambda );\n";
+std::string GetGammaDist =
+"double GetGammaDist( double fX, double fAlpha, double fLambda )\n"
+"{\n"
+" if (fX <= 0.0)\n"
+" return 0.0;\n"
+" else\n"
+" return GetLowRegIGamma( fAlpha, fX / fLambda);\n"
+"}\n";
+std::string GetGammaDistPDFDecl = "double GetGammaDistPDF( double fX"
+", double fAlpha, double fLambda );\n";
+std::string GetGammaDistPDF =
+"double GetGammaDistPDF( double fX, double fAlpha, double fLambda )\n"
+"{\n"
+" if (fX < 0.0)\n"
+" return 0.0;\n"
+" else if (fX == 0)\n"
+" {\n"
+" if (fAlpha < 1.0)\n"
+" {\n"
+" return HUGE_VAL;\n"
+" }\n"
+" else if (fAlpha == 1)\n"
+" {\n"
+" return (1.0 / fLambda);\n"
+" }\n"
+" else\n"
+" {\n"
+" return 0.0;\n"
+" }\n"
+" }\n"
+" else\n"
+" {\n"
+" double fXr = fX / fLambda;\n"
+" if (fXr > 1.0)\n"
+" {\n"
+" if (log(fXr) * (fAlpha-1.0) < fLogDblMax &&"
+"fAlpha < fMaxGammaArgument)\n"
+" {\n"
+" return pow( fXr, fAlpha-1.0) * exp(-fXr) / "
+"fLambda / tgamma(fAlpha);\n"
+" }\n"
+" else\n"
+" {\n"
+" return exp( (fAlpha-1.0) * log(fXr) - fXr - "
+"log(fLambda) - lgamma(fAlpha));\n"
+" }\n"
+" }\n"
+" else\n"
+" {\n"
+" if (fAlpha<fMaxGammaArgument)\n"
+" {\n"
+" return pow( fXr, fAlpha-1.0) * exp(-fXr) / "
+"fLambda / tgamma(fAlpha);\n"
+" }\n"
+" else\n"
+" {\n"
+" return pow( fXr, fAlpha-1.0) * exp(-fXr) / "
+"fLambda / exp( lgamma(fAlpha));\n"
+" }\n"
+" }\n"
+" }\n"
+"}\n";
+std::string GetBetaDistDecl =
+"double GetBetaDist(double fXin, double fAlpha, double fBeta);\n";
+std::string GetBetaDist =
+"double GetBetaDist(double fXin, double fAlpha, double fBeta)\n"
+"{\n"
+" if (fXin <= 0.0)\n"
+" return 0.0;\n"
+" if (fXin >= 1.0)\n"
+" return 1.0;\n"
+" if (fBeta == 1.0)\n"
+" return pow(fXin, fAlpha);\n"
+" if (fAlpha == 1.0)\n"
+" return -expm1(fBeta * log1p(-fXin));\n"
+" double fResult;\n"
+" double fY = (0.5-fXin)+0.5;\n"
+" double flnY = log1p(-fXin);\n"
+" double fX = fXin;\n"
+" double flnX = log(fXin);\n"
+" double fA = fAlpha;\n"
+" double fB = fBeta;\n"
+" bool bReflect = fXin > fAlpha*pow((fAlpha+fBeta),-1.0);\n"
+" if (bReflect)\n"
+" {\n"
+" fA = fBeta;\n"
+" fB = fAlpha;\n"
+" fX = fY;\n"
+" fY = fXin;\n"
+" flnX = flnY;\n"
+" flnY = log(fXin);\n"
+" }\n"
+" fResult = lcl_GetBetaHelperContFrac(fX,fA,fB)*pow(fA,-1.0);\n"
+" double fP = fA*pow((fA+fB),-1.0);\n"
+" double fQ = fB*pow((fA+fB),-1.0);\n"
+" if (fA > 1.0 && fB > 1.0 && fP < 0.97 && fQ < 0.97)\n"
+" fResult *= GetBetaDistPDF(fX,fA,fB)*fX*fY;\n"
+" else\n"
+" fResult *= pow(exp(1.0),(fA*flnX + fB*flnY - GetLogBeta(fA,fB)));\n"
+" if (bReflect)\n"
+" fResult = 0.5 - fResult + 0.5;\n"
+" if (fResult > 1.0)\n"
+" fResult = 1.0;\n"
+" if (fResult < 0.0)\n"
+" fResult = 0.0;\n"
+" return fResult;\n"
+"}\n";
+
+std::string GetFDistDecl =
+ "double GetFDist(double x, double fF1, double fF2);\n";
+std::string GetFDist =
+"double GetFDist(double x, double fF1, double fF2)\n"
+"{\n"
+" double arg = fF2*pow((fF2+fF1*x),-1.0);\n"
+" double alpha = fF2*pow(2.0,-1.0);\n"
+" double beta = fF1*pow(2.0,-1.0);\n"
+" return (GetBetaDist(arg, alpha, beta));\n"
+"}\n";
+std::string GetGammaInvValueDecl = "double"
+" GetGammaInvValue(double fAlpha,double fBeta,double fX1 );\n";
+std::string GetGammaInvValue =
+"double GetGammaInvValue(double fAlpha,double fBeta,double fX1 )\n"
+"{\n"
+" if (fX1 <= 0.0)\n"
+" return 0.0;\n"
+" else\n"
+" {\n"
+" double fX=fX1*pow(fBeta,-1.0);\n"
+" double fLnFactor = fAlpha * log(fX) - fX - lgamma(fAlpha);\n"
+" double fFactor = exp(fLnFactor);\n"
+" if (fX>fAlpha+1.0)\n"
+" return 1.0 - fFactor * GetGammaContFraction(fAlpha,fX);\n"
+" else\n"
+" return fFactor * GetGammaSeries(fAlpha,fX);\n"
+" }\n"
+"}\n";
+std::string GetFInvValueDecl = "double GetFInvValue(double fF1,double fF2"
+",double fX1 );";
+std::string GetFInvValue =
+"double GetFInvValue(double fF1,double fF2,double fX1 )\n"
+"{\n"
+" double arg = fF2*pow((fF2+fF1*fX1),-1.0);\n"
+" double alpha = fF2*pow(2.0,-1.0);\n"
+" double beta = fF1*pow(2.0,-1.0);\n"
+" double fXin,fAlpha,fBeta;\n"
+" fXin=arg;fAlpha=alpha;fBeta=beta;\n"
+" if (fXin <= 0.0)\n"
+" return 0.0;\n"
+" if (fXin >= 1.0)\n"
+" return 1.0;\n"
+" if (fBeta == 1.0)\n"
+" return pow(fXin, fAlpha);\n"
+" if (fAlpha == 1.0)\n"
+" return -expm1(fBeta * log1p(-fXin));\n"
+" double fResult;\n"
+" double fY = (0.5-fXin)+0.5;\n"
+" double flnY = log1p(-fXin);\n"
+" double fX = fXin;\n"
+" double flnX = log(fXin);\n"
+" double fA = fAlpha;\n"
+" double fB = fBeta;\n"
+" bool bReflect = fXin > fAlpha*pow((fAlpha+fBeta),-1.0);\n"
+" if (bReflect)\n"
+" {\n"
+" fA = fBeta;\n"
+" fB = fAlpha;\n"
+" fX = fY;\n"
+" fY = fXin;\n"
+" flnX = flnY;\n"
+" flnY = log(fXin);\n"
+" }\n"
+" fResult = lcl_GetBetaHelperContFrac(fX,fA,fB);\n"
+" fResult = fResult*pow(fA,-1.0);\n"
+" double fP = fA*pow((fA+fB),-1.0);\n"
+" double fQ = fB*pow((fA+fB),-1.0);\n"
+" double fTemp;\n"
+" if (fA > 1.0 && fB > 1.0 && fP < 0.97 && fQ < 0.97)\n"
+" fTemp = GetBetaDistPDF(fX,fA,fB)*fX*fY;\n"
+" else\n"
+" fTemp = exp(fA*flnX + fB*flnY - GetLogBeta(fA,fB));\n"
+" fResult *= fTemp;\n"
+" if (bReflect)\n"
+" fResult = 0.5 - fResult + 0.5;\n"
+" if (fResult > 1.0)\n"
+" fResult = 1.0;\n"
+" if (fResult < 0.0)\n"
+" fResult = 0.0;\n"
+" return fResult;\n"
+"}\n";
+std::string GetBinomDistPMFDecl =
+ "double GetBinomDistPMF(double x, double n, double p);";
+std::string GetBinomDistPMF =
+"double GetBinomDistPMF(double x, double n, double p)\n"
+"{\n"
+" double q = (0.5 - p) + 0.5;\n"
+" double fFactor = pow(q, n);\n"
+" if (fFactor <= Min)\n"
+" {\n"
+" fFactor = pow(p, n);\n"
+" if (fFactor <= Min)\n"
+" return GetBetaDistPDF(p, x + 1.0, n - x + 1.0)*pow((n + 1.0),-1.0);\n"
+" else\n"
+" {\n"
+" uint max = (uint)(n - x);\n"
+" for (uint i = 0; i < max && fFactor > 0.0; ++i)\n"
+" fFactor *= (n - i)*pow((i + 1),-1.0)*q*pow(p,-1.0);\n"
+" return fFactor;\n"
+" }\n"
+" }\n"
+" else\n"
+" {\n"
+" uint max = (uint)x;\n"
+" for (uint i = 0; i < max && fFactor > 0.0; ++i)\n"
+" fFactor *= (n - i)*pow((i + 1),-1.0)*p*pow(q,-1.0);\n"
+" return fFactor;\n"
+" }\n"
+"}\n";
+
+std::string lcl_GetBinomDistRangeDecl =
+ "double lcl_GetBinomDistRange(double n, \n"
+"double xs, double xe, double fFactor, double p, double q);";
+std::string lcl_GetBinomDistRange=
+"double lcl_GetBinomDistRange(double n, double xs, double xe,\n"
+" double fFactor, double p, double q)\n"
+"{\n"
+" uint i;\n"
+" double fSum;\n"
+" uint nXs = (uint)xs;\n"
+" for (i = 1; i <= nXs && fFactor > 0.0; ++i)\n"
+" fFactor *= (n - i + 1)*pow(i,-1.0)*p*pow(q,-1.0);\n"
+" fSum = fFactor;\n"
+" uint nXe =(uint)xe;\n"
+" for (i = nXs + 1; i <= nXe && fFactor > 0.0; ++i)\n"
+" {\n"
+" fFactor *= (n - i + 1)*pow(i,-1.0)*p*pow(q,-1.0);\n"
+" fSum += fFactor;\n"
+" }\n"
+" return (fSum > 1.0) ? 1.0 : fSum;\n"
+"}\n";
+
+std::string GetLogGammaDecl = "double GetLogGamma(double fZ);\n";
+std::string GetLogGamma =
+"double GetLogGamma(double fZ)\n"
+"{\n"
+" if (fZ >= fMaxGammaArgument)\n"
+" return lcl_GetLogGammaHelper(fZ);\n"
+" if (fZ >= 1.0)\n"
+" return log(lcl_GetGammaHelper(fZ));\n"
+" if (fZ >= 0.5)\n"
+" return log( lcl_GetGammaHelper(fZ+1) *pow(fZ,-1.0));\n"
+" return lcl_GetLogGammaHelper(fZ+2) - log(fZ+1) - log(fZ);\n"
+"}\n";
+
+std::string GetChiDistDecl = "double GetChiDist(double fX, double fDF);\n";
+std::string GetChiDist =
+"double GetChiDist(double fX, double fDF)\n"
+"{\n"
+" if (fX <= 0.0)\n"
+" return 1.0;\n"
+" else\n"
+" return GetUpRegIGamma( fDF*pow(2.0,-1.0), fX*pow(2.0,-1.0));\n"
+"}\n";
+
+std::string GetChiSqDistCDFDecl =
+"double GetChiSqDistCDF(double fX, double fDF);\n";
+std::string GetChiSqDistCDF =
+"double GetChiSqDistCDF(double fX, double fDF)\n"
+"{\n"
+" if (fX <= 0.0)\n"
+" return 0.0;"
+" else\n"
+" return GetLowRegIGamma( fDF*pow(2.0,-1.0), fX*pow(2.0,-1.0));\n"
+"}\n";
+
+std::string GetChiSqDistPDFDecl=
+"double GetChiSqDistPDF(double fX, double fDF);\n";
+std::string GetChiSqDistPDF =
+"double GetChiSqDistPDF(double fX, double fDF)\n"
+"{\n"
+" double fValue;\n"
+" if (fX <= 0.0)\n"
+" return 0.0;\n"
+" if (fDF*fX > 1391000.0)\n"
+" {\n"
+" fValue = exp((0.5*fDF - 1) * log(fX*0.5) - 0.5 * fX - log(2.0)"
+" - lgamma(0.5*fDF));\n"
+" }\n"
+" else\n"
+" {\n"
+" double fCount;\n"
+" if (fmod(fDF,2.0)<0.5)\n"
+" {\n"
+" fValue = 0.5;\n"
+" fCount = 2.0;\n"
+" }\n"
+" else\n"
+" {\n"
+" fValue = pow(sqrt(fX*2*M_PI),-1.0);\n"
+" fCount = 1.0;\n"
+" }\n"
+" while ( fCount < fDF)\n"
+" {\n"
+" fValue *= (fX *pow(fCount,-1.0));\n"
+" fCount += 2.0;\n"
+" }\n"
+" if (fX>=1425.0)\n"
+" fValue = exp(log(fValue)-fX*pow(2,-1.0));\n"
+" else\n"
+" fValue *= exp(-fX*pow(2,-1.0));\n"
+" }\n"
+" return fValue;\n"
+"}\n";
+
+std::string lcl_IterateInverseBetaInvDecl =
+"static double lcl_IterateInverseBetaInv(double fp, double fAlpha, \n"
+" double fBeta, double fAx, double fBx, bool *rConvError );\n";
+std::string lcl_IterateInverseBetaInv =
+"static double lcl_IterateInverseBetaInv(double fp, double fAlpha, \n"
+" double fBeta, double fAx, double fBx, bool *rConvError )\n"
+"{\n"
+" *rConvError = false;\n"
+" double fYEps = 1.0E-307;\n"
+" double fXEps = fMachEps;\n"
+" if(!(fAx < fBx))\n"
+" {\n"
+" //print error\n"
+" }\n"
+" double fAy = fp - GetBetaDist(fAx, fAlpha, fBeta);\n"
+" double fBy = fp - GetBetaDist(fBx, fAlpha, fBeta);\n"
+" double fTemp;\n"
+" unsigned short nCount;\n"
+" for (nCount = 0; nCount < 1000 && !lcl_HasChangeOfSign(fAy,fBy);"
+" nCount++)\n"
+" {\n"
+" if (fabs(fAy) <= fabs(fBy))\n"
+" {\n"
+" fTemp = fAx;\n"
+" fAx += 2.0 * (fAx - fBx);\n"
+" if (fAx < 0.0)\n"
+" fAx = 0.0;\n"
+" fBx = fTemp;\n"
+" fBy = fAy;\n"
+" fAy = fp - GetBetaDist(fAx, fAlpha, fBeta);\n"
+" }\n"
+" else\n"
+" {\n"
+" fTemp = fBx;\n"
+" fBx += 2.0 * (fBx - fAx);\n"
+" fAx = fTemp;\n"
+" fAy = fBy;\n"
+" fBy = fp - GetBetaDist(fBx, fAlpha, fBeta);\n"
+" }\n"
+" }\n"
+" if (fAy == 0.0)\n"
+" return fAx;\n"
+" if (fBy == 0.0)\n"
+" return fBx;\n"
+" if (!lcl_HasChangeOfSign( fAy, fBy))\n"
+" {\n"
+" *rConvError = true;\n"
+" return 0.0;\n"
+" }\n"
+" double fPx = fAx;\n"
+" double fPy = fAy;\n"
+" double fQx = fBx;\n"
+" double fQy = fBy;\n"
+" double fRx = fAx;\n"
+" double fRy = fAy;\n"
+" double fSx = 0.5 * (fAx + fBx);\n"
+" bool bHasToInterpolate = true;\n"
+" nCount = 0;\n"
+" while ( nCount < 500 && fabs(fRy) > fYEps &&\n"
+" (fBx-fAx) > fmax( fabs(fAx), fabs(fBx)) * fXEps )\n"
+" {\n"
+" if (bHasToInterpolate)\n"
+" {\n"
+" if (fPy!=fQy && fQy!=fRy && fRy!=fPy)\n"
+" {\n"
+" fSx = fPx*fRy*fQy*pow(fRy-fPy,-1.0)*pow(fQy-fPy,-1.0)\n"
+" + fRx*fQy*fPy*pow(fQy-fRy,-1.0)*pow(fPy-fRy,-1.0)\n"
+" + fQx*fPy*fRy*pow(fPy-fQy,-1.0)*pow(fRy-fQy,-1.0);\n"
+" bHasToInterpolate = (fAx < fSx) && (fSx < fBx);\n"
+" }\n"
+" else\n"
+" bHasToInterpolate = false;\n"
+" }\n"
+" if(!bHasToInterpolate)\n"
+" {\n"
+" fSx = 0.5 * (fAx + fBx);\n"
+" fPx = fAx; fPy = fAy;\n"
+" fQx = fBx; fQy = fBy;\n"
+" bHasToInterpolate = true;\n"
+" }\n"
+" fPx = fQx; fQx = fRx; fRx = fSx;\n"
+" fPy = fQy; fQy = fRy; fRy = fp - GetBetaDist(fSx, fAlpha, fBeta);\n"
+" if (lcl_HasChangeOfSign( fAy, fRy))\n"
+" {\n"
+" fBx = fRx; fBy = fRy;\n"
+" }\n"
+" else\n"
+" {\n"
+" fAx = fRx; fAy = fRy;\n"
+" }\n"
+" bHasToInterpolate = bHasToInterpolate && (fabs(fRy) *"
+" 2.0 <= fabs(fQy));\n"
+" ++nCount;\n"
+" }\n"
+" return fRx;\n"
+"}\n";
+
+std::string lcl_IterateInverseChiInvDecl =
+"static double lcl_IterateInverseChiInv"
+"(double fp, double fdf, double fAx, double fBx, bool *rConvError);\n";
+std::string lcl_IterateInverseChiInv =
+"static double lcl_IterateInverseChiInv"
+"(double fp, double fdf, double fAx, double fBx, bool *rConvError)\n"
+"{\n"
+" *rConvError = false;\n"
+" double fYEps = 1.0E-307;\n"
+" double fXEps = fMachEps;\n"
+" if(!(fAx < fBx))\n"
+" {\n"
+" //print error\n"
+" }"
+" double fAy = fp - GetChiDist(fAx, fdf);\n"
+" double fBy = fp - GetChiDist(fBx, fdf);\n"
+" double fTemp;\n"
+" unsigned short nCount;\n"
+" for (nCount = 0; nCount < 1000 && "
+"!lcl_HasChangeOfSign(fAy,fBy); nCount++)\n"
+" {\n"
+" if (fabs(fAy) <= fabs(fBy))\n"
+" {\n"
+" fTemp = fAx;\n"
+" fAx += 2.0 * (fAx - fBx);\n"
+" if (fAx < 0.0)\n"
+" fAx = 0.0;\n"
+" fBx = fTemp;\n"
+" fBy = fAy;\n"
+" fAy = fp - GetChiDist(fAx, fdf);\n"
+" }\n"
+" else\n"
+" {\n"
+" fTemp = fBx;\n"
+" fBx += 2.0 * (fBx - fAx);\n"
+" fAx = fTemp;\n"
+" fAy = fBy;\n"
+" fBy = fp - GetChiDist(fBx, fdf);\n"
+" }\n"
+" }\n"
+" if (fAy == 0.0)\n"
+" return fAx;\n"
+" if (fBy == 0.0)\n"
+" return fBx;\n"
+" if (!lcl_HasChangeOfSign( fAy, fBy))\n"
+" {\n"
+" *rConvError = true;\n"
+" return 0.0;\n"
+" }\n"
+" double fPx = fAx;\n"
+" double fPy = fAy;\n"
+" double fQx = fBx;\n"
+" double fQy = fBy;\n"
+" double fRx = fAx;\n"
+" double fRy = fAy;\n"
+" double fSx = 0.5 * (fAx + fBx);\n"
+" bool bHasToInterpolate = true;\n"
+" nCount = 0;\n"
+" while ( nCount < 500 && fabs(fRy) > fYEps &&\n"
+" (fBx-fAx) > fmax( fabs(fAx), fabs(fBx)) * fXEps )\n"
+" {\n"
+" if (bHasToInterpolate)\n"
+" {\n"
+" if (fPy!=fQy && fQy!=fRy && fRy!=fPy)\n"
+" {\n"
+" fSx = fPx * fRy * fQy*pow(fRy-fPy,-1.0)*pow(fQy-fPy,-1.0)\n"
+" + fRx * fQy * fPy*pow(fQy-fRy,-1.0)*pow(fPy-fRy,-1.0)\n"
+" + fQx * fPy * fRy*pow(fPy-fQy,-1.0)*pow(fRy-fQy,-1.0);\n"
+" bHasToInterpolate = (fAx < fSx) && (fSx < fBx);\n"
+" }\n"
+" else\n"
+" bHasToInterpolate = false;\n"
+" }\n"
+" if(!bHasToInterpolate)\n"
+" {\n"
+" fSx = 0.5 * (fAx + fBx);\n"
+" fPx = fAx; fPy = fAy;\n"
+" fQx = fBx; fQy = fBy;\n"
+" bHasToInterpolate = true;\n"
+" }\n"
+" fPx = fQx; fQx = fRx; fRx = fSx;\n"
+" fPy = fQy; fQy = fRy; fRy = fp - GetChiDist(fSx, fdf);\n"
+" if (lcl_HasChangeOfSign( fAy, fRy))\n"
+" {\n"
+" fBx = fRx; fBy = fRy;\n"
+" }\n"
+" else\n"
+" {\n"
+" fAx = fRx; fAy = fRy;\n"
+" }\n"
+" bHasToInterpolate = bHasToInterpolate && (fabs(fRy)"
+" * 2.0 <= fabs(fQy));\n"
+" ++nCount;\n"
+" }\n"
+" return fRx;\n"
+"}\n";
+
+std::string lcl_IterateInverseChiSQInvDecl =
+"static double lcl_IterateInverseChiSQInv( double fp, double fdf, \n"
+" double fAx, double fBx, bool *rConvError );\n";
+std::string lcl_IterateInverseChiSQInv =
+"static double lcl_IterateInverseChiSQInv( double fp, double fdf, \n"
+" double fAx, double fBx, bool *rConvError )\n"
+"{\n"
+" *rConvError = false;\n"
+" double fYEps = 1.0E-307;\n"
+" double fXEps = fMachEps;\n"
+
+" if(!(fAx < fBx))\n"
+" {\n"
+" //print error\n"
+" }\n"
+" double fAy = fp - GetChiSqDistCDF(fAx, fdf);\n"
+" double fBy = fp - GetChiSqDistCDF(fBx, fdf);\n"
+" double fTemp;\n"
+" unsigned short nCount;\n"
+" for (nCount = 0; nCount < 1000 && !lcl_HasChangeOfSign(fAy,fBy);"
+" nCount++)\n"
+" {\n"
+" if (fabs(fAy) <= fabs(fBy))\n"
+" {\n"
+" fTemp = fAx;\n"
+" fAx += 2.0 * (fAx - fBx);\n"
+" if (fAx < 0.0)\n"
+" fAx = 0.0;\n"
+" fBx = fTemp;\n"
+" fBy = fAy;\n"
+" fAy = fp - GetChiSqDistCDF(fAx, fdf);\n"
+" }\n"
+" else\n"
+" {\n"
+" fTemp = fBx;\n"
+" fBx += 2.0 * (fBx - fAx);\n"
+" fAx = fTemp;\n"
+" fAy = fBy;\n"
+" fBy = fp - GetChiSqDistCDF(fBx, fdf);\n"
+" }\n"
+" }\n"
+" if (fAy == 0.0)\n"
+" return fAx;\n"
+" if (fBy == 0.0)\n"
+" return fBx;\n"
+" if (!lcl_HasChangeOfSign( fAy, fBy))\n"
+" {\n"
+" *rConvError = true;\n"
+" return 0.0;\n"
+" }\n"
+" double fPx = fAx;\n"
+" double fPy = fAy;\n"
+" double fQx = fBx;\n"
+" double fQy = fBy;\n"
+" double fRx = fAx;\n"
+" double fRy = fAy;\n"
+" double fSx = 0.5 * (fAx + fBx);\n"
+" bool bHasToInterpolate = true;\n"
+" nCount = 0;\n"
+" while ( nCount < 500 && fabs(fRy) > fYEps &&\n"
+" (fBx-fAx) > fmax( fabs(fAx), fabs(fBx)) * fXEps )\n"
+" {\n"
+" if (bHasToInterpolate)\n"
+" {\n"
+" if (fPy!=fQy && fQy!=fRy && fRy!=fPy)\n"
+" {\n"
+" fSx = fPx * fRy * fQy / (fRy-fPy) / (fQy-fPy)\n"
+" + fRx * fQy * fPy / (fQy-fRy) / (fPy-fRy)\n"
+" + fQx * fPy * fRy / (fPy-fQy) / (fRy-fQy);\n"
+" bHasToInterpolate = (fAx < fSx) && (fSx < fBx);\n"
+" }\n"
+" else\n"
+" bHasToInterpolate = false;\n"
+" }\n"
+" if(!bHasToInterpolate)\n"
+" {\n"
+" fSx = 0.5 * (fAx + fBx);\n"
+" fPx = fAx; fPy = fAy;\n"
+" fQx = fBx; fQy = fBy;\n"
+" bHasToInterpolate = true;\n"
+" }\n"
+" fPx = fQx; fQx = fRx; fRx = fSx;\n"
+" fPy = fQy; fQy = fRy; fRy = fp - GetChiSqDistCDF(fSx, fdf);\n"
+" if (lcl_HasChangeOfSign( fAy, fRy))\n"
+" {\n"
+" fBx = fRx; fBy = fRy;\n"
+" }\n"
+" else\n"
+" {\n"
+" fAx = fRx; fAy = fRy;\n"
+" }\n"
+" bHasToInterpolate = bHasToInterpolate && (fabs(fRy) * 2.0"
+" <= fabs(fQy));\n"
+" ++nCount;\n"
+" }\n"
+" return fRx;\n"
+"}\n";
+
+std::string gaussinvDecl = "double gaussinv(double x);\n";
+std::string gaussinv =
+"double gaussinv(double x)\n"
+"{\n"
+" double q,t,z;\n"
+" q=x-0.5;\n"
+" if(fabs(q)<=.425)\n"
+" {\n"
+" t=0.180625-q*q;\n"
+" z=\n"
+" q*\n"
+" (\n"
+" (\n"
+" (\n"
+" (\n"
+" (\n"
+" (\n"
+" (\n"
+" t*2509.0809287301226727+33430.575583588128105\n"
+" )\n"
+" *t+67265.770927008700853\n"
+" )\n"
+" *t+45921.953931549871457\n"
+" )\n"
+" *t+13731.693765509461125\n"
+" )\n"
+" *t+1971.5909503065514427\n"
+" )\n"
+" *t+133.14166789178437745\n"
+" )\n"
+" *t+3.387132872796366608\n"
+" )\n"
+" *pow\n"
+" (\n"
+" (\n"
+" (\n"
+" (\n"
+" (\n"
+" (\n"
+" (\n"
+" t*5226.495278852854561+28729.085735721942674\n"
+" )\n"
+" *t+39307.89580009271061\n"
+" )\n"
+" *t+21213.794301586595867\n"
+" )\n"
+" *t+5394.1960214247511077\n"
+" )\n"
+" *t+687.1870074920579083\n"
+" )\n"
+" *t+42.313330701600911252\n"
+" )\n"
+" *t+1.0\n"
+" , -1.0);\n"
+" }\n"
+" else\n"
+" {\n"
+" if(q>0) t=1-x;\n"
+" else t=x;\n"
+" t=sqrt(-log(t));\n"
+" if(t<=5.0)\n"
+" {\n"
+" t+=-1.6;\n"
+" z=\n"
+" (\n"
+" (\n"
+" (\n"
+" (\n"
+" (\n"
+" (\n"
+" (\n"
+" t*7.7454501427834140764e-4+0.0227238449892691845833\n"
+" )\n"
+" *t+0.24178072517745061177\n"
+" )\n"
+" *t+1.27045825245236838258\n"
+" )\n"
+" *t+3.64784832476320460504\n"
+" )\n"
+" *t+5.7694972214606914055\n"
+" )\n"
+" *t+4.6303378461565452959\n"
+" )\n"
+" *t+1.42343711074968357734\n"
+" )\n"
+" *pow\n"
+" (\n"
+" (\n"
+" (\n"
+" (\n"
+" (\n"
+" (\n"
+" (\n"
+" t*1.05075007164441684324e-9+5.475938084995344946e-4\n"
+" )\n"
+" *t+0.0151986665636164571966\n"
+" )\n"
+" *t+0.14810397642748007459\n"
+" )\n"
+" *t+0.68976733498510000455\n"
+" )\n"
+" *t+1.6763848301838038494\n"
+" )\n"
+" *t+2.05319162663775882187\n"
+" )\n"
+" *t+1.0\n"
+" , -1.0);\n"
+" }\n"
+" else\n"
+" {\n"
+" t+=-5.0;\n"
+" z=\n"
+" (\n"
+" (\n"
+" (\n"
+" (\n"
+" (\n"
+" (\n"
+" (\n"
+" t*2.01033439929228813265e-7+2.71155556874348757815e-5\n"
+" )\n"
+" *t+0.0012426609473880784386\n"
+" )\n"
+" *t+0.026532189526576123093\n"
+" )\n"
+" *t+0.29656057182850489123\n"
+" )\n"
+" *t+1.7848265399172913358\n"
+" )\n"
+" *t+5.4637849111641143699\n"
+" )\n"
+" *t+6.6579046435011037772\n"
+" )\n"
+" *pow\n"
+" (\n"
+" (\n"
+" (\n"
+" (\n"
+" (\n"
+" (\n"
+" (\n"
+" t*2.04426310338993978564e-15+1.4215117583164458887e-7\n"
+" )\n"
+" *t+1.8463183175100546818e-5\n"
+" )\n"
+" *t+7.868691311456132591e-4\n"
+" )\n"
+" *t+0.0148753612908506148525\n"
+" )\n"
+" *t+0.13692988092273580531\n"
+" )\n"
+" *t+0.59983220655588793769\n"
+" )\n"
+" *t+1.0\n"
+" , -1.0);\n"
+" }\n"
+" if(q<0.0) z=-z;\n"
+" }\n"
+" return z;\n"
+"}\n";
+
+std::string lcl_GetLogGammaHelperDecl=
+"static double lcl_GetLogGammaHelper(double fZ);\n";
+std::string lcl_GetLogGammaHelper =
+"static double lcl_GetLogGammaHelper(double fZ)\n"
+"{\n"
+" double fg = 6.024680040776729583740234375;\n"
+" double fZgHelp = fZ + fg - 0.5;\n"
+" return log( lcl_getLanczosSum(fZ)) + (fZ-0.5) * log(fZgHelp) - fZgHelp;\n"
+"}\n";
+std::string lcl_GetGammaHelperDecl=
+"static double lcl_GetGammaHelper(double fZ);\n";
+std::string lcl_GetGammaHelper =
+"static double lcl_GetGammaHelper(double fZ)\n"
+"{\n"
+" double fGamma = lcl_getLanczosSum(fZ);\n"
+" double fg = 6.024680040776729583740234375;\n"
+" double fZgHelp = fZ + fg - 0.5;\n"
+" double fHalfpower = pow( fZgHelp, fZ*pow(2,-1.0) - 0.25);\n"
+" fGamma *= fHalfpower;\n"
+" fGamma = fGamma*pow(exp(fZgHelp),-1.0);\n"
+" fGamma *= fHalfpower;\n"
+" fGamma = 120.4;\n"
+" if (fZ <= 20.0 && fZ == (int)fZ)\n"
+" {\n"
+" fGamma = (int)(fGamma+0.5);\n"
+" }\n"
+" return fGamma;\n"
+"}\n";
+std::string lcl_getLanczosSumDecl=
+"static double lcl_getLanczosSum(double fZ);\n";
+std::string lcl_getLanczosSum =
+"static double lcl_getLanczosSum(double fZ) \n"
+"{ \n"
+" double fNum[13] ={ \n"
+" 23531376880.41075968857200767445163675473, \n"
+" 42919803642.64909876895789904700198885093, \n"
+" 35711959237.35566804944018545154716670596, \n"
+" 17921034426.03720969991975575445893111267, \n"
+" 6039542586.35202800506429164430729792107, \n"
+" 1439720407.311721673663223072794912393972, \n"
+" 248874557.8620541565114603864132294232163, \n"
+" 31426415.58540019438061423162831820536287, \n"
+" 2876370.628935372441225409051620849613599, \n"
+" 186056.2653952234950402949897160456992822, \n"
+" 8071.672002365816210638002902272250613822, \n"
+" 210.8242777515793458725097339207133627117, \n"
+" 2.506628274631000270164908177133837338626 \n"
+" }; \n"
+" double fDenom[13] = { \n"
+" 0,\n"
+" 39916800,\n"
+" 120543840,\n"
+" 150917976,\n"
+" 105258076,\n"
+" 45995730,\n"
+" 13339535,\n"
+" 2637558,\n"
+" 357423,\n"
+" 32670,\n"
+" 1925,\n"
+" 66,\n"
+" 1\n"
+" };\n"
+" double fSumNum;\n"
+" double fSumDenom;\n"
+" int nI;\n"
+" if (fZ<=1.0)\n"
+" {\n"
+" fSumNum = fNum[12];\n"
+" fSumDenom = fDenom[12];\n"
+" nI = 11;\n"
+" fSumNum = fSumNum*fZ+fNum[nI];\n"
+" fSumDenom = fSumDenom*fZ+fDenom[nI];\n"
+" nI = 10;\n"
+" fSumNum = fSumNum*fZ+fNum[nI];\n"
+" fSumDenom = fSumDenom*fZ+fDenom[nI];\n"
+" nI = 9;\n"
+" fSumNum = fSumNum*fZ+fNum[nI];\n"
+" fSumDenom = fSumDenom*fZ+fDenom[nI];\n"
+" nI = 8;\n"
+" fSumNum = fSumNum*fZ+fNum[nI];\n"
+" fSumDenom = fSumDenom*fZ+fDenom[nI];\n"
+" nI = 7;\n"
+" fSumNum = fSumNum*fZ+fNum[nI];\n"
+" fSumDenom = fSumDenom*fZ+fDenom[nI];\n"
+" nI = 6;\n"
+" fSumNum = fSumNum*fZ+fNum[nI];\n"
+" fSumDenom = fSumDenom*fZ+fDenom[nI];\n"
+" nI = 5;\n"
+" fSumNum = fSumNum*fZ+fNum[nI];\n"
+" fSumDenom = fSumDenom*fZ+fDenom[nI];\n"
+" nI = 4;\n"
+" fSumNum = fSumNum*fZ+fNum[nI];\n"
+" fSumDenom = fSumDenom*fZ+fDenom[nI];\n"
+" nI = 3;\n"
+" fSumNum = fSumNum*fZ+fNum[nI];\n"
+" fSumDenom = fSumDenom*fZ+fDenom[nI];\n"
+" nI = 2;\n"
+" fSumNum = fSumNum*fZ+fNum[nI];\n"
+" fSumDenom = fSumDenom*fZ+fDenom[nI];\n"
+" nI = 1;\n"
+" fSumNum = fSumNum*fZ+fNum[nI];\n"
+" fSumDenom = fSumDenom*fZ+fDenom[nI];\n"
+" nI = 0;\n"
+" fSumNum = fSumNum*fZ+fNum[nI];\n"
+" fSumDenom = fSumDenom*fZ+fDenom[nI];\n"
+" }\n"
+" if (fZ>1.0)\n"
+" {\n"
+" double fZInv = pow(fZ,-1.0);\n"
+" fSumNum = fNum[0];\n"
+" fSumDenom = fDenom[0];\n"
+" nI = 1;\n"
+" fSumNum = fSumNum*fZInv+fNum[nI];\n"
+" fSumDenom = fSumDenom*fZInv+fDenom[nI];\n"
+" nI = 2;\n"
+" fSumNum = fSumNum*fZInv+fNum[nI];\n"
+" fSumDenom = fSumDenom*fZInv+fDenom[nI];\n"
+" nI = 3;\n"
+" fSumNum = fSumNum*fZInv+fNum[nI];\n"
+" fSumDenom = fSumDenom*fZInv+fDenom[nI];\n"
+" nI = 4;\n"
+" fSumNum = fSumNum*fZInv+fNum[nI];\n"
+" fSumDenom = fSumDenom*fZInv+fDenom[nI];\n"
+" nI = 5;\n"
+" fSumNum = fSumNum*fZInv+fNum[nI];\n"
+" fSumDenom = fSumDenom*fZInv+fDenom[nI];\n"
+" nI = 6;\n"
+" fSumNum = fSumNum*fZInv+fNum[nI];\n"
+" fSumDenom = fSumDenom*fZInv+fDenom[nI];\n"
+" nI = 7;\n"
+" fSumNum = fSumNum*fZInv+fNum[nI];\n"
+" fSumDenom = fSumDenom*fZInv+fDenom[nI];\n"
+" nI = 8;\n"
+" fSumNum = fSumNum*fZInv+fNum[nI];\n"
+" fSumDenom = fSumDenom*fZInv+fDenom[nI];\n"
+" nI = 9;\n"
+" fSumNum = fSumNum*fZInv+fNum[nI];\n"
+" fSumDenom = fSumDenom*fZInv+fDenom[nI];\n"
+" nI = 10;\n"
+" fSumNum = fSumNum*fZInv+fNum[nI];\n"
+" fSumDenom = fSumDenom*fZInv+fDenom[nI];\n"
+" nI = 11;\n"
+" fSumNum = fSumNum*fZInv+fNum[nI];\n"
+" fSumDenom = fSumDenom*fZInv+fDenom[nI];\n"
+" nI = 12;\n"
+" fSumNum = fSumNum*fZInv+fNum[nI];\n"
+" fSumDenom = fSumDenom*fZInv+fDenom[nI];\n"
+" }\n"
+" return fSumNum*pow(fSumDenom,-1.0);\n"
+"}\n";
+
+std::string GetUpRegIGammaDecl=
+" double GetUpRegIGamma( double fA, double fX ) ;\n";
+std::string GetUpRegIGamma =
+"double GetUpRegIGamma( double fA, double fX )\n"
+"{\n"
+" double fLnFactor= fA*log(fX)-fX-lgamma(fA);\n"
+" double fFactor = exp(fLnFactor); \n"
+" if (fX>fA+1.0) \n"
+" return fFactor * GetGammaContFraction(fA,fX);\n"
+" else \n"
+" return 1.0 -fFactor * GetGammaSeries(fA,fX);\n"
+"}\n";
+
+std::string lcl_HasChangeOfSignDecl=
+"static inline bool lcl_HasChangeOfSign( double u, double w );\n";
+std::string lcl_HasChangeOfSign =
+"static inline bool lcl_HasChangeOfSign( double u, double w )\n"
+"{\n"
+" return (u < 0.0 && w > 0.0) || (u > 0.0 && w < 0.0);\n"
+"}\n";
+
+std::string GetTDistDecl=" double GetTDist(double T, double fDF);\n";
+std::string GetTDist =
+"double GetTDist(double T, double fDF)\n"
+"{\n"
+" return 0.5 * GetBetaDist(fDF*pow(fDF+T*T,-1.0),fDF*pow(2.0,-1.0), 0.5);\n"
+"}\n";
+
+std::string GetBetaDecl=" double GetBeta(double fAlpha, double fBeta);\n";
+std::string GetBeta =
+"double GetBeta(double fAlpha, double fBeta)\n"
+"{\n"
+" double fA;\n"
+" double fB;\n"
+" fAlpha>fBeta?(fA = fAlpha,fB = fBeta):(fA = fBeta,fB = fAlpha);\n"
+" double fAB = fA + fB;\n"
+
+" if (fAB < fMaxGammaArgument)\n"
+" return tgamma(fA)*pow(tgamma(fAB),-1.0)*tgamma(fB);\n"
+" double fgm = 5.524680040776729583740234375;\n"
+" double fLanczos = lcl_getLanczosSum(fA)*lcl_getLanczosSum(fB)\n"
+" *pow(lcl_getLanczosSum(fAB),-1.0);\n"
+" fLanczos *= sqrt(((fAB + fgm)*pow(fA + fgm,-1.0))*pow(fB + fgm,-1.0));\n"
+" return fLanczos * pow(exp(1.0),(-fA*log1p(fB*pow(fA + fgm,-1.0)))"
+" - fB*log1p(fA*pow(fB + fgm,-1.0)) - fgm);\n"
+"}\n";
+
+std::string GetLogBetaDecl=
+" double GetLogBeta(double fAlpha, double fBeta);\n";
+std::string GetLogBeta =
+"double GetLogBeta(double fAlpha, double fBeta)\n"
+"{\n"
+" double fA;\n"
+" double fB;\n"
+" fAlpha>fBeta?(fA = fAlpha,fB = fBeta):(fA = fBeta,fB = fAlpha);\n"
+" double fgm = 5.524680040776729583740234375;\n"
+
+" double fLanczos = lcl_getLanczosSum(fA)*lcl_getLanczosSum(fB)*\n"
+" pow(lcl_getLanczosSum(fA + fB),-1.0);\n"
+" double fResult= -fA *log1p(fB*pow(fA + fgm,-1.0))"
+"-fB *log1p(fA*pow(fB + fgm,-1.0))-fgm;\n"
+" fResult += log(fLanczos)+0.5*(log(fA + fB + fgm) - log(fA + fgm)\n"
+" - log(fB + fgm));\n"
+" return fResult;\n"
+"}\n";
+
+std::string GetBetaDistPDFDecl=
+"double GetBetaDistPDF(double fX, double fA, double fB);\n";
+std::string GetBetaDistPDF =
+"double GetBetaDistPDF(double fX, double fA, double fB)\n"
+"{\n"
+" if (fA == 1.0) \n"
+" {\n"
+" if (fB == 1.0)\n"
+" return 1.0;\n"
+" if (fB == 2.0)\n"
+" return -2.0*fX + 2.0;\n"
+" if (fX == 1.0 && fB < 1.0)\n"
+" {\n"
+" return HUGE_VAL;\n"
+" }\n"
+" if (fX <= 0.01)\n"
+" return fB + fB * expm1((fB-1.0) * log1p(-fX));\n"
+" else \n"
+" return fB * pow(0.5-fX+0.5,fB-1.0);\n"
+" }\n"
+" if (fB == 1.0) \n"
+" {\n"
+" if (fA == 2.0)\n"
+" return fA * fX;\n"
+" if (fX == 0.0 && fA < 1.0)\n"
+" {\n"
+" return HUGE_VAL;\n"
+" }\n"
+" return fA * pow(fX,fA-1);\n"
+" }\n"
+" if (fX <= 0.0)\n"
+" {\n"
+" if (fA < 1.0 && fX == 0.0)\n"
+" {\n"
+" return HUGE_VAL;\n"
+" }\n"
+" else\n"
+" return 0.0;\n"
+" }\n"
+" if (fX >= 1.0)\n"
+" {\n"
+" if (fB < 1.0 && fX == 1.0)\n"
+" {\n"
+" return HUGE_VAL;\n"
+" }\n"
+" else \n"
+" return 0.0;\n"
+" }\n"
+" double fLogDblMax = log( 1.79769e+308 );\n"
+" double fLogDblMin = log( 2.22507e-308 );\n"
+" double fLogY = (fX < 0.1) ? log1p(-fX) : log(0.5-fX+0.5);\n"
+" double fLogX = log(fX);\n"
+" double fAm1LogX = (fA-1.0) * fLogX;\n"
+" double fBm1LogY = (fB-1.0) * fLogY;\n"
+" double fLogBeta = GetLogBeta(fA,fB);\n"
+" if ( fAm1LogX < fLogDblMax && fAm1LogX > fLogDblMin\n"
+" && fBm1LogY < fLogDblMax && fBm1LogY > fLogDblMin\n"
+" && fLogBeta < fLogDblMax && fLogBeta > fLogDblMin\n"
+" && fAm1LogX + fBm1LogY < fLogDblMax && fAm1LogX + fBm1LogY > \n"
+" fLogDblMin)\n"
+" return pow(fX,fA-1.0)*pow(0.5-fX+0.5,fB-1.0)"
+"*pow(GetBeta(fA,fB),-1.0);\n"
+" else \n"
+" return exp( fAm1LogX + fBm1LogY - fLogBeta);\n"
+"}\n";
+
+std::string lcl_GetBetaHelperContFracDecl=
+"double lcl_GetBetaHelperContFrac(double fX, double fA, double fB);\n";
+std::string lcl_GetBetaHelperContFrac =
+"double lcl_GetBetaHelperContFrac(double fX, double fA, double fB)\n"
+"{ \n"
+
+" double a1, b1, a2, b2, fnorm, apl2m, d2m, d2m1, cfnew, cf;\n"
+" a1 = 1.0; b1 = 1.0;\n"
+" b2 = 1.0 - (fA+fB)*pow(fA+1.0,-1.0)*fX;\n"
+" b2==0.0?(a2 = 0.0,fnorm = 1.0,cf = 1.0):\n"
+" (a2 = 1.0,fnorm = pow(b2,-1.0),cf = a2*fnorm);\n"
+" cfnew = 1.0;\n"
+" double rm = 1.0;\n"
+" double fMaxIter = 50000.0;\n"
+" bool bfinished = false;\n"
+" do\n"
+" {\n"
+" apl2m = fA + 2.0*rm;\n"
+" d2m = (rm*(fB-rm))*fX*pow(apl2m*(apl2m-1.0),-1.0);\n"
+" d2m1 = -((fA+rm)*(fA+rm+fB))*fX*pow(apl2m*(apl2m+1.0),-1.0);\n"
+" a1 = (a2+d2m*a1)*fnorm;\n"
+" b1 = (b2+d2m*b1)*fnorm;\n"
+" a2 = a1 + d2m1*a2*fnorm;\n"
+" b2 = b1 + d2m1*b2*fnorm;\n"
+" if (b2 != 0.0) \n"
+" {\n"
+" fnorm = pow(b2,-1.0);\n"
+" cfnew = a2*fnorm;\n"
+" bfinished = (fabs(cf-cfnew) < fabs(cf)*fMachEps);\n"
+" }\n"
+" cf = cfnew;\n"
+" rm += 1.0;\n"
+" }\n"
+" while (rm < fMaxIter && !bfinished);\n"
+" return cf;\n"
+"}\n";
+
+std::string lcl_IterateInverseDecl=
+"double lcl_IterateInverse("
+"double fAx, double fBx, bool* rConvError,double fp,double fDF );\n";
+std::string lcl_IterateInverse =
+"double lcl_IterateInverse( "
+"double fAx, double fBx, bool* rConvError,double fp,double fDF )\n"
+"{\n"
+" *rConvError = false;\n"
+" double fYEps = 1.0E-307;\n"
+" double fXEps =DBL_EPSILON;\n"
+" if(fAx>fBx)\n"
+" return DBL_MAX;\n"
+" double fAy = GetValue(fAx,fp,fDF);\n"
+" double fBy = GetValue(fBx,fp,fDF);\n"
+" double fTemp;\n"
+" unsigned short nCount;\n"
+" double inter;\n"
+" bool sign;\n"
+" for (nCount =0;nCount<1000&&!lcl_HasChangeOfSign(fAy,fBy);nCount++)\n"
+" {\n"
+" inter = 2.0 * (fAx - fBx);\n"
+" if (fabs(fAy) <= fabs(fBy)) \n"
+" {\n"
+" sign = true;\n"
+" fTemp = fAx;\n"
+" fAx += inter;\n"
+" if (fAx < 0.0)\n"
+" fAx = 0.0;\n"
+" fBx = fTemp;\n"
+" fBy = fAy;\n"
+" fTemp = fAx;\n"
+" }\n"
+" else\n"
+" {\n"
+" sign = false;\n"
+" fTemp = fBx;\n"
+" fBx -= inter;\n"
+" fAx = fTemp;\n"
+" fAy = fBy;\n"
+" fTemp = fBx;\n"
+" }\n"
+" fTemp = GetValue(fTemp,fp,fDF);\n"
+" sign?(fAy = fTemp):(fBy = fTemp);\n"
+" }\n"
+" if (fAy == 0.0)\n"
+" return fAx;\n"
+" if (fBy == 0.0)\n"
+" return fBx;\n"
+" if (!lcl_HasChangeOfSign( fAy, fBy))\n"
+" {\n"
+" *rConvError = true;\n"
+" return 0.0;\n"
+" }\n"
+" double fPx = fAx;\n"
+" double fPy = fAy;\n"
+" double fQx = fBx;\n"
+" double fQy = fBy;\n"
+" double fRx = fAx;\n"
+" double fRy = fAy;\n"
+" double fSx = 0.5 * (fAx + fBx); \n"
+" bool bHasToInterpolate = true;\n"
+" nCount = 0;\n"
+" while ( nCount < 500 && fabs(fRy) > fYEps &&\n"
+" (fBx-fAx) > max( fabs(fAx), fabs(fBx)) * fXEps )\n"
+" {\n"
+" if (bHasToInterpolate)\n"
+" {\n"
+" if (fPy!=fQy && fQy!=fRy && fRy!=fPy)\n"
+" {\n"
+" fSx = fPx * fRy * fQy * pow(fRy-fPy,-1.0)*pow(fQy-fPy,-1.0)\n"
+" + fRx * fQy * fPy * pow(fQy-fRy,-1.0)*pow(fPy-fRy,-1.0)\n"
+" + fQx * fPy * fRy * pow(fPy-fQy,-1.0)*pow(fRy-fQy,-1.0);\n"
+" bHasToInterpolate = (fAx < fSx) && (fSx < fBx);\n"
+" }\n"
+" else\n"
+" bHasToInterpolate = false;\n"
+" }\n"
+" if(!bHasToInterpolate)\n"
+" {\n"
+" fSx = 0.5 * (fAx + fBx);\n"
+" \n"
+" fPx = fAx; fPy = fAy;\n"
+" fQx = fBx; fQy = fBy;\n"
+" bHasToInterpolate = true;\n"
+" }\n"
+" fPx = fQx; fQx = fRx; fRx = fSx;\n"
+" fPy = fQy; fQy = fRy; fRy = GetValue(fSx,fp,fDF);\n"
+" lcl_HasChangeOfSign( fAy, fRy)?(fBx = fRx,fBy = fRy):\n"
+" (fAx = fRx,fAy = fRy);\n"
+" bHasToInterpolate =\n"
+" bHasToInterpolate && (fabs(fRy) * 2.0 <= fabs(fQy));\n"
+" ++nCount;\n"
+" }\n"
+" return fRx;\n"
+"}\n";
+std::string phiDecl=
+"double phi(double x);\n";
+std::string phi =
+"double phi(double x)\n"
+"{\n"
+" return 0.39894228040143268 * exp(-(x * x) / 2.0);\n"
+"}\n";
+std::string taylorDecl =
+"double taylor(double* pPolynom, uint nMax, double x);\n";
+std::string taylor =
+"double taylor(double* pPolynom, uint nMax, double x)\n"
+"{\n"
+" double nVal = pPolynom[nMax];\n"
+" for (short i = nMax-1; i >= 0; i--)\n"
+" {\n"
+" nVal = pPolynom[i] + (nVal * x);\n"
+" }\n"
+" return nVal;\n"
+"}";
+std::string gaussDecl = "double gauss(double x);\n";
+std::string gauss =
+"double gauss(double x)\n"
+"{\n"
+" double xAbs = fabs(x);\n"
+" uint xShort = (uint)(floor(xAbs));\n"
+" double nVal = 0.0;\n"
+" if (xShort == 0)\n"
+" {\n"
+" double t0[] =\n"
+" { 0.39894228040143268, -0.06649038006690545, 0.00997355701003582,\n"
+" -0.00118732821548045, 0.00011543468761616, -0.00000944465625950,\n"
+" 0.00000066596935163, -0.00000004122667415, 0.00000000227352982,\n"
+" 0.00000000011301172, 0.00000000000511243, -0.00000000000021218 };\n"
+" nVal = taylor(t0, 11, (xAbs * xAbs)) * xAbs;\n"
+" }\n"
+" else if ((xShort >= 1) && (xShort <= 2))\n"
+" {\n"
+" double t2[] =\n"
+" { 0.47724986805182079, 0.05399096651318805, -0.05399096651318805,\n"
+" 0.02699548325659403, -0.00449924720943234, -0.00224962360471617,\n"
+" 0.00134977416282970, -0.00011783742691370, -0.00011515930357476,\n"
+" 0.00003704737285544, 0.00000282690796889, -0.00000354513195524,\n"
+" 0.00000037669563126, 0.00000019202407921, -0.00000005226908590,\n"
+" -0.00000000491799345, 0.00000000366377919, -0.00000000015981997,\n"
+" -0.00000000017381238, 0.00000000002624031, 0.00000000000560919,\n"
+" -0.00000000000172127, -0.00000000000008634, 0.00000000000007894 };\n"
+" nVal = taylor(t2, 23, (xAbs - 2.0));\n"
+" }\n"
+" else if ((xShort >= 3) && (xShort <= 4))\n"
+" {\n"
+" double t4[] =\n"
+" { 0.49996832875816688, 0.00013383022576489, -0.00026766045152977,\n"
+" 0.00033457556441221, -0.00028996548915725, 0.00018178605666397,\n"
+" -0.00008252863922168, 0.00002551802519049, -0.00000391665839292,\n"
+" -0.00000074018205222, 0.00000064422023359, -0.00000017370155340,\n"
+" 0.00000000909595465, 0.00000000944943118, -0.00000000329957075,\n"
+" 0.00000000029492075, 0.00000000011874477, -0.00000000004420396,\n"
+" 0.00000000000361422, 0.00000000000143638, -0.00000000000045848 };\n"
+" nVal = taylor(t4, 20, (xAbs - 4.0));\n"
+" }\n"
+" else\n"
+" {\n"
+" double asympt[] = { -1.0, 1.0, -3.0, 15.0, -105.0 };\n"
+" nVal = 0.5 + phi(xAbs) * taylor(asympt, 4, 1.0/(xAbs * xAbs))/xAbs;\n"
+" }\n"
+" if (x < 0.0)\n"
+" return -nVal;\n"
+" else\n"
+" return nVal;\n"
+"}\n";
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/tool/addincfg.cxx b/sc/source/core/tool/addincfg.cxx
new file mode 100644
index 000000000..3f09b7def
--- /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 000000000..ef2ae2f44
--- /dev/null
+++ b/sc/source/core/tool/addincol.cxx
@@ -0,0 +1,1694 @@
+/* -*- 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 <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,
+ const OUString& rDesc,
+ sal_uInt16 nCat, const OString& sHelp,
+ const uno::Reference<reflection::XIdlMethod>& rFunc,
+ const uno::Any& rO,
+ tools::Long nAC, const ScAddInArgDesc* pAD,
+ tools::Long nCP ) :
+ aOriginalName( rNam ),
+ aLocalName( rLoc ),
+ aUpperName( rNam ),
+ aUpperLocal( rLoc ),
+ aDescription( rDesc ),
+ xFunction( rFunc ),
+ aObject( rO ),
+ nArgCount( nAC ),
+ nCallerPos( nCP ),
+ nCategory( nCat ),
+ sHelpId( 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 OUStringLiteral CFGSTR_DISPLAYNAME = u"DisplayName";
+constexpr OUStringLiteral CFGSTR_DESCRIPTION = u"Description";
+constexpr OUStringLiteral CFGSTR_CATEGORY = u"Category";
+// 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 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 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;
+ aCompNames.emplace_back( aLocale, aName);
+ // 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;
+ }
+ }
+ }
+
+ OString 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 ) );
+
+ OString 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 000000000..92c5fb4f7
--- /dev/null
+++ b/sc/source/core/tool/addinhelpid.cxx
@@ -0,0 +1,209 @@
+/* -*- 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>
+
+// A struct containing the built-in function name and the built-in help ID.
+struct ScUnoAddInHelpId
+{
+ const char* pFuncName;
+ const char* 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 );
+}
+
+OString ScUnoAddInHelpIdGenerator::GetHelpId( const OUString& rFuncName ) const
+{
+ if( !pCurrHelpIds || !nArrayCount )
+ return OString();
+
+ const ScUnoAddInHelpId* pFirst = pCurrHelpIds;
+ const ScUnoAddInHelpId* pLast = pCurrHelpIds + nArrayCount - 1;
+
+ while( pFirst <= pLast )
+ {
+ const ScUnoAddInHelpId* pMiddle = pFirst + (pLast - pFirst) / 2;
+ sal_Int32 nResult = rFuncName.compareToAscii( pMiddle->pFuncName );
+ if( !nResult )
+ return pMiddle->sHelpId;
+ else if( nResult < 0 )
+ pLast = pMiddle - 1;
+ else
+ pFirst = pMiddle + 1;
+ }
+
+ return OString();
+}
+
+/* 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 000000000..56d178eb6
--- /dev/null
+++ b/sc/source/core/tool/addinlis.cxx
@@ -0,0 +1,133 @@
+/* -*- 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 <vcl/svapp.hxx>
+
+#include <addinlis.hxx>
+#include <miscuno.hxx>
+#include <document.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> const & xVR, ScDocument* pDoc ) :
+ xVolRes( 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 000000000..a1e93472f
--- /dev/null
+++ b/sc/source/core/tool/address.cxx
@@ -0,0 +1,2532 @@
+/* -*- 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 <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;
+ OUString aName(aEnd.Format(nFlags, pDoc, rDetails));
+ r.append(":");
+ r.append(aName);
+ }
+ 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, const OUString& rStr)
+{
+ SCCOL nResult = 0;
+ sal_Int32 nStop = rStr.getLength();
+ 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 000000000..d9822fee3
--- /dev/null
+++ b/sc/source/core/tool/adiasync.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/.
+ *
+ * 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 <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 000000000..244f4ffc7
--- /dev/null
+++ b/sc/source/core/tool/appoptio.cxx
@@ -0,0 +1,654 @@
+/* -*- 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;
+}
+
+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;
+ 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
+
+// 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
+}
+
+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;
+
+ // if setting is "default", keep default values from ScUserList ctor
+ //TODO: mark "default" in a safe way
+ const bool bDefault = (aSeq.getLength() == 1 && aSeq[0] == "NULL");
+
+ if (!bDefault)
+ {
+ for (const OUString& rStr : std::as_const(aSeq))
+ {
+ ScUserListData* pNew = new ScUserListData(rStr);
+ aList.push_back(pNew);
+ }
+ }
+
+ 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));
+}
+
+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;
+ }
+ }
+ 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/arraysum.hxx b/sc/source/core/tool/arraysum.hxx
new file mode 100644
index 000000000..ce8a7f30f
--- /dev/null
+++ b/sc/source/core/tool/arraysum.hxx
@@ -0,0 +1,36 @@
+/* -*- 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/.
+ *
+ */
+
+#pragma once
+
+#include <math.h>
+
+namespace sc::op
+{
+/**
+ * Performs one step of the Neumanier sum between doubles
+ * Overwrites the summand and error
+ * @parma sum
+ * @param err
+ * @param value
+ */
+inline void sumNeumanierNormal(double& sum, double& err, const double& value)
+{
+ double t = sum + value;
+ if (fabs(sum) >= fabs(value))
+ err += (sum - t) + value;
+ else
+ err += (value - t) + sum;
+ sum = t;
+}
+
+} // end namespace sc::op
+
+/* 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 000000000..5d962baf9
--- /dev/null
+++ b/sc/source/core/tool/arraysumSSE2.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 "arraysum.hxx"
+
+#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
+ sumNeumanierNormal(sums[0], errs[0], sums[1]);
+ sumNeumanierNormal(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 000000000..55676816b
--- /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 OUStringLiteral sAutoTblFmtName = u"autotbl.fmt";
+
+// 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::IsEqualData( 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 000000000..7eb36d73a
--- /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 000000000..5a7fce852
--- /dev/null
+++ b/sc/source/core/tool/callform.cxx
@@ -0,0 +1,411 @@
+/* -*- 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 <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(const OUString& rStr, std::unique_ptr<osl::Module> pInst) : aName(rStr), pInstance(std::move(pInst)) {}
+
+ const OUString& GetName() const { return aName; }
+ osl::Module* GetInstance() const { return pInstance.get(); }
+};
+
+LegacyFuncData::LegacyFuncData(const ModuleData*pModule,
+ const OUString& rIName,
+ const OUString& rFName,
+ sal_uInt16 nNo,
+ sal_uInt16 nCount,
+ const ParamType* peType,
+ ParamType eType) :
+ pModuleData (pModule),
+ aInternalName (rIName),
+ aFuncName (rFName),
+ 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 000000000..0dc6f03de
--- /dev/null
+++ b/sc/source/core/tool/cellform.cxx
@@ -0,0 +1,214 @@
+/* -*- 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.meType)
+ {
+ case CELLTYPE_STRING:
+ {
+ OUString str;
+ rFormatter.GetOutputString(rCell.mpString->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.mfValue;
+ 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.mpFormula;
+ 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.meType)
+ {
+ case CELLTYPE_STRING:
+ case CELLTYPE_EDIT:
+ return rCell.getString(&rDoc);
+ case CELLTYPE_VALUE:
+ {
+ OUString str;
+ rFormatter.GetInputLineString(rCell.mfValue, nFormat, str, bFiltering, bForceSystemLocale);
+ return str;
+ }
+ break;
+ case CELLTYPE_FORMULA:
+ {
+ OUString str;
+ ScFormulaCell* pFC = rCell.mpFormula;
+ if (pFC->IsEmptyDisplayedAsString())
+ ; // empty
+ else if (pFC->IsValue())
+ 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.clear();
+ if( pShared != nullptr )
+ *pShared = nullptr;
+ }
+
+ return str;
+ }
+ case CELLTYPE_NONE:
+ if( pShared != nullptr )
+ *pShared = &svl::SharedString::getEmptyString();
+ return OUString();
+ default:
+ return OUString();
+ }
+}
+
+OUString ScCellFormat::GetOutputString( ScDocument& rDoc, const ScAddress& rPos, const ScRefCellValue& rCell )
+{
+ if (rCell.isEmpty())
+ return OUString();
+
+ if (rCell.meType == CELLTYPE_EDIT)
+ {
+ // GetString converts line breaks into spaces in EditCell,
+ // but here we need the line breaks
+ const EditTextObject* pData = rCell.mpEditText;
+ 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 000000000..1192e3c04
--- /dev/null
+++ b/sc/source/core/tool/cellkeytranslator.cxx
@@ -0,0 +1,232 @@
+/* -*- 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);
+}
+
+ScCellKeywordTranslator::ScCellKeywordTranslator() :
+ maTransWrapper( ::comphelper::getProcessComponentContext(),
+ TransliterationFlags::LOWERCASE_UPPERCASE )
+{
+ init();
+}
+
+ScCellKeywordTranslator::~ScCellKeywordTranslator()
+{
+}
+
+struct TransItem
+{
+ const sal_Unicode* from;
+ const char* to;
+ OpCode func;
+};
+
+void ScCellKeywordTranslator::init()
+{
+ ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex() );
+
+ // 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"
+}
+
+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 000000000..b56e3eead
--- /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 000000000..13e5df250
--- /dev/null
+++ b/sc/source/core/tool/chartarr.cxx
@@ -0,0 +1,377 @@
+/* -*- 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.meType)
+ {
+ 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.mpFormula;
+ 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())
+ {
+ OUStringBuffer aBuf;
+ aBuf.append(ScResId(STR_ROW));
+ aBuf.append(' ');
+ aBuf.append(static_cast<sal_Int32>(aRows[nRow]+1));
+ aString = aBuf.makeStringAndClear();
+ }
+ 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 000000000..a59a61aad
--- /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 <tools/diagnose_ex.h>
+
+#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() )
+ {
+ 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();
+ 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;
+ 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 000000000..2fe7f97ee
--- /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 <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( const uno::Reference< chart::XChartDataChangeEventListener >& rL,
+ const uno::Reference< chart::XChartData >& rS ) :
+ xListener( rL ), xSource( rS ) {}
+
+ 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( const OUString& rName, ScDocument& rDocP,
+ const ScRangeListRef& rRangeList ) :
+ maName(rName),
+ mrDoc( rDocP ),
+ bUsed( false ),
+ bDirty( false )
+{
+ ScRefTokenHelper::getTokensFromRangeList(&rDocP, maTokens, *rRangeList);
+}
+
+ScChartListener::ScChartListener( const OUString& rName, ScDocument& rDocP, vector<ScTokenRef> aTokens ) :
+ maTokens(std::move(aTokens)),
+ maName(rName),
+ 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 )
+{
+ const ScHint* p = dynamic_cast<const ScHint*>(&rHint);
+ if (p && (p->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 000000000..daeecbbc2
--- /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 <tools/diagnose_ex.h>
+
+#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 000000000..4c93be995
--- /dev/null
+++ b/sc/source/core/tool/chartpos.cxx
@@ -0,0 +1,523 @@
+/* -*- 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>
+
+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, const ScRangeListRef& rRangeList ) :
+ aRangeListRef( rRangeList ),
+ 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 000000000..55530e494
--- /dev/null
+++ b/sc/source/core/tool/chgtrack.cxx
@@ -0,0 +1,4671 @@
+/* -*- 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 <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>
+
+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, const ScBigRange& rRange,
+ const sal_uLong nTempAction, const sal_uLong nTempRejectAction,
+ const ScChangeActionState eTempState, const DateTime& aTempDateTime,
+ const OUString& aTempUser, const OUString& aTempComment) :
+ aBigRange( rRange ),
+ aDateTime( aTempDateTime ),
+ aUser( aTempUser ),
+ aComment( 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, const ScBigRange& rRange,
+ const sal_uLong nTempAction)
+ :
+ aBigRange( rRange ),
+ 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('.');
+ }
+ aBuf.append(ScColToAlpha(aTmpRange.aStart.Col()));
+ aBuf.append(':');
+ aBuf.append(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('.');
+ }
+ aBuf.append(static_cast<sal_Int64>(aTmpRange.aStart.Row()+1));
+ aBuf.append(':');
+ aBuf.append(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, const 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(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,
+ const ScCellValue& rOldCell, const ScDocument* pDoc, const OUString& sOldValue ) :
+ ScChangeAction(SC_CAT_CONTENT, aBigRangeP, nActionNumber, nRejectingNumber, eStateP, aDateTimeP, aUserP, sComment),
+ maOldCell(rOldCell),
+ 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,
+ const ScCellValue& rNewCell, const ScBigRange& aBigRangeP,
+ const ScDocument* pDoc, const OUString& sNewValue ) :
+ ScChangeAction(SC_CAT_CONTENT, aBigRangeP, nActionNumber),
+ maNewCell(rNewCell),
+ 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.meType = CELLTYPE_FORMULA;
+ rCell.mpFormula = new ScFormulaCell(
+ *pDoc, aBigRange.aStart.MakeAddress(*pDoc), rStr,
+ pDoc->GetGrammar() );
+ rCell.mpFormula->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.mpFormula->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.meType)
+ {
+ case CELLTYPE_VALUE:
+ {
+ OUString str;
+ pDoc->GetFormatTable()->GetInputLineString(rCell.mfValue, nFormat, str);
+ return str;
+ }
+ case CELLTYPE_STRING:
+ return rCell.mpString->getString();
+ case CELLTYPE_EDIT:
+ if (rCell.mpEditText)
+ return ScEditUtil::GetString(*rCell.mpEditText, pDoc);
+ return OUString();
+ case CELLTYPE_FORMULA:
+ return rCell.mpFormula->GetFormula();
+ default:
+ return OUString();
+ }
+}
+
+ScChangeActionContentCellType ScChangeActionContent::GetContentCellType( const ScCellValue& rCell )
+{
+ switch (rCell.meType)
+ {
+ case CELLTYPE_VALUE :
+ case CELLTYPE_STRING :
+ case CELLTYPE_EDIT :
+ return SC_CACCT_NORMAL;
+ case CELLTYPE_FORMULA :
+ switch (rCell.mpFormula->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.meType)
+ {
+ case CELLTYPE_VALUE:
+ case CELLTYPE_STRING:
+ case CELLTYPE_EDIT:
+ return SC_CACCT_NORMAL;
+ case CELLTYPE_FORMULA:
+ {
+ const ScFormulaCell* pCell = rCell.mpFormula;
+ 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.meType == 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.meType)
+ {
+ case CELLTYPE_VALUE :
+ { // E.g.: Remember date as such
+ pFromDoc->GetFormatTable()->GetInputLineString(
+ rOrgCell.mfValue, nFormat, rStr);
+ }
+ break;
+ case CELLTYPE_FORMULA :
+ rCell.mpFormula->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.meType)
+ {
+ case CELLTYPE_VALUE :
+ // e.g. remember date as date string
+ pDoc->GetFormatTable()->GetInputLineString(rCell.mfValue, nFormat, rStr);
+ break;
+ case CELLTYPE_FORMULA :
+ rCell.mpFormula->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.meType)
+ {
+ case CELLTYPE_STRING :
+ return rCell.mpString->getString();
+ case CELLTYPE_EDIT :
+ if (rCell.mpEditText)
+ return ScEditUtil::GetString(*rCell.mpEditText, pDoc);
+ return OUString();
+ case CELLTYPE_VALUE : // Is always in rValue
+ return rValue;
+ case CELLTYPE_FORMULA :
+ return GetFormulaString(rCell.mpFormula);
+ 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.meType == CELLTYPE_VALUE)
+ {
+ pDoc->SetString( aPos.Col(), aPos.Row(), aPos.Tab(), rValue );
+ return;
+ }
+
+ switch (GetContentCellType(rCell))
+ {
+ case SC_CACCT_MATORG :
+ {
+ SCCOL nC;
+ SCROW nR;
+ rCell.mpFormula->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.mpFormula->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.meType == CELLTYPE_FORMULA;
+ bool bNewFormula = maNewCell.meType == 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.mpFormula->aPos = aBigRange.aStart.MakeAddress(pTrack->GetDocument());
+ if ( bNewFormula )
+ maNewCell.mpFormula->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.mpFormula->UpdateReference(aRefCxt);
+ if ( bNewFormula )
+ maNewCell.mpFormula->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.mpFormula->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.mpFormula->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;
+
+ const SvtUserOptions& rUserOpt = SC_MOD()->GetUserOptions();
+ maUser = rUserOpt.GetFirstName() + " " + rUserOpt.GetLastName();
+ maUserCollection.insert(maUser);
+}
+
+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;
+
+ const SvtUserOptions& rUserOptions = SC_MOD()->GetUserOptions();
+ size_t nOldCount = maUserCollection.size();
+
+ SetUser(rUserOptions.GetFirstName() + " " + rUserOptions.GetLastName());
+
+ 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)
+
+ SfxObjectShell* pDocSh = rDoc.GetDocumentShell();
+ if (pDocSh)
+ pDocSh->Broadcast( ScPaintHint( ScRange(0,0,0,rDoc.MaxCol(),rDoc.MaxRow(),MAXTAB), PaintPartFlags::Grid ) );
+ }
+}
+
+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.meType == CELLTYPE_FORMULA && rOldCell.mpFormula->GetMatrixFlag() == ScMatrixMode::Formula)
+ rOldCell.mpFormula->GetMatColsRows(nC1, nR1);
+
+ if (rNewCell.meType == CELLTYPE_FORMULA && rNewCell.mpFormula->GetMatrixFlag() == ScMatrixMode::Formula)
+ rNewCell.mpFormula->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.mpFormula->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.mpFormula->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(),
+ 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 000000000..0a394cc86
--- /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 000000000..58ed51f65
--- /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 000000000..cdb7dedff
--- /dev/null
+++ b/sc/source/core/tool/compiler.cxx
@@ -0,0 +1,6568 @@
+/* -*- 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 <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 <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;
+
+osl::Mutex ScCompiler::maMutex;
+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
+}
+
+const CharClass* ScCompiler::GetCharClassEnglish()
+{
+ if (!pCharClassEnglish)
+ {
+ osl::MutexGuard aGuard(maMutex);
+ 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.
+ osl::MutexGuard aGuard(maMutex);
+ 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[39] |= 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( const OUString& rFormula, sal_Int32 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.getLength() && rFormula[nSrcPos] == '\'')
+ {
+ sal_Int32 nPos = nSrcPos+1;
+ while (nPos < rFormula.getLength())
+ {
+ if (rFormula[nPos] == '\'')
+ {
+ if ( (nPos+1 == rFormula.getLength()) || (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 OUStringLiteral aAddAllowed(u"?#");
+ 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 OUStringLiteral aAddAllowed(u"?!");
+ 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('[').append( static_cast<sal_Int32>(nFileId+1) ).append(']');
+ }
+};
+
+}
+
+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 OUStringLiteral aAddAllowed(u"?-[]!");
+
+ 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 mistakingly 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 )
+{
+ bool bFound = false;
+ sal_uInt16 i;
+
+ for( i = ocInternalBegin; i <= ocInternalEnd && !bFound; i++ )
+ bFound = o3tl::equalsAscii( rName, pInternal[ i-ocInternalBegin ] );
+
+ if (bFound)
+ {
+ maRawToken.SetOpCode( static_cast<OpCode>(--i) );
+ }
+ return bFound;
+}
+
+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
+ const OUString aErrRef("#REF!"); // 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;
+ SfxObjectShell* 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 = dynamic_cast<ScTableRefToken*>(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 = dynamic_cast<ScTableRefToken*>(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 ))
+ {
+ // ODFF has a defined set 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)
+ {
+ FormulaToken* 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( aStr );
+ rBuffer.append(aStr);
+ }
+ 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]");
+ aBuffer.append(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 );
+}
+
+// Put quotes around string if non-alphanumeric characters are contained,
+// quote characters contained within are escaped by '\\'.
+bool ScCompiler::EnQuote( OUString& rStr )
+{
+ sal_Int32 nPos = 0;
+ while ( (nPos = rStr.indexOf( '\'', nPos)) != -1 )
+ {
+ rStr = rStr.replaceAt( nPos, 0, u"'" );
+ nPos += 2;
+ }
+ rStr = "'" + rStr + "'";
+ return true;
+}
+
+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 == 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 ))
+ {
+ 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)
+ return;
+
+ 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;
+ }
+
+ return;
+}
+
+/* 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 000000000..aa8dbcc3e
--- /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 000000000..0e36eb960
--- /dev/null
+++ b/sc/source/core/tool/dbdata.cxx
@@ -0,0 +1,1637 @@
+/* -*- 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>
+
+using ::std::unique_ptr;
+using ::std::for_each;
+using ::std::find_if;
+using ::std::remove_if;
+using ::std::pair;
+
+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),
+ 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),
+ 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;
+ 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
+{
+ OUStringBuffer aBuf;
+ if (mpImportParam->bImport)
+ {
+ aBuf.append(mpImportParam->aDBName);
+ aBuf.append('/');
+ aBuf.append(mpImportParam->aStatement);
+ }
+ return aBuf.makeStringAndClear();
+}
+
+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;
+ 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.
+ AdjustTableColumnNames( 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;
+ 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::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::AdjustTableColumnNames( 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;
+ 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);
+ // Copy head.
+ for (size_t i = 0; i < nHead; ++i)
+ {
+ aNewNames[i] = maTableColumnNames[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];
+ }
+ }
+ } // else empty aNewNames invalidates names/offsets
+
+ SAL_WARN_IF( !maTableColumnNames.empty() && aNewNames.empty(),
+ "sc.core", "ScDBData::AdjustTableColumnNames - invalidating column names/offsets");
+ aNewNames.swap( maTableColumnNames);
+ 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( const OUString& rSearchName ) :
+ maSearchName( rSearchName )
+ {
+ }
+
+ 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);
+ 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 )
+{
+ const ScHint* pScHint = dynamic_cast<const ScHint*>(&rHint);
+ if (!pScHint)
+ return;
+
+ if (pScHint->GetId() != SfxHintId::ScDataChanged)
+ return;
+
+ 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++);
+
+ 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);
+ m_DBs.erase(std::remove_if(m_DBs.begin(), m_DBs.end(), func), m_DBs.end());
+}
+
+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 000000000..eced7cd86
--- /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>
+
+
+bool ScDdeLink::bIsInUpdate = false;
+
+ScDdeLink::ScDdeLink( ScDocument& rD, const OUString& rA, const OUString& rT, const OUString& rI,
+ sal_uInt8 nM ) :
+ ::sfx2::SvBaseLink(SfxLinkUpdateMode::ALWAYS,SotClipboardFormatId::STRING),
+ rDoc( rD ),
+ aAppl( rA ),
+ aTopic( rT ),
+ aItem( rI ),
+ 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;
+ if (!(rValue >>= aLinkStr))
+ ScByteSequenceToString::GetString( aLinkStr, rValue, osl_getThreadTextEncoding() );
+ 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 000000000..3f18da093
--- /dev/null
+++ b/sc/source/core/tool/defaultsoptions.cxx
@@ -0,0 +1,151 @@
+/* -*- 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>
+
+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( const ScDefaultsOptions& rOpt ) :
+ SfxPoolItem ( SID_SCDEFAULTSOPTIONS ),
+ theOptions ( rOpt )
+{
+}
+
+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 000000000..6faa3cb37
--- /dev/null
+++ b/sc/source/core/tool/detdata.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 <algorithm>
+#include <detdata.hxx>
+#include <refupdat.hxx>
+
+ScDetOpList::ScDetOpList(const ScDetOpList& rList) :
+ bHasAddError( false ),
+ aDetOpDataVector( rList.aDetOpDataVector )
+{
+}
+
+void ScDetOpList::DeleteOnTab( SCTAB nTab )
+{
+ aDetOpDataVector.erase(std::remove_if(aDetOpDataVector.begin(), aDetOpDataVector.end(),
+ [&nTab](const ScDetOpData& rxDetOpData) {
+ return rxDetOpData.GetPos().Tab() == nTab; // look for operations on the deleted sheet
+ }),
+ aDetOpDataVector.end());
+}
+
+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 000000000..9e700f411
--- /dev/null
+++ b/sc/source/core/tool/detfunc.cxx
@@ -0,0 +1,1755 @@
+/* -*- 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 <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; }
+};
+
+namespace {
+
+class ScCommentData
+{
+public:
+ ScCommentData( ScDocument& rDoc, SdrModel* pModel );
+
+ SfxItemSet& GetCaptionSet() { return aCaptionSet; }
+ void UpdateCaptionSet( const SfxItemSet& rItemSet );
+
+private:
+ SfxItemSet aCaptionSet;
+};
+
+}
+
+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
+}
+
+ScCommentData::ScCommentData( ScDocument& rDoc, SdrModel* pModel ) :
+ aCaptionSet( pModel->GetItemPool(), svl::Items<SDRATTR_START, SDRATTR_END, EE_ITEMS_START, EE_ITEMS_END> )
+{
+ 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);
+
+ aCaptionSet.Put( XLineStartItem( OUString(), basegfx::B2DPolyPolygon(aTriangle)));
+ aCaptionSet.Put( XLineStartWidthItem( 200 ) );
+ aCaptionSet.Put( XLineStartCenterItem( false ) );
+ aCaptionSet.Put( XFillStyleItem( drawing::FillStyle_SOLID ) );
+ Color aYellow( ScDetectiveFunc::GetCommentColor() );
+ aCaptionSet.Put( XFillColorItem( OUString(), aYellow ) );
+
+ // shadow
+ // SdrShadowItem has sal_False, instead the shadow is set for the rectangle
+ // only with SetSpecialTextBoxShadow when the object is created
+ // (item must be set to adjust objects from older files)
+ aCaptionSet.Put( makeSdrShadowItem( false ) );
+ aCaptionSet.Put( makeSdrShadowXDistItem( 100 ) );
+ aCaptionSet.Put( makeSdrShadowYDistItem( 100 ) );
+
+ // text attributes
+ aCaptionSet.Put( makeSdrTextLeftDistItem( 100 ) );
+ aCaptionSet.Put( makeSdrTextRightDistItem( 100 ) );
+ aCaptionSet.Put( makeSdrTextUpperDistItem( 100 ) );
+ aCaptionSet.Put( makeSdrTextLowerDistItem( 100 ) );
+
+ aCaptionSet.Put( makeSdrTextAutoGrowWidthItem( false ) );
+ aCaptionSet.Put( makeSdrTextAutoGrowHeightItem( true ) );
+
+ // do use the default cell style, so the user has a chance to
+ // modify the font for the annotations
+ rDoc.GetPool()->GetDefaultItem(ATTR_PATTERN).FillEditItemSet( &aCaptionSet );
+
+ // support the best position for the tail connector now that
+ // that notes can be resized and repositioned.
+ aCaptionSet.Put( SdrCaptionEscDirItem( SdrCaptionEscDir::BestFit) );
+}
+
+void ScCommentData::UpdateCaptionSet( const SfxItemSet& rItemSet )
+{
+ SfxWhichIter aWhichIter( rItemSet );
+ const SfxPoolItem* pPoolItem = nullptr;
+
+ for( sal_uInt16 nWhich = aWhichIter.FirstWhich(); nWhich > 0; nWhich = aWhichIter.NextWhich() )
+ {
+ if(aWhichIter.GetItemState(false, &pPoolItem) == SfxItemState::SET)
+ {
+ switch(nWhich)
+ {
+ case SDRATTR_SHADOW:
+ // use existing Caption default - appears that setting this
+ // to true screws up the tail appearance. See also comment
+ // for default setting above.
+ break;
+ case SDRATTR_SHADOWXDIST:
+ // use existing Caption default - svx sets a value of 35
+ // but default 100 gives a better appearance.
+ break;
+ case SDRATTR_SHADOWYDIST:
+ // use existing Caption default - svx sets a value of 35
+ // but default 100 gives a better appearance.
+ break;
+
+ default:
+ aCaptionSet.Put(*pPoolItem);
+ }
+ }
+ }
+}
+
+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.Justify(); // 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 );
+ SdrRectObj* pBox = new SdrRectObj(
+ *pModel,
+ aRect);
+
+ pBox->SetMergedItemSetAndBroadcast(rData.GetBoxSet());
+
+ pBox->SetLayer( SC_LAYER_INTERN );
+ pPage->InsertObject( pBox );
+ pModel->AddCalcUndo( std::make_unique<SdrUndoInsertObj>( *pBox ) );
+
+ ScDrawObjData* pData = ScDrawLayer::GetObjData( pBox, 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()));
+ SdrPathObj* pArrow = new SdrPathObj(
+ *pModel,
+ SdrObjKind::Line,
+ basegfx::B2DPolyPolygon(aTempPoly));
+ pArrow->NbcSetLogicRect(tools::Rectangle::Justify(aStartPos,aEndPos)); //TODO: needed ???
+ pArrow->SetMergedItemSetAndBroadcast(rAttrSet);
+
+ pArrow->SetLayer( SC_LAYER_INTERN );
+ pPage->InsertObject( pArrow );
+ pModel->AddCalcUndo( std::make_unique<SdrUndoInsertObj>( *pArrow ) );
+
+ ScDrawObjData* pData = ScDrawLayer::GetObjData(pArrow, 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 );
+ SdrRectObj* pBox = new SdrRectObj(
+ *pModel,
+ aRect);
+
+ pBox->SetMergedItemSetAndBroadcast(rData.GetBoxSet());
+
+ pBox->SetLayer( SC_LAYER_INTERN );
+ pPage->InsertObject( pBox );
+ pModel->AddCalcUndo( std::make_unique<SdrUndoInsertObj>( *pBox ) );
+
+ ScDrawObjData* pData = ScDrawLayer::GetObjData( pBox, 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()));
+ SdrPathObj* pArrow = new SdrPathObj(
+ *pModel,
+ SdrObjKind::Line,
+ basegfx::B2DPolyPolygon(aTempPoly));
+ pArrow->NbcSetLogicRect(tools::Rectangle::Justify(aStartPos,aEndPos)); //TODO: needed ???
+
+ pArrow->SetMergedItemSetAndBroadcast(rAttrSet);
+
+ pArrow->SetLayer( SC_LAYER_INTERN );
+ pPage->InsertObject( pArrow );
+ pModel->AddCalcUndo( std::make_unique<SdrUndoInsertObj>( *pArrow ) );
+
+ ScDrawObjData* pData = ScDrawLayer::GetObjData( pArrow, 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 );
+
+ SdrCircObj* pCircle = new SdrCircObj(
+ *pModel,
+ SdrCircKind::Full,
+ aRect);
+ SfxItemSet& rAttrSet = rData.GetCircleSet();
+
+ pCircle->SetMergedItemSetAndBroadcast(rAttrSet);
+
+ pCircle->SetLayer( SC_LAYER_INTERN );
+ pPage->InsertObject( pCircle );
+ pModel->AddCalcUndo( std::make_unique<SdrUndoInsertObj>( *pCircle ) );
+
+ ScDrawObjData* pData = ScDrawLayer::GetObjData( pCircle, 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
+ SdrObject* pObj = pPage->RemoveObject(ppObj[nDelCount-i]->GetOrdNum());
+ if( !bRecording )
+ SdrObject::Free( pObj );
+ }
+
+ 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.Justify();
+ 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.meType != CELLTYPE_FORMULA)
+ return DET_INS_EMPTY;
+
+ ScFormulaCell* pFCell = aCell.mpFormula;
+ 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.meType != CELLTYPE_FORMULA)
+ return nLevel;
+
+ ScFormulaCell* pFCell = aCell.mpFormula;
+ 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.meType != CELLTYPE_FORMULA)
+ return DET_INS_EMPTY;
+
+ ScFormulaCell* pFCell = aCell.mpFormula;
+ 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_uLong 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 )
+{
+ // for all caption objects, update attributes and SpecialTextBoxShadow flag
+ // (on all tables - nTab is ignored!)
+
+ // no undo actions, this is refreshed after undo
+
+ 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 ( ScDrawObjData* pData = ScDrawLayer::GetNoteCaptionData( pObject, nObjTab ) )
+ {
+ ScPostIt* pNote = rDoc.GetNote( pData->maStart );
+ // caption should exist, we iterate over drawing objects...
+ OSL_ENSURE( pNote && (pNote->GetCaption() == pObject), "ScDetectiveFunc::UpdateAllComments - invalid cell note" );
+ if( pNote )
+ {
+ ScCommentData aData( rDoc, pModel );
+ SfxItemSet aAttrColorSet = pObject->GetMergedItemSet();
+ aAttrColorSet.Put( XFillColorItem( OUString(), GetCommentColor() ) );
+ aData.UpdateCaptionSet( aAttrColorSet );
+ pObject->SetMergedItemSetAndBroadcast( aData.GetCaptionSet() );
+ if( SdrCaptionObj* pCaption = dynamic_cast< SdrCaptionObj* >( pObject ) )
+ {
+ pCaption->SetSpecialTextBoxShadow();
+ pCaption->SetFixedTail();
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+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 000000000..1f88b07c9
--- /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 000000000..4ef076003
--- /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, const ScMatrixRef& pMat) :
+ ScDBRangeBase(pDoc), mpMatrix(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 000000000..00a2b6c71
--- /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 000000000..c7e0c0cb6
--- /dev/null
+++ b/sc/source/core/tool/editutil.cxx
@@ -0,0 +1,935 @@
+/* -*- 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 <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 )
+{
+ 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;
+ }
+ 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)
+ {
+ SfxObjectShell* 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->GetFont( aFont, SC_AUTOCOL_BLACK, 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 */ )
+{
+ 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 */ )
+{
+ const SvxFieldData* pFieldData = rField.GetField();
+
+ if (!pFieldData)
+ return " ";
+
+ return ScEditUtil::GetCellFieldValue(*pFieldData, mpDoc, &rTxtColor);
+}
+
+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 000000000..13c856150
--- /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 000000000..c7ac689f2
--- /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(const OUString& rDeviceId, bool bAutoSelect, bool bForceEvaluation)
+{
+ bool bOpenCLEnabled = ScCalcConfig::isOpenCLEnabled();
+ if (!bOpenCLEnabled || (rDeviceId == 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 000000000..46465b561
--- /dev/null
+++ b/sc/source/core/tool/formulalogger.cxx
@@ -0,0 +1,357 @@
+/* -*- 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 <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>
+
+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, const OUString& rPrefix, const ScDocument& rDoc,
+ const ScFormulaCell& rCell, bool bOutputEnabled ) :
+ mrLogger(rLogger), mrDoc(rDoc), maPrefix(rPrefix),
+ 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 )
+{
+ OUStringBuffer aBuf;
+
+ ScRange aRefRange(rRefPos);
+ aRefRange.aEnd.IncRow(nLen-1);
+ OUString aRangeStr = aRefRange.Format(mpImpl->mrDoc, getRefFlags(rCellPos, rRefPos));
+ aBuf.append(aRangeStr);
+ aBuf.append(": ");
+
+ if (rArray.mpNumericArray)
+ {
+ if (rArray.mpStringArray)
+ {
+ // mixture of numeric and string cells.
+ aBuf.append("numeric and string");
+ }
+ else
+ {
+ // numeric cells only.
+ aBuf.append("numeric only");
+ }
+ }
+ else
+ {
+ if (rArray.mpStringArray)
+ {
+ // string cells only.
+ aBuf.append("string only");
+ }
+ else
+ {
+ // empty cells.
+ aBuf.append("empty");
+ }
+ }
+
+ mpImpl->maMessages.push_back(aBuf.makeStringAndClear());
+}
+
+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 )
+{
+ OUStringBuffer aBuf;
+ OUString aPosStr = rRefPos.Format(getRefFlags(rCellPos, rRefPos), &mpImpl->mrDoc);
+ aBuf.append(aPosStr);
+ aBuf.append(": ");
+
+ switch (rToken.GetType())
+ {
+ case formula::svDouble:
+ aBuf.append("numeric value");
+ break;
+ case formula::svString:
+ aBuf.append("string value");
+ break;
+ default:
+ aBuf.append("unknown value");
+ }
+
+ mpImpl->maMessages.push_back(aBuf.makeStringAndClear());
+}
+
+void FormulaLogger::GroupScope::addGroupSizeThresholdMessage( const ScFormulaCell& rCell )
+{
+ OUStringBuffer aBuf;
+ aBuf.append("group length below minimum threshold (");
+ aBuf.append(rCell.GetWeight());
+ aBuf.append(" < ");
+ aBuf.append(ScInterpreter::GetGlobalConfig().mnOpenCLMinimumFormulaGroupSize);
+ aBuf.append(")");
+ mpImpl->maMessages.push_back(aBuf.makeStringAndClear());
+}
+
+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).getStr();
+ 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 SfxObjectShell* 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 000000000..6c9e790aa
--- /dev/null
+++ b/sc/source/core/tool/formulaopt.cxx
@@ -0,0 +1,652 @@
+/* -*- 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>
+
+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( const ScFormulaOptions& rOpt ) :
+ SfxPoolItem ( SID_SCFORMULAOPTIONS ),
+ theOptions ( rOpt )
+{
+}
+
+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 000000000..00c5c9108
--- /dev/null
+++ b/sc/source/core/tool/formulaparserpool.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 <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>
+
+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( SfxObjectShell* pDocShell = mrDoc.GetDocumentShell() ) try
+ {
+ static ScParserFactoryMap theScParserFactoryMap;
+
+ Reference< XComponent > xComponent( pDocShell->GetModel(), UNO_QUERY_THROW );
+ 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 000000000..c14f5a9a7
--- /dev/null
+++ b/sc/source/core/tool/formularesult.cxx
@@ -0,0 +1,640 @@
+/* -*- 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>
+
+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( const svl::SharedString& rStr ) : mfValue(0.0), maString(rStr), 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());
+
+ // 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 000000000..39b92625b
--- /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 goup 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 000000000..e2f47824e
--- /dev/null
+++ b/sc/source/core/tool/hints.cxx
@@ -0,0 +1,113 @@
+/* -*- 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>
+
+// 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, const OUString& rSt1,
+ sal_uLong nT, const OUString& rSt2 ) :
+ aRange( rR ),
+ aStyle1( rSt1 ),
+ aStyle2( rSt2 ),
+ nTimeout( nT )
+{
+}
+
+ScAutoStyleHint::~ScAutoStyleHint()
+{
+}
+
+ScDBRangeRefreshedHint::ScDBRangeRefreshedHint( const ScImportParam& rP )
+ : aParam(rP)
+{
+}
+ScDBRangeRefreshedHint::~ScDBRangeRefreshedHint()
+{
+}
+
+ScDataPilotModifiedHint::ScDataPilotModifiedHint( const OUString& rName )
+ : maName(rName)
+{
+}
+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 000000000..7fde948f5
--- /dev/null
+++ b/sc/source/core/tool/inputopt.cxx
@@ -0,0 +1,162 @@
+/* -*- 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_TEXTWYSIWYG 9
+#define SCINPUTOPT_REPLCELLSWARN 10
+#define SCINPUTOPT_LEGACY_CELL_SELECTION 11
+#define SCINPUTOPT_ENTER_PASTE_MODE 12
+
+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
+ "UsePrinterMetrics", // SCINPUTOPT_TEXTWYSIWYG
+ "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_TEXTWYSIWYG] >>= bVal)
+ SetTextWysiwyg(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_TEXTWYSIWYG] <<= GetTextWysiwyg();
+ 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 000000000..94964b1a0
--- /dev/null
+++ b/sc/source/core/tool/interpr1.cxx
@@ -0,0 +1,10198 @@
+/* -*- 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 <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, mrDoc, 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, mrDoc, 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, mrDoc, 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.meType == 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.meType)
+ {
+ case CELLTYPE_STRING :
+ case CELLTYPE_EDIT :
+ bRes = true;
+ break;
+ case CELLTYPE_FORMULA :
+ bRes = (!aCell.mpFormula->IsValue() && !aCell.mpFormula->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.meType)
+ {
+ // 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" )
+ { // file name and table name: 'FILENAME'#$TABLE
+ SCTAB nTab = aCellPos.Tab();
+ OUString aFuncResult;
+ if( nTab < mrDoc.GetTableCount() )
+ {
+ if( mrDoc.GetLinkMode( nTab ) == ScLinkMode::VALUE )
+ mrDoc.GetName( nTab, aFuncResult );
+ else
+ {
+ SfxObjectShell* pShell = mrDoc.GetDocumentShell();
+ if( pShell && pShell->GetMedium() )
+ {
+ const INetURLObject& rURLObj = pShell->GetMedium()->GetURLObject();
+ OUString aTabName;
+ mrDoc.GetName( nTab, aTabName );
+ 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()->GetFont( aDefFont, SC_AUTOCOL_BLACK, 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" )
+ {
+ // 'file URI'#$SheetName
+
+ const OUString* p = pRefMgr->getExternalFileName(nFileId);
+ if (!p)
+ {
+ // In theory this should never happen...
+ SetError(FormulaError::NoName);
+ return;
+ }
+
+ OUString 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.meType)
+ {
+ case CELLTYPE_VALUE :
+ bRes = true;
+ break;
+ case CELLTYPE_FORMULA :
+ bRes = (aCell.mpFormula->IsValue() && !aCell.mpFormula->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.meType == 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.meType)
+ {
+ case CELLTYPE_FORMULA :
+ aFormula = aCell.mpFormula->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.meType)
+ {
+ case CELLTYPE_FORMULA :
+ aFormula = aCell.mpFormula->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.meType)
+ {
+ case CELLTYPE_VALUE :
+ fVal = GetCellValue(aAdr, aCell);
+ bRes = true;
+ break;
+ case CELLTYPE_FORMULA :
+ if (aCell.mpFormula->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.meType)
+ {
+ case CELLTYPE_VALUE :
+ bValue = true;
+ break;
+ case CELLTYPE_FORMULA :
+ bValue = aCell.mpFormula->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, mrDoc, aRange, mnSubTotalFlags, bTextAsZero );
+ if (aValIter.GetFirst(nVal, nErr))
+ {
+ if (nMin > nVal)
+ nMin = nVal;
+ aValIter.GetCurNumFmtInfo( mrContext, 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, mrDoc, aRange, mnSubTotalFlags, bTextAsZero );
+ if (aValIter.GetFirst(nVal, nErr))
+ {
+ if (nMax < nVal)
+ nMax = nVal;
+ aValIter.GetCurNumFmtInfo( mrContext, 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, mrDoc, 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, mrDoc, 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);
+ if (nCols == 0)
+ {
+ // Happens if called via ScViewFunc::EnterMatrix()
+ // ScFormulaCell::GetResultDimensions() as of course a
+ // matrix result is not available yet.
+ nCols = 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);
+ if (nRows == 0)
+ {
+ // Happens if called via ScViewFunc::EnterMatrix()
+ // ScFormulaCell::GetResultDimensions() as of course a
+ // matrix result is not available yet.
+ 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
+ {
+ PushDouble( nHitIndex); // non-exact match
+ return;
+ }
+
+ PushNA();
+ return;
+ }
+
+ 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.meType)
+ {
+ 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.mpFormula->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.meType)
+ {
+ case CELLTYPE_VALUE :
+ fVal = GetCellValue(aAdr, aCell);
+ bIsString = false;
+ break;
+ case CELLTYPE_FORMULA :
+ if (aCell.mpFormula->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.meType)
+ {
+ case CELLTYPE_VALUE :
+ fVal = GetCellValue(aAdr, aCell);
+ bIsString = false;
+ break;
+ case CELLTYPE_FORMULA :
+ if (aCell.mpFormula->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.meType)
+ {
+ case CELLTYPE_VALUE :
+ fVal = GetCellValue(aAdr, aCell);
+ bIsString = false;
+ break;
+ case CELLTYPE_FORMULA :
+ if (aCell.mpFormula->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 );
+ auto it = vConditions.begin() + oldCol * nDimensionRows;
+ newConditions.insert( newConditions.end(), it, it + nDimensionRows );
+ 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];
+
+ 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;
+ if ( bHLookup )
+ {
+ nX = nDelta;
+ nY = static_cast<SCSIZE>(nZIndex);
+ }
+ assert( nX < nC && nY < nR );
+ if ( pMat->IsStringOrEmpty( nX, nY) )
+ PushString(pMat->GetString( nX,nY).getString());
+ else
+ PushDouble(pMat->GetDouble( nX,nY));
+ }
+ 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);
+}
+
+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;
+ }
+
+ OUString sRefStr = GetString().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();
+
+ // 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))
+ 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
+ {
+ OUString aName( ScGlobal::getCharClass().uppercase( sRefStr));
+ 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 ( 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 ( 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 = ScGlobal::FindUnquoted( sRefStr, '[');
+ const bool bTableRef = (nIndex > 0 && ScGlobal::FindUnquoted( sRefStr, ']', nIndex+1) > 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( 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;
+ if (nParamCount >= 3)
+ nCol = static_cast<SCCOL>(GetInt16());
+ else
+ 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?
+ bool bVector = ((nCol == 0 || nRow == 0) && (nC == 1 || nR == 1));
+ SCSIZE nElement = ::std::max( static_cast<SCSIZE>(nCol),
+ static_cast<SCSIZE>(nRow));
+ if (nC == 0 || nR == 0 ||
+ (!bVector && (o3tl::make_unsigned(nCol) > nC ||
+ o3tl::make_unsigned(nRow) > nR)) ||
+ (bVector && nElement > nC * nR))
+ PushIllegalArgument();
+ else if (nCol == 0 && nRow == 0)
+ sp = nOldSp;
+ else if (bVector)
+ {
+ --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
+ PushIllegalArgument();
+ }
+ 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
+ PushIllegalArgument();
+ }
+ 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)
+ PushIllegalArgument();
+ 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)
+ {
+ PushIllegalParameter();
+ 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 ))
+ PushIllegalArgument();
+ 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:
+ PushIllegalParameter();
+ }
+}
+
+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(const OUString &str)
+{
+ if(str.isEmpty())
+ return 0;
+ else
+ return lcl_getLengthB( str, str.getLength() );
+}
+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;
+#if (U_ICU_VERSION_MAJOR_NUM < 55)
+ int32_t nStartPos = 0;
+ while (aRegexMatcher.find(nStartPos, status) && U_SUCCESS(status) && ++nCount < nOccurrence)
+#else
+ while (aRegexMatcher.find(status) && U_SUCCESS(status) && ++nCount < nOccurrence)
+#endif
+ ;
+ 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;
+#if (U_ICU_VERSION_MAJOR_NUM < 55)
+ int32_t nStartPos = 0;
+ while (aRegexMatcher.find(nStartPos, status) && U_SUCCESS(status))
+#else
+ while (aRegexMatcher.find(status) && U_SUCCESS(status))
+#endif
+ {
+ // 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::ParameterExpected : // #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 000000000..6ee1de409
--- /dev/null
+++ b/sc/source/core/tool/interpr2.cxx
@@ -0,0 +1,3679 @@
+/* -*- 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 <sfx2/bindings.hxx>
+#include <sfx2/linkmgr.hxx>
+#include <sfx2/objsh.hxx>
+#include <svl/numformat.hxx>
+#include <svl/zforlist.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 <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( GetInt32());
+ PushDouble( static_cast<double>(aDate.GetYear()) );
+}
+
+void ScInterpreter::ScGetMonth()
+{
+ Date aDate = pFormatter->GetNullDate();
+ aDate.AddDays( GetInt32());
+ PushDouble( static_cast<double>(aDate.GetMonth()) );
+}
+
+void ScInterpreter::ScGetDay()
+{
+ Date aDate = pFormatter->GetNullDate();
+ aDate.AddDays( GetInt32());
+ 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( GetInt32());
+ 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( GetInt32());
+ 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( GetInt32());
+
+ 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( GetInt32());
+ 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 )
+ {
+ sal_uInt32 nTemp = nDate1;
+ nDate1 = nDate2;
+ nDate2 = nTemp;
+ }
+ 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 = GetInt32();
+ 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 = GetInt32();
+ sal_Int32 nDate1 = GetInt32();
+ 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 = GetInt32();
+ sal_Int32 nDate1 = GetInt32();
+
+ 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;
+ 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
+ {
+ sal_Int16 nDec = GetInt16();
+ double fX = GetDouble();
+ if (nGlobalError == FormulaError::NONE)
+ {
+ if ( ( eMode == rtl_math_RoundingMode_Down ||
+ eMode == rtl_math_RoundingMode_Up ) &&
+ nDec < 12 && fmod( fX, 1.0 ) != 0.0 )
+ {
+ // tdf124286 : round to 12 significant digits before rounding
+ // down or up to avoid unexpected rounding errors
+ // caused by decimal -> binary -> decimal conversion
+ double fRes;
+ RoundSignificant( fX, 12, fRes );
+ fVal = ::rtl::math::round( fRes, 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 = ::rtl::math::approxFloor( log10( std::abs(fX) ) ) + 1.0 - fDigits;
+ fRes = ::rtl::math::round( pow(10.0, -fTemp ) * fX ) * 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, mrDoc, 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, mrDoc, 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 ) / rtl::math::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 * ::rtl::math::log1p(fRate) ) ) * fRate /
+ (std::expm1( (fNper + 1) * ::rtl::math::log1p(fRate) ) - fRate);
+ else // payment in arrear
+ fPayment = (fFv + fPv * exp(fNper * ::rtl::math::log1p(fRate) ) ) * fRate /
+ std::expm1( fNper * ::rtl::math::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)))
+ / rtl::math::log1p(fRate));
+ else
+ PushDouble(log(-(fRate*fFV-fPmt)/(fRate*fPV+fPmt)) / rtl::math::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::NoRef);
+ 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::NoRef);
+ 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() )
+ {
+ SfxObjectShell* 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( pChars[ nIndex ] ).append( 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( const OUString& 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 ( aSearchUnit.equalsIgnoreAsciiCaseAscii( 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 000000000..1fa4500a9
--- /dev/null
+++ b/sc/source/core/tool/interpr3.cxx
@@ -0,0 +1,5579 @@
+/* -*- 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 * 0.7071067811865475); // * 1/sqrt(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) - rtl::math::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) - rtl::math::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 * ::rtl::math::log1p(fTempA)
+ -fB * ::rtl::math::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 * ::rtl::math::log1p(fTempA)
+ -fB * ::rtl::math::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) * ::rtl::math::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) ? ::rtl::math::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 * ::rtl::math::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 = ::rtl::math::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, mrDoc, 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, mrDoc, 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, mrDoc, 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, mrDoc, 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 = GetTopNumberArray(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),
+ [](double f) {
+ f = rtl::math::approxFloor(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::GetTopNumberArray( 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, mrDoc, aRange, mnSubTotalFlags);
+ if (aValIter.GetFirst(fCellVal, nErr))
+ {
+ do
+ aArray.push_back(fCellVal);
+ while (aValIter.GetNext(fCellVal, nErr) && nErr == FormulaError::NONE);
+ }
+ 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, mrDoc, 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 ] )
+ PushNoValue();
+ 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 ( !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, mrDoc, 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, mrDoc, 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)
+ {
+ double fTemp = fLo;
+ fLo = fUp;
+ fUp = fTemp;
+ }
+ 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 = sqrt(fR*fR + fI*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 000000000..120aea5d3
--- /dev/null
+++ b/sc/source/core/tool/interpr4.cxx
@@ -0,0 +1,4795 @@
+/* -*- 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 <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 <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.meType == CELLTYPE_FORMULA)
+ nErr = rCell.mpFormula->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.meType == CELLTYPE_FORMULA ? rCell.mpFormula->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);
+ if ( nGlobalError == FormulaError::NONE || nGlobalError == FormulaError::CellNoValue )
+ nGlobalError = nErr;
+ return nVal;
+}
+
+double ScInterpreter::GetCellValueOrZero( const ScAddress& rPos, ScRefCellValue& rCell )
+{
+ double fValue = 0.0;
+
+ CellType eType = rCell.meType;
+ switch (eType)
+ {
+ case CELLTYPE_FORMULA:
+ {
+ ScFormulaCell* pFCell = rCell.mpFormula;
+ 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.mfValue;
+ 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.meType)
+ {
+ case CELLTYPE_STRING:
+ case CELLTYPE_EDIT:
+ rStr = mrStrPool.intern(rCell.getString(&mrDoc));
+ break;
+ case CELLTYPE_FORMULA:
+ {
+ ScFormulaCell* pFCell = rCell.mpFormula;
+ nErr = pFCell->GetErrCode();
+ if (pFCell->IsValue())
+ {
+ rStr = GetStringFromDouble( pFCell->GetValue() );
+ }
+ else
+ rStr = pFCell->GetString();
+ }
+ break;
+ case CELLTYPE_VALUE:
+ {
+ rStr = GetStringFromDouble( rCell.mfValue );
+ }
+ 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.meType)
+ {
+ case CELLTYPE_VALUE :
+ nVal = GetValueCellValue(aAdr, aCell.mfValue);
+ break;
+ case CELLTYPE_FORMULA :
+ if (aCell.mpFormula->IsValue())
+ {
+ nErr = aCell.mpFormula->GetErrCode();
+ nVal = aCell.mpFormula->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.meType)
+ {
+ case CELLTYPE_STRING:
+ case CELLTYPE_EDIT:
+ aStr = aCell.getString(&mrDoc);
+ break;
+ case CELLTYPE_FORMULA:
+ if (!aCell.mpFormula->IsValue())
+ {
+ nErr = aCell.mpFormula->GetErrCode();
+ aStr = aCell.mpFormula->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.meType)
+ {
+ case CELLTYPE_STRING :
+ case CELLTYPE_EDIT :
+ aStr = aCell.getString(&mrDoc);
+ nType = 1;
+ break;
+ case CELLTYPE_VALUE :
+ nVal = GetValueCellValue(aAdr, aCell.mfValue);
+ break;
+ case CELLTYPE_FORMULA :
+ nErr = aCell.mpFormula->GetErrCode();
+ if (aCell.mpFormula->IsValue())
+ nVal = aCell.mpFormula->GetValue();
+ else
+ aStr = aCell.mpFormula->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.meType == CELLTYPE_FORMULA);
+ if (pRetTypeExpr && pRetIndexExpr)
+ mrDoc.GetNumberFormatInfo(mrContext, *pRetTypeExpr, *pRetIndexExpr, rAddress);
+ PushTempToken( new ScEmptyCellToken( bInherited, bDisplayEmptyAsString));
+ return;
+ }
+
+ FormulaError nErr = FormulaError::NONE;
+ if (aCell.meType == CELLTYPE_FORMULA)
+ nErr = aCell.mpFormula->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;
+}
+
+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, 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.mpFormula->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();
+ OSL_ENSURE( nParams <= sp, "ConvertMatrixParameters: stack/param count mismatch");
+ 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)
+ {
+ OSL_FAIL( "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( 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( 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::NoValue);
+ }
+ // else: the consuming function has to decide if and how to
+ // handle a reference list argument in array context.
+ }
+ break;
+ default:
+ OSL_FAIL( "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
+ {
+ auto pJumpMat = std::make_shared<ScJumpMatrix>( pCur->GetOpCode(), nJumpCols, nJumpRows);
+ 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_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 )
+ {
+ SfxObjectShell* 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() );
+
+ SfxObjectShell* 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->IsVBACompat();
+ 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.meType)
+ {
+ case CELLTYPE_VALUE :
+ nVal = GetValueCellValue(rPos, aCell.mfValue);
+ pVar->PutDouble( nVal );
+ break;
+ case CELLTYPE_STRING :
+ case CELLTYPE_EDIT :
+ pVar->PutString(aCell.getString(&mrDoc));
+ break;
+ case CELLTYPE_FORMULA :
+ nErr = aCell.mpFormula->GetErrCode();
+ if( nErr == FormulaError::NONE )
+ {
+ if (aCell.mpFormula->IsValue())
+ {
+ nVal = aCell.mpFormula->GetValue();
+ pVar->PutDouble( nVal );
+ }
+ else
+ pVar->PutString(aCell.mpFormula->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.meType == CELLTYPE_FORMULA)
+ aCell.mpFormula->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.meType == CELLTYPE_FORMULA)
+ aCell.mpFormula->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.meType == CELLTYPE_FORMULA)
+ {
+ aCell.mpFormula->SetDirtyVar();
+ aCell.mpFormula->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.
+ nStackBase = sp - pCur->GetParamCount();
+ if ( nStackBase > sp )
+ nStackBase = sp; // 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.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 000000000..aa445079f
--- /dev/null
+++ b/sc/source/core/tool/interpr5.cxx
@@ -0,0 +1,3345 @@
+/* -*- 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 <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 {
+
+struct MatrixAdd
+{
+ double operator() (const double& lhs, const double& rhs) const
+ {
+ return ::rtl::math::approxAdd( lhs,rhs);
+ }
+};
+
+struct MatrixSub
+{
+ double operator() (const double& lhs, const double& rhs) const
+ {
+ return ::rtl::math::approxSub( lhs,rhs);
+ }
+};
+
+struct MatrixMul
+{
+ double operator() (const double& lhs, const double& rhs) const
+ {
+ return lhs * rhs;
+ }
+};
+
+struct MatrixDiv
+{
+ double operator() (const double& lhs, const double& rhs) const
+ {
+ return ScInterpreter::div( lhs,rhs);
+ }
+};
+
+struct MatrixPow
+{
+ double operator() (const double& lhs, const double& rhs) const
+ {
+ 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, mrDoc, 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, mrDoc, 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.mpFormula->GetErrCode(), nMatCol, nMatRow);
+ }
+ else if (aCell.hasNumeric())
+ {
+ double fVal = aCell.getValue();
+ // CELLTYPE_FORMULA already stores the rounded value.
+ if (aCell.meType == 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.meType == CELLTYPE_FORMULA)
+ {
+ FormulaError nErrCode = aCell.mpFormula->GetErrCode();
+ if (nErrCode != FormulaError::NONE)
+ PushError( nErrCode);
+ else
+ {
+ const ScMatrix* pMat = aCell.mpFormula->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;
+}
+
+template<class Function>
+static ScMatrixRef lcl_MatrixCalculation(
+ const ScMatrix& rMat1, const ScMatrix& rMat2, ScInterpreter* pInterpreter)
+{
+ static const Function Op;
+
+ SCSIZE nC1, nC2, nMinC;
+ SCSIZE nR1, nR2, nMinR;
+ SCSIZE i, j;
+ 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)
+ {
+ for (i = 0; i < nMinC; i++)
+ {
+ for (j = 0; j < nMinR; j++)
+ {
+ bool bVal1 = rMat1.IsValueOrEmpty(i,j);
+ bool bVal2 = rMat2.IsValueOrEmpty(i,j);
+ FormulaError nErr;
+ if (bVal1 && bVal2)
+ {
+ double d = Op(rMat1.GetDouble(i,j), rMat2.GetDouble(i,j));
+ xResMat->PutDouble( d, i, j);
+ }
+ else if (((nErr = rMat1.GetErrorIfNotString(i,j)) != FormulaError::NONE) ||
+ ((nErr = rMat2.GetErrorIfNotString(i,j)) != FormulaError::NONE))
+ {
+ xResMat->PutError( nErr, i, j);
+ }
+ else if ((!bVal1 && rMat1.IsStringOrEmpty(i,j)) || (!bVal2 && rMat2.IsStringOrEmpty(i,j)))
+ {
+ FormulaError nError1 = FormulaError::NONE;
+ SvNumFormatType nFmt1 = SvNumFormatType::ALL;
+ double fVal1 = (bVal1 ? rMat1.GetDouble(i,j) :
+ pInterpreter->ConvertStringToValue( rMat1.GetString(i,j).getString(), nError1, nFmt1));
+
+ FormulaError nError2 = FormulaError::NONE;
+ SvNumFormatType nFmt2 = SvNumFormatType::ALL;
+ double fVal2 = (bVal2 ? rMat2.GetDouble(i,j) :
+ pInterpreter->ConvertStringToValue( rMat2.GetString(i,j).getString(), nError2, nFmt2));
+
+ if (nError1 != FormulaError::NONE)
+ xResMat->PutError( nError1, i, j);
+ else if (nError2 != FormulaError::NONE)
+ xResMat->PutError( nError2, i, j);
+ else
+ {
+ double d = Op( fVal1, fVal2);
+ xResMat->PutDouble( d, i, j);
+ }
+ }
+ else
+ xResMat->PutError( FormulaError::NoValue, i, j);
+ }
+ }
+ }
+ 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;
+ 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;
+ 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;
+ 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<MatrixSub>( *pMat1, *pMat2, this);
+ }
+ else
+ {
+ pResMat = lcl_MatrixCalculation<MatrixAdd>( *pMat1, *pMat2, this);
+ }
+
+ 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 ( _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<MatrixMul>( *pMat1, *pMat2, this);
+ 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<MatrixDiv>( *pMat1, *pMat2, this);
+ 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<MatrixPow>( *pMat1, *pMat2, this);
+ 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<MatrixSub>( *pMat1, *pMat2, this);
+ 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.meType != CELLTYPE_FORMULA)
+ {
+ PushError( FormulaError::NoRef );
+ return;
+ }
+
+ if (aCell.mpFormula->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.mpFormula->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.mpFormula->GetErrCode();
+ if (nErr != FormulaError::NONE)
+ PushError( nErr );
+ else if (aCell.mpFormula->IsValue())
+ PushDouble(aCell.mpFormula->GetValue());
+ else
+ {
+ svl::SharedString aVal = aCell.mpFormula->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 000000000..545bbd58c
--- /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.meType;
+ 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, mrDoc, aRange, mnSubTotalFlags, bTextAsZero );
+ FormulaError nErr = FormulaError::NONE;
+ if (aValIter.GetFirst(fVal, nErr))
+ {
+ // placed the loop on the inside for performance reasons:
+ aValIter.GetCurNumFmtInfo( mrContext, 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 000000000..352c7cf70
--- /dev/null
+++ b/sc/source/core/tool/interpr7.cxx
@@ -0,0 +1,556 @@
+/* -*- 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>
+
+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 000000000..df75c92d4
--- /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:
+ {
+ const OUString& rStr = GetString().getString();
+ if (CheckStringResultLen(aResBuf, rStr.getLength()))
+ aResBuf.append( rStr);
+ }
+ 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 ) )
+ {
+ const OUString& rStr = pMat->GetString( j, k ).getString();
+ if (CheckStringResultLen(aResBuf, rStr.getLength()))
+ aResBuf.append( rStr);
+ }
+ else
+ {
+ if ( pMat->IsValue( j, k ) )
+ {
+ const OUString& rStr = pMat->GetString( *pFormatter, j, k ).getString();
+ if (CheckStringResultLen(aResBuf, rStr.getLength()))
+ aResBuf.append( rStr);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ 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 000000000..e66a8b977
--- /dev/null
+++ b/sc/source/core/tool/interpretercontext.cxx
@@ -0,0 +1,215 @@
+/* -*- 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() { 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()
+{
+ for (auto& rPtr : aThreadedInterpreterPool.maPool)
+ rPtr->ClearLookupCache();
+ for (auto& rPtr : aNonThreadedInterpreterPool.maPool)
+ rPtr->ClearLookupCache();
+}
+
+/* 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 000000000..868d5da65
--- /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 000000000..10cebdd42
--- /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 000000000..979eca033
--- /dev/null
+++ b/sc/source/core/tool/lookupcache.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/.
+ *
+ * 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())
+ {
+ const ScHint* p = dynamic_cast<const ScHint*>(&rHint);
+ if ((p && (p->GetId() == SfxHintId::ScDataChanged)) || dynamic_cast<const ScAreaChangedHint*>(&rHint))
+ {
+ mpDoc->RemoveLookupCache( *this);
+ delete this;
+ }
+ }
+}
+
+/* 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 000000000..e61d39386
--- /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 000000000..9406d0925
--- /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 000000000..ad8e29580
--- /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 000000000..8240c1ac6
--- /dev/null
+++ b/sc/source/core/tool/numformat.cxx
@@ -0,0 +1,65 @@
+/* -*- 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>
+
+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
+
+ OUString aDecSep;
+ LanguageType nFormatLang = pFormat->GetLanguage();
+ if (nFormatLang == LANGUAGE_SYSTEM)
+ aDecSep = ScGlobal::getLocaleData().getNumDecimalSep();
+ else
+ {
+ LocaleDataWrapper aLocaleData(
+ comphelper::getProcessComponentContext(), LanguageTag(nFormatLang));
+ aDecSep = aLocaleData.getNumDecimalSep();
+ }
+
+ SvtScriptType nScript = rDoc.GetStringScriptType(aDecSep);
+ 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 000000000..66756c0ab
--- /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, MapDupToInternal when writing ODFF, 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 000000000..8f3e1fec9
--- /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 000000000..26fc0e27f
--- /dev/null
+++ b/sc/source/core/tool/orcusxml.cxx
@@ -0,0 +1,30 @@
+/* -*- 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 <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, const OString& rPath) :
+ maPos(rPos), maPath(rPath) {}
+
+/* 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 000000000..473177c8f
--- /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 000000000..e9b3e1516
--- /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 000000000..ff4298e54
--- /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.getStr());
+ }
+}
+
+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 000000000..83f4e687f
--- /dev/null
+++ b/sc/source/core/tool/progress.cxx
@@ -0,0 +1,179 @@
+/* -*- 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 <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)
+ {
+ SfxItemSet* pSet = pMed->GetItemSet();
+ const SfxBoolItem* pItem;
+ if ( pSet && (pItem = pSet->GetItemIfSet( SID_HIDDEN )) &&
+ 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 000000000..d66382d2e
--- /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 000000000..286a1ef4f
--- /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 000000000..e762908e2
--- /dev/null
+++ b/sc/source/core/tool/rangecache.cxx
@@ -0,0 +1,200 @@
+/* -*- 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())
+ {
+ const ScHint* p = dynamic_cast<const ScHint*>(&rHint);
+ if ((p && (p->GetId() == SfxHintId::ScDataChanged))
+ || dynamic_cast<const ScAreaChangedHint*>(&rHint))
+ {
+ mpDoc->RemoveSortedRangeCache(*this);
+ delete this;
+ }
+ }
+}
+
+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 000000000..fb880d308
--- /dev/null
+++ b/sc/source/core/tool/rangelst.cxx
@@ -0,0 +1,1532 @@
+/* -*- 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 )
+{
+ maPairs.erase(std::remove_if(maPairs.begin(), maPairs.end(),
+ [&nTab](const ScRangePair& rR) {
+ const ScRange & rRange = rR.GetRange(0);
+ return (rRange.aStart.Tab() == nTab) && (rRange.aEnd.Tab() == nTab);
+ }),
+ maPairs.end());
+}
+
+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 000000000..b5578ca26
--- /dev/null
+++ b/sc/source/core/tool/rangenam.cxx
@@ -0,0 +1,899 @@
+/* -*- 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;
+ 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 000000000..978f75b44
--- /dev/null
+++ b/sc/source/core/tool/rangeseq.cxx
@@ -0,0 +1,446 @@
+/* -*- 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 <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.meType == CELLTYPE_FORMULA && aCell.mpFormula->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,
+ sal_uInt16 nEncoding )
+{
+ uno::Sequence<sal_Int8> aSeq;
+ if ( rAny >>= aSeq )
+ {
+ rString = OUString( reinterpret_cast<const char*>(aSeq.getConstArray()),
+ aSeq.getLength(), nEncoding );
+ rString = comphelper::string::stripEnd(rString, 0);
+ return true;
+ }
+ return false;
+}
+
+/* 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 000000000..6eb1cf52f
--- /dev/null
+++ b/sc/source/core/tool/rangeutl.cxx
@@ -0,0 +1,1068 @@
+/* -*- 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(
+ const OUString& rString,
+ sal_Unicode cSearchChar,
+ sal_Int32 nOffset,
+ sal_Unicode cQuote )
+{
+ sal_Int32 nLength = rString.getLength();
+ 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(
+ const OUString& rString,
+ sal_Unicode cSearchChar,
+ sal_Int32 nOffset )
+{
+ sal_Int32 nLength = rString.getLength();
+ 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,
+ const OUString& rString,
+ sal_Int32& nOffset,
+ sal_Unicode cSeparator,
+ sal_Unicode cQuote)
+{
+ sal_Int32 nLength = rString.getLength();
+ 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.copy( 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( const OUString& 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,
+ const OUString& 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,
+ const OUString& 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,
+ const OUString& rRangeListStr,
+ const ScDocument& rDocument,
+ FormulaGrammar::AddressConvention eConv,
+ sal_Unicode cSeparator,
+ sal_Unicode cQuote )
+{
+ bool bRet = true;
+ OSL_ENSURE( !rRangeListStr.isEmpty(), "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,
+ const OUString& 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,
+ const OUString& 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, const OUString& 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('.');
+ 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 000000000..abd44d041
--- /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 000000000..59601f37a
--- /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 000000000..3e1b9b1af
--- /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 000000000..1d930dadf
--- /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>
+
+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 (; nNewEnd <= nEndPos; ++p, ++nNewEnd)
+ if (*p == '\'')
+ break;
+ if (nNewEnd > nEndPos)
+ break;
+ }
+ else if (*p == '[')
+ {
+ // Skip until the closing bracket.
+ for (; 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(
+ const OUString& rFormula, const ScAddress& rPos,
+ ScDocument& rDoc, formula::FormulaGrammar::AddressConvention eConvP) :
+ maFormula(rFormula),
+ 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);
+ aResult.append(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 000000000..a7245fd28
--- /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 000000000..1ad6734d9
--- /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 000000000..a4992485e
--- /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 000000000..ef0902aab
--- /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 bCut = false;
+ if ( rRef >= nStart )
+ rRef = sal::static_int_cast<R>( rRef + nDelta );
+ else if ( nDelta < 0 && 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 bCut = false;
+ if ( rRef >= nStart )
+ rRef = sal::static_int_cast<R>( rRef + nDelta );
+ else if ( nDelta < 0 && 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 );
+ bCut2 = lcl_MoveEnd( theTab2, nTab1, nDz, nMaxTab );
+ 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 000000000..cb04dd315
--- /dev/null
+++ b/sc/source/core/tool/scmatrix.cxx
@@ -0,0 +1,3420 @@
+/* -*- 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 <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_trait
+{
+ typedef sc::string_block string_element_block;
+ typedef sc::uint16_block integer_element_block;
+
+ typedef mdds::mtv::custom_block_func1<sc::string_block> element_block_func;
+};
+
+}
+
+typedef mdds::multi_type_matrix<matrix_trait> MatrixImplType;
+
+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;
+
+}
+
+/* TODO: it would be good if mdds had get/set<sal_uInt8> additionally to
+ * get/set<bool>, we're abusing double here. */
+typedef double TMatFlag;
+const TMatFlag SC_MATFLAG_EMPTYRESULT = 1.0;
+const TMatFlag SC_MATFLAG_EMPTYPATH = 2.0;
+
+class ScMatrixImpl
+{
+ MatrixImplType maMat;
+ MatrixImplType 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() COVERITY_NOEXCEPT_FALSE;
+
+ 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);
+
+#if DEBUG_MATRIX
+ void Dump() const;
+#endif
+
+private:
+ void CalcPosition(SCSIZE nIndex, SCSIZE& rC, SCSIZE& rR) const;
+};
+
+static bool bElementsMaxFetched;
+static 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() COVERITY_NOEXCEPT_FALSE
+{
+ nElementsMax += GetElementCount();
+ Clear();
+}
+
+void ScMatrixImpl::Clear()
+{
+ 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);
+ maMatFlag.set(nR, nC, SC_MATFLAG_EMPTYPATH);
+ }
+ 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 ))
+ {
+ double fErr = 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:
+ return svl::SharedString::getEmptyString();
+ case mdds::mtm::element_numeric:
+ case mdds::mtm::element_boolean:
+ fErr = maMat.get_numeric(aPos);
+ [[fallthrough]];
+ default:
+ OSL_FAIL("ScMatrixImpl::GetString: access error, no string");
+ }
+ SetErrorAtInterpreter(GetDoubleErrorValue(fErr));
+ }
+ else
+ {
+ OSL_FAIL("ScMatrixImpl::GetString: dimension error");
+ }
+ 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_numeric(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_numeric:
+ aVal.nType = maMatFlag.get<TMatFlag>(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
+{
+ ValidColRowReplicated( nC, nR );
+ 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
+{
+ // Flag must indicate an 'empty' or 'empty cell' or 'empty result' element,
+ // but not an 'empty path' element.
+ ValidColRowReplicated( nC, nR );
+ return maMat.get_type(nR, nC) == mdds::mtm::element_empty &&
+ maMatFlag.get_numeric(nR, nC) != SC_MATFLAG_EMPTYPATH;
+}
+
+bool ScMatrixImpl::IsEmptyCell( SCSIZE nC, SCSIZE nR ) const
+{
+ // Flag must indicate an 'empty cell' element instead of an
+ // 'empty' or 'empty result' or 'empty path' element.
+ ValidColRowReplicated( nC, nR );
+ 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
+{
+ // Flag must indicate an 'empty result' element instead of an
+ // 'empty' or 'empty cell' or 'empty path' element.
+ ValidColRowReplicated( nC, nR );
+ return maMat.get_type(nR, nC) == mdds::mtm::element_empty &&
+ maMatFlag.get_numeric(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_numeric(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
+{
+ ValidColRowReplicated(nC, nR);
+ 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
+{
+ ValidColRowReplicated(nC, nR);
+ 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
+{
+ ValidColRowReplicated( nC, nR );
+ 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<TMatFlag> 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<TMatFlag> 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(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 const & it_, U const & aOp):
+ it(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 const & itBegin, typename T::const_iterator const & itEnd, U const & aOp):
+ m_itBegin(itBegin),
+ m_itEnd(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 const & aDoubleFunc,
+ ScMatrix::BoolOpFunction const & aBoolFunc,
+ ScMatrix::StringOpFunction const & aStringFunc,
+ ScMatrix::EmptyOpFunction const & aEmptyFunc):
+ mnRowSize(nRowSize),
+ mnRowPos(0),
+ mnColPos(0),
+ maDoubleFunc(aDoubleFunc),
+ maBoolFunc(aBoolFunc),
+ maStringFunc(aStringFunc),
+ maEmptyFunc(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),
+ aDoubleFunc, aBoolFunc, aStringFunc, 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),
+ aDoubleFunc2, aBoolFunc2, aStringFunc2, 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);
+ }
+ }
+}
+
+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;
+
+ if (!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);
+ }
+ bElementsMaxFetched = true;
+ }
+
+ 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);
+}
+
+/* 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 000000000..38ca8c252
--- /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 000000000..7680aac40
--- /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 000000000..7b8749006
--- /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 000000000..493f3fdee
--- /dev/null
+++ b/sc/source/core/tool/stringutil.cxx
@@ -0,0 +1,466 @@
+/* -*- 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),
+ meSetTextNumFormat(Never),
+ mbHandleApostrophe(true),
+ meStartListening(sc::SingleCellListening),
+ mbCheckLinkFormula(false)
+{
+}
+
+void ScSetStringParam::setTextInput()
+{
+ mbDetectNumberFormat = false;
+ mbHandleApostrophe = false;
+ meSetTextNumFormat = Always;
+}
+
+void ScSetStringParam::setNumericInput()
+{
+ mbDetectNumberFormat = true;
+ mbHandleApostrophe = true;
+ meSetTextNumFormat = Never;
+}
+
+bool ScStringUtil::parseSimpleNumber(
+ const OUString& rStr, sal_Unicode dsep, sal_Unicode gsep, sal_Unicode dsepa, 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. */
+
+ 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)
+ // 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 000000000..e9a920500
--- /dev/null
+++ b/sc/source/core/tool/stylehelper.cxx
@@ -0,0 +1,162 @@
+/* -*- 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 ), OUString(SC_STYLE_PROG_STANDARD) },
+ { ScResId( STR_STYLENAME_RESULT ), OUString(SC_STYLE_PROG_RESULT) },
+ { ScResId( STR_STYLENAME_RESULT1 ), OUString(SC_STYLE_PROG_RESULT1) },
+ { ScResId( STR_STYLENAME_HEADING ), OUString(SC_STYLE_PROG_HEADING) },
+ { ScResId( STR_STYLENAME_HEADING_1 ), OUString(SC_STYLE_PROG_HEADING1) },
+ // Pivot table styles.
+ { ScResId( STR_PIVOT_STYLENAME_INNER ), OUString(SC_PIVOT_STYLE_PROG_INNER) },
+ { ScResId( STR_PIVOT_STYLENAME_RESULT ), OUString(SC_PIVOT_STYLE_PROG_RESULT) },
+ { ScResId( STR_PIVOT_STYLENAME_CATEGORY ), OUString(SC_PIVOT_STYLE_PROG_CATEGORY) },
+ { ScResId( STR_PIVOT_STYLENAME_TITLE ), OUString(SC_PIVOT_STYLE_PROG_TITLE) },
+ { ScResId( STR_PIVOT_STYLENAME_FIELDNAME ), OUString(SC_PIVOT_STYLE_PROG_FIELDNAME) },
+ { ScResId( STR_PIVOT_STYLENAME_TOP ), OUString(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 ), OUString(SC_STYLE_PROG_STANDARD) },
+ { ScResId( STR_STYLENAME_REPORT ), OUString(SC_STYLE_PROG_REPORT) },
+ // last entry remains empty
+ { OUString(), OUString() },
+ };
+ return aPageMap;
+ }
+ OSL_FAIL("invalid family");
+ return nullptr;
+}
+
+// programmatic name suffix for display names that match other programmatic names
+// is " (user)" including a space
+
+constexpr OUStringLiteral SC_SUFFIX_USER = u" (user)";
+
+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 000000000..4c213893b
--- /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 000000000..2e1c641da
--- /dev/null
+++ b/sc/source/core/tool/token.cxx
@@ -0,0 +1,5389 @@
+/* -*- 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>
+
+using ::std::vector;
+
+#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 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(aSS);
+ else
+ return new FormulaStringOpToken(eOp, 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, aTabName, extref.aRef.Ref1);
+ }
+ case svExternalDoubleRef:
+ {
+ svl::SharedString aTabName(maExternalName); // string not interned
+ return new ScExternalDoubleRefToken(extref.nFileId, aTabName, extref.aRef);
+ }
+ case svExternalName:
+ {
+ svl::SharedString aName(maExternalName); // string not interned
+ return new ScExternalNameToken( extname.nFileId, 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( const ScMatrixRef& p ) :
+ FormulaToken(formula::svMatrix), pMatrix(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, const svl::SharedString& rTabName, const ScSingleRefData& r ) :
+ FormulaToken( svExternalSingleRef, ocPush),
+ mnFileId(nFileId),
+ maTabName(rTabName),
+ 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, const svl::SharedString& rTabName, const ScComplexRefData& r ) :
+ FormulaToken( svExternalDoubleRef, ocPush),
+ mnFileId(nFileId),
+ maTabName(rTabName),
+ 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, const svl::SharedString& rName ) :
+ FormulaToken( svExternalName, ocPush),
+ mnFileId(nFileId),
+ maName(rName)
+{
+}
+
+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( const ScConstMatrixRef& pMat, const formula::FormulaToken* pUL ) :
+ FormulaToken(formula::svMatrixCell), xMatrix(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, const OUString & rFormula, bool bEmptyDisplayedAsString ) :
+ FormulaToken( formula::svHybridCell ),
+ mfDouble( f ), maString( rStr ),
+ maFormula( rFormula ),
+ 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(aAbs.Tab() != rNewPos.Tab() || !rRef.IsTabRel());
+ }
+ 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);
+ // Absolute sheet reference => set 3D flag.
+ // More than one sheet referenced => has to have both 3D flags.
+ // If end part has 3D flag => start part must have it too.
+ rRef.Ref2.SetFlag3D(aAbs.aStart.Tab() != aAbs.aEnd.Tab() || !rRef.Ref2.IsTabRel());
+ rRef.Ref1.SetFlag3D(aAbs.aStart.Tab() != rNewPos.Tab() || !rRef.Ref1.IsTabRel() ||
+ rRef.Ref2.IsFlag3D());
+ }
+ 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;
+
+ 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 nNumCellsTerm =
+ (1 + (pComplexRef->Ref2.Row() - pComplexRef->Ref1.Row())) *
+ (1 + (pComplexRef->Ref2.Col() - pComplexRef->Ref1.Col())) / 10.;
+
+ 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 000000000..5a4430c15
--- /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 000000000..ecfdfdba9
--- /dev/null
+++ b/sc/source/core/tool/typedstrdata.cxx
@@ -0,0 +1,103 @@
+/* -*- 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>
+
+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)
+ return left.mfValue < right.mfValue;
+
+ if (left.mbIsDate != right.mbIsDate)
+ return left.mbIsDate < right.mbIsDate;
+
+ return ScGlobal::GetCaseCollator().compareString(
+ left.maStrValue, right.maStrValue) < 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)
+ return left.mfValue < right.mfValue;
+
+ if (left.mbIsDate != right.mbIsDate)
+ return left.mbIsDate < right.mbIsDate;
+
+ return ScGlobal::GetCollator().compareString(
+ left.maStrValue, right.maStrValue) < 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 ) :
+ maStrValue(rStr),
+ mfValue(fVal),
+ mfRoundedValue(fRVal),
+ meStrType(nType),
+ mbIsDate( bDate ) {}
+
+FindTypedStrData::FindTypedStrData(const ScTypedStrData& rVal, bool bCaseSens) :
+ maVal(rVal), 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 000000000..1f5337cd8
--- /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 000000000..10a4de328
--- /dev/null
+++ b/sc/source/core/tool/userlist.cxx
@@ -0,0 +1,360 @@
+/* -*- 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 <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>
+
+namespace {
+
+class FindByName
+{
+ const OUString& mrName;
+ bool mbUpper;
+public:
+ FindByName(const OUString& rName, bool bUpper) : mrName(rName), mbUpper(bUpper) {}
+ bool operator() (const ScUserListData::SubStr& r) const
+ {
+ return mbUpper ? r.maUpper == mrName : r.maReal == mrName;
+ }
+};
+
+}
+
+ScUserListData::SubStr::SubStr(const OUString& rReal, const OUString& rUpper) :
+ maReal(rReal), maUpper(rUpper) {}
+
+void ScUserListData::InitTokens()
+{
+ sal_Unicode cSep = ScGlobal::cListDelimiter;
+ maSubStrings.clear();
+ const sal_Unicode* p = aStr.getStr();
+ const sal_Unicode* p0 = p;
+ sal_Int32 nLen = 0;
+ bool bFirst = true;
+ for (sal_Int32 i = 0, n = aStr.getLength(); i < n; ++i, ++p, ++nLen)
+ {
+ if (bFirst)
+ {
+ // very first character, or the first character after a separator.
+ p0 = p;
+ nLen = 0;
+ bFirst = false;
+ }
+ if (*p == cSep)
+ {
+ if (nLen)
+ {
+ OUString aSub(p0, nLen);
+ OUString aUpStr = ScGlobal::getCharClass().uppercase(aSub);
+ maSubStrings.emplace_back(aSub, aUpStr);
+ }
+ bFirst = true;
+ }
+ }
+
+ if (nLen)
+ {
+ OUString aSub(p0, nLen);
+ OUString aUpStr = ScGlobal::getCharClass().uppercase(aSub);
+ maSubStrings.emplace_back(aSub, aUpStr);
+ }
+}
+
+ScUserListData::ScUserListData(const OUString& rStr) :
+ aStr(rStr)
+{
+ InitTokens();
+}
+
+ScUserListData::ScUserListData(const ScUserListData& rData) :
+ aStr(rData.aStr)
+{
+ InitTokens();
+}
+
+ScUserListData::~ScUserListData()
+{
+}
+
+void ScUserListData::SetString( const OUString& rStr )
+{
+ aStr = rStr;
+ InitTokens();
+}
+
+size_t ScUserListData::GetSubCount() const
+{
+ return maSubStrings.size();
+}
+
+bool ScUserListData::GetSubIndex(const OUString& rSubStr, sal_uInt16& rIndex, bool& bMatchCase) const
+{
+ // First, case sensitive search.
+ SubStringsType::const_iterator itr = ::std::find_if(
+ maSubStrings.begin(), maSubStrings.end(), FindByName(rSubStr, false));
+ if (itr != maSubStrings.end())
+ {
+ rIndex = ::std::distance(maSubStrings.begin(), itr);
+ bMatchCase = true;
+ return true;
+ }
+
+ // When that fails, do a case insensitive search.
+ OUString aUpStr = ScGlobal::getCharClass().uppercase(rSubStr);
+ itr = ::std::find_if(
+ maSubStrings.begin(), maSubStrings.end(), FindByName(aUpStr, true));
+ if (itr != maSubStrings.end())
+ {
+ rIndex = ::std::distance(maSubStrings.begin(), itr);
+ bMatchCase = false;
+ return true;
+ }
+ bMatchCase = false;
+ 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()
+{
+ using namespace ::com::sun::star;
+
+ sal_Unicode cDelimiter = ScGlobal::cListDelimiter;
+ uno::Sequence< i18n::CalendarItem2 > xCal;
+
+ const uno::Sequence< i18n::Calendar2 > xCalendars(
+ ScGlobal::getLocaleData().getAllCalendars() );
+
+ for ( const auto& rCalendar : xCalendars )
+ {
+ xCal = rCalendar.Days;
+ if ( 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 ) )
+ maData.push_back( std::make_unique<ScUserListData>( aDayShort ));
+ if ( !HasEntry( aDayLong ) )
+ maData.push_back( std::make_unique<ScUserListData>( aDayLong ));
+ }
+
+ xCal = rCalendar.Months;
+ if ( 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 ) )
+ maData.push_back( std::make_unique<ScUserListData>( aMonthShort ));
+ if ( !HasEntry( aMonthLong ) )
+ maData.push_back( std::make_unique<ScUserListData>( aMonthLong ));
+ }
+ }
+}
+
+ScUserList::ScUserList(const ScUserList& rOther)
+{
+ for (const std::unique_ptr<ScUserListData>& rData : rOther.maData)
+ maData.push_back(std::make_unique<ScUserListData>(*rData));
+}
+
+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.get();
+ if (!pFirstCaseInsensitive)
+ pFirstCaseInsensitive = rxItem.get();
+ }
+ }
+
+ return pFirstCaseInsensitive;
+}
+
+const ScUserListData& ScUserList::operator[](size_t nIndex) const
+{
+ return *maData[nIndex];
+}
+
+ScUserListData& ScUserList::operator[](size_t nIndex)
+{
+ return *maData[nIndex];
+}
+
+ScUserList& ScUserList::operator=( const ScUserList& rOther )
+{
+ maData.clear();
+ for (const std::unique_ptr<ScUserListData>& rData : rOther.maData)
+ maData.push_back(std::make_unique<ScUserListData>(*rData));
+ return *this;
+}
+
+bool ScUserList::operator==( const ScUserList& r ) const
+{
+ return std::equal(maData.begin(), maData.end(), r.maData.begin(), r.maData.end(),
+ [](const std::unique_ptr<ScUserListData>& lhs, const std::unique_ptr<ScUserListData>& rhs) {
+ return (lhs->GetString() == rhs->GetString()) && (lhs->GetSubCount() == rhs->GetSubCount());
+ });
+}
+
+bool ScUserList::operator!=( const ScUserList& r ) const
+{
+ return !operator==( r );
+}
+
+bool ScUserList::HasEntry( std::u16string_view rStr ) const
+{
+ return ::std::any_of(maData.begin(), maData.end(),
+ [&] (std::unique_ptr<ScUserListData> const& pData)
+ { return pData->GetString() == rStr; } );
+}
+
+ScUserList::iterator ScUserList::begin()
+{
+ return maData.begin();
+}
+
+void ScUserList::clear()
+{
+ maData.clear();
+}
+
+size_t ScUserList::size() const
+{
+ return maData.size();
+}
+
+void ScUserList::push_back(ScUserListData* p)
+{
+ maData.push_back(std::unique_ptr<ScUserListData>(p));
+}
+
+void ScUserList::erase(const iterator& itr)
+{
+ maData.erase(itr);
+}
+
+/* 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 000000000..30e5e14c9
--- /dev/null
+++ b/sc/source/core/tool/viewopti.cxx
@@ -0,0 +1,611 @@
+/* -*- 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 <global.hxx>
+#include <viewopti.hxx>
+#include <sc.hrc>
+#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;
+ nFldSnapX = 1000;
+ nFldSnapY = 1000;
+ }
+ else
+ {
+ nFldDrawX = 1270; // 0,5"
+ nFldDrawY = 1270;
+ nFldSnapX = 1270;
+ nFldSnapY = 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
+ && nFldSnapX == rCpy.nFldSnapX
+ && nFldSnapY == rCpy.nFldSnapY
+ && 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_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_CLIPMARKS ] = true;
+ aOptArr[ VOPT_SUMMARY ] = true;
+ 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 = SC_STD_GRIDCOLOR;
+
+ 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);
+
+ 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->SetFieldSnapX ( aGridOpt.GetFieldSnapX() );
+ pItem->SetFieldSnapY ( aGridOpt.GetFieldSnapY() );
+ 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_VALUEHI 3
+#define SCDISPLAYOPT_ANCHOR 4
+#define SCDISPLAYOPT_TEXTOVER 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_OPTION_X 4
+#define SCGRIDOPT_OPTION_Y 5
+#define SCGRIDOPT_SNAPTOGRID 6
+#define SCGRIDOPT_SYNCHRON 7
+#define SCGRIDOPT_VISIBLE 8
+#define SCGRIDOPT_SIZETOGRID 9
+
+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
+ "ValueHighlighting", // SCDISPLAYOPT_VALUEHI
+ "Anchor", // SCDISPLAYOPT_ANCHOR
+ "TextOverflow", // SCDISPLAYOPT_TEXTOVER
+ "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
+ (bIsMetric ? OUString("Option/XAxis/Metric")
+ : OUString("Option/XAxis/NonMetric")), // SCGRIDOPT_OPTION_X
+ (bIsMetric ? OUString("Option/YAxis/Metric")
+ : OUString("Option/YAxis/NonMetric")), // SCGRIDOPT_OPTION_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_VALUEHI:
+ SetOption( VOPT_SYNTAX, ScUnoHelpFunctions::GetBoolFromAny( pValues[nProp] ) );
+ break;
+ case SCDISPLAYOPT_ANCHOR:
+ SetOption( VOPT_ANCHOR, ScUnoHelpFunctions::GetBoolFromAny( pValues[nProp] ) );
+ break;
+ case SCDISPLAYOPT_TEXTOVER:
+ SetOption( VOPT_CLIPMARKS, 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_OPTION_X:
+ if (pValues[nProp] >>= nIntVal) aGrid.SetFieldSnapX( nIntVal );
+ break;
+ case SCGRIDOPT_OPTION_Y:
+ if (pValues[nProp] >>= nIntVal) aGrid.SetFieldSnapY( 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_VALUEHI:
+ pValues[nProp] <<= GetOption( VOPT_SYNTAX );
+ break;
+ case SCDISPLAYOPT_ANCHOR:
+ pValues[nProp] <<= GetOption( VOPT_ANCHOR );
+ break;
+ case SCDISPLAYOPT_TEXTOVER:
+ pValues[nProp] <<= GetOption( VOPT_CLIPMARKS );
+ 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_OPTION_X:
+ pValues[nProp] <<= static_cast<sal_Int32>(rGrid.GetFieldSnapX());
+ break;
+ case SCGRIDOPT_OPTION_Y:
+ pValues[nProp] <<= static_cast<sal_Int32>(rGrid.GetFieldSnapY());
+ 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 000000000..9bd14de22
--- /dev/null
+++ b/sc/source/core/tool/webservicelink.cxx
@@ -0,0 +1,98 @@
+/* -*- 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 <webservicelink.hxx>
+#include <brdcst.hxx>
+#include <document.hxx>
+#include <sc.hrc>
+
+ScWebServiceLink::ScWebServiceLink(ScDocument* pD, const OUString& rURL)
+ : ::sfx2::SvBaseLink(SfxLinkUpdateMode::ALWAYS, SotClipboardFormatId::STRING)
+ , pDoc(pD)
+ , aURL(rURL)
+ , 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 000000000..f6fb26cba
--- /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: */